Skip to content

Commit

Permalink
gh-120057: Add os.reload_environ() function (#126268)
Browse files Browse the repository at this point in the history
Replace the os.environ.refresh() method with a new
os.reload_environ() function.

Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Adam Turner <[email protected]>
  • Loading branch information
3 people authored Nov 5, 2024
1 parent d384050 commit 4a0d574
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 31 deletions.
33 changes: 24 additions & 9 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,6 @@ process and user.
to the environment made after this time are not reflected in :data:`os.environ`,
except for changes made by modifying :data:`os.environ` directly.

The :meth:`!os.environ.refresh` method updates :data:`os.environ` with
changes to the environment made by :func:`os.putenv`, by
:func:`os.unsetenv`, or made outside Python in the same process.

This mapping may be used to modify the environment as well as query the
environment. :func:`putenv` will be called automatically when the mapping
is modified.
Expand Down Expand Up @@ -226,12 +222,13 @@ process and user.
:data:`os.environ`, and when one of the :meth:`pop` or :meth:`clear` methods is
called.

.. seealso::

The :func:`os.reload_environ` function.

.. versionchanged:: 3.9
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.

.. versionchanged:: 3.14
Added the :meth:`!os.environ.refresh` method.


.. data:: environb

Expand All @@ -249,6 +246,24 @@ process and user.
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.


.. function:: reload_environ()

The :data:`os.environ` and :data:`os.environb` mappings are a cache of
environment variables at the time that Python started.
As such, changes to the current process environment are not reflected
if made outside Python, or by :func:`os.putenv` or :func:`os.unsetenv`.
Use :func:`!os.reload_environ` to update :data:`os.environ` and :data:`os.environb`
with any such changes to the current process environment.

.. warning::
This function is not thread-safe. Calling it while the environment is
being modified in an other thread is an undefined behavior. Reading from
:data:`os.environ` or :data:`os.environb`, or calling :func:`os.getenv`
while reloading, may return an empty result.

.. versionadded:: next


.. function:: chdir(path)
fchdir(fd)
getcwd()
Expand Down Expand Up @@ -568,7 +583,7 @@ process and user.
of :data:`os.environ`. This also applies to :func:`getenv` and :func:`getenvb`, which
respectively use :data:`os.environ` and :data:`os.environb` in their implementations.

See also the :data:`os.environ.refresh() <os.environ>` method.
See also the :func:`os.reload_environ` function.

.. note::

Expand Down Expand Up @@ -818,7 +833,7 @@ process and user.
don't update :data:`os.environ`, so it is actually preferable to delete items of
:data:`os.environ`.

See also the :data:`os.environ.refresh() <os.environ>` method.
See also the :func:`os.reload_environ` function.

.. audit-event:: os.unsetenv key os.unsetenv

Expand Down
7 changes: 4 additions & 3 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,10 @@ operator
os
--

* Add the :data:`os.environ.refresh() <os.environ>` method to update
:data:`os.environ` with changes to the environment made by :func:`os.putenv`,
by :func:`os.unsetenv`, or made outside Python in the same process.
* Add the :func:`os.reload_environ` function to update :data:`os.environ` and
:data:`os.environb` with changes to the environment made by
:func:`os.putenv`, by :func:`os.unsetenv`, or made outside Python in the
same process.
(Contributed by Victor Stinner in :gh:`120057`.)


Expand Down
25 changes: 14 additions & 11 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,17 +765,6 @@ def __ror__(self, other):
new.update(self)
return new

if _exists("_create_environ"):
def refresh(self):
data = _create_environ()
if name == 'nt':
data = {self.encodekey(key): value
for key, value in data.items()}

# modify in-place to keep os.environb in sync
self._data.clear()
self._data.update(data)

def _create_environ_mapping():
if name == 'nt':
# Where Env Var Names Must Be UPPERCASE
Expand Down Expand Up @@ -810,6 +799,20 @@ def decode(value):
del _create_environ_mapping


if _exists("_create_environ"):
def reload_environ():
data = _create_environ()
if name == 'nt':
encodekey = environ.encodekey
data = {encodekey(key): value
for key, value in data.items()}

# modify in-place to keep os.environb in sync
env_data = environ._data
env_data.clear()
env_data.update(data)


def getenv(key, default=None):
"""Get an environment variable, return None if it doesn't exist.
The optional second argument can specify an alternate default.
Expand Down
16 changes: 8 additions & 8 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -1298,8 +1298,8 @@ def test_ror_operator(self):
self._test_underlying_process_env('_A_', '')
self._test_underlying_process_env(overridden_key, original_value)

def test_refresh(self):
# Test os.environ.refresh()
def test_reload_environ(self):
# Test os.reload_environ()
has_environb = hasattr(os, 'environb')

# Test with putenv() which doesn't update os.environ
Expand All @@ -1309,7 +1309,7 @@ def test_refresh(self):
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'python_value')

os.environ.refresh()
os.reload_environ()
self.assertEqual(os.environ['test_env'], 'new_value')
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value')
Expand All @@ -1320,28 +1320,28 @@ def test_refresh(self):
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value')

os.environ.refresh()
os.reload_environ()
self.assertNotIn('test_env', os.environ)
if has_environb:
self.assertNotIn(b'test_env', os.environb)

if has_environb:
# test os.environb.refresh() with putenv()
# test reload_environ() on os.environb with putenv()
os.environb[b'test_env'] = b'python_value2'
os.putenv("test_env", "new_value2")
self.assertEqual(os.environb[b'test_env'], b'python_value2')
self.assertEqual(os.environ['test_env'], 'python_value2')

os.environb.refresh()
os.reload_environ()
self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], 'new_value2')

# test os.environb.refresh() with unsetenv()
# test reload_environ() on os.environb with unsetenv()
os.unsetenv('test_env')
self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], 'new_value2')

os.environb.refresh()
os.reload_environ()
self.assertNotIn(b'test_env', os.environb)
self.assertNotIn('test_env', os.environ)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Replace the ``os.environ.refresh()`` method with a new
:func:`os.reload_environ` function. Patch by Victor Stinner.

0 comments on commit 4a0d574

Please sign in to comment.