gh-120057: Add os.reload_environ() function (#126268)

Replace the os.environ.refresh() method with a new
os.reload_environ() function.

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
Victor Stinner 2024-11-05 08:43:34 +01:00 committed by GitHub
parent d3840503b0
commit 4a0d574273
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 31 deletions

View File

@ -193,10 +193,6 @@ process and user.
to the environment made after this time are not reflected in :data:`os.environ`, to the environment made after this time are not reflected in :data:`os.environ`,
except for changes made by modifying :data:`os.environ` directly. 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 This mapping may be used to modify the environment as well as query the
environment. :func:`putenv` will be called automatically when the mapping environment. :func:`putenv` will be called automatically when the mapping
is modified. is modified.
@ -226,12 +222,13 @@ process and user.
:data:`os.environ`, and when one of the :meth:`pop` or :meth:`clear` methods is :data:`os.environ`, and when one of the :meth:`pop` or :meth:`clear` methods is
called. called.
.. seealso::
The :func:`os.reload_environ` function.
.. versionchanged:: 3.9 .. versionchanged:: 3.9
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators. Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.
.. versionchanged:: 3.14
Added the :meth:`!os.environ.refresh` method.
.. data:: environb .. data:: environb
@ -249,6 +246,24 @@ process and user.
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators. 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) .. function:: chdir(path)
fchdir(fd) fchdir(fd)
getcwd() getcwd()
@ -568,7 +583,7 @@ process and user.
of :data:`os.environ`. This also applies to :func:`getenv` and :func:`getenvb`, which 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. 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:: .. note::
@ -818,7 +833,7 @@ process and user.
don't update :data:`os.environ`, so it is actually preferable to delete items of don't update :data:`os.environ`, so it is actually preferable to delete items of
:data:`os.environ`. :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 .. audit-event:: os.unsetenv key os.unsetenv

View File

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

View File

@ -765,17 +765,6 @@ class _Environ(MutableMapping):
new.update(self) new.update(self)
return new 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(): def _create_environ_mapping():
if name == 'nt': if name == 'nt':
# Where Env Var Names Must Be UPPERCASE # Where Env Var Names Must Be UPPERCASE
@ -810,6 +799,20 @@ environ = _create_environ_mapping()
del _create_environ_mapping 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): def getenv(key, default=None):
"""Get an environment variable, return None if it doesn't exist. """Get an environment variable, return None if it doesn't exist.
The optional second argument can specify an alternate default. The optional second argument can specify an alternate default.

View File

@ -1298,8 +1298,8 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
self._test_underlying_process_env('_A_', '') self._test_underlying_process_env('_A_', '')
self._test_underlying_process_env(overridden_key, original_value) self._test_underlying_process_env(overridden_key, original_value)
def test_refresh(self): def test_reload_environ(self):
# Test os.environ.refresh() # Test os.reload_environ()
has_environb = hasattr(os, 'environb') has_environb = hasattr(os, 'environb')
# Test with putenv() which doesn't update os.environ # Test with putenv() which doesn't update os.environ
@ -1309,7 +1309,7 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
if has_environb: if has_environb:
self.assertEqual(os.environb[b'test_env'], b'python_value') self.assertEqual(os.environb[b'test_env'], b'python_value')
os.environ.refresh() os.reload_environ()
self.assertEqual(os.environ['test_env'], 'new_value') self.assertEqual(os.environ['test_env'], 'new_value')
if has_environb: if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value') self.assertEqual(os.environb[b'test_env'], b'new_value')
@ -1320,28 +1320,28 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
if has_environb: if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value') self.assertEqual(os.environb[b'test_env'], b'new_value')
os.environ.refresh() os.reload_environ()
self.assertNotIn('test_env', os.environ) self.assertNotIn('test_env', os.environ)
if has_environb: if has_environb:
self.assertNotIn(b'test_env', os.environb) self.assertNotIn(b'test_env', os.environb)
if has_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.environb[b'test_env'] = b'python_value2'
os.putenv("test_env", "new_value2") os.putenv("test_env", "new_value2")
self.assertEqual(os.environb[b'test_env'], b'python_value2') self.assertEqual(os.environb[b'test_env'], b'python_value2')
self.assertEqual(os.environ['test_env'], '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.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], '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') os.unsetenv('test_env')
self.assertEqual(os.environb[b'test_env'], b'new_value2') self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], '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(b'test_env', os.environb)
self.assertNotIn('test_env', os.environ) self.assertNotIn('test_env', os.environ)

View File

@ -0,0 +1,2 @@
Replace the ``os.environ.refresh()`` method with a new
:func:`os.reload_environ` function. Patch by Victor Stinner.