From 4a0d574273819b2b5006decb661da05b3baa8a4b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 5 Nov 2024 08:43:34 +0100 Subject: [PATCH] gh-120057: Add os.reload_environ() function (#126268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- Doc/library/os.rst | 33 ++++++++++++++----- Doc/whatsnew/3.14.rst | 7 ++-- Lib/os.py | 25 +++++++------- Lib/test/test_os.py | 16 ++++----- ...-11-01-10-35-49.gh-issue-120057.YWy81Q.rst | 2 ++ 5 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-01-10-35-49.gh-issue-120057.YWy81Q.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index f9cded40c2c755..c0354b2280c45c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -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. @@ -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 @@ -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() @@ -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() ` method. + See also the :func:`os.reload_environ` function. .. note:: @@ -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() ` method. + See also the :func:`os.reload_environ` function. .. audit-event:: os.unsetenv key os.unsetenv diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 80c1a93b95a6af..9300de440cdc30 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -365,9 +365,10 @@ operator os -- -* Add the :data:`os.environ.refresh() ` 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`.) diff --git a/Lib/os.py b/Lib/os.py index aaa758d955fe4c..9c2258e6ccf5ba 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -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 @@ -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. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 307f0f11ddc33f..9a4be78556c648 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -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 @@ -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') @@ -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) diff --git a/Misc/NEWS.d/next/Library/2024-11-01-10-35-49.gh-issue-120057.YWy81Q.rst b/Misc/NEWS.d/next/Library/2024-11-01-10-35-49.gh-issue-120057.YWy81Q.rst new file mode 100644 index 00000000000000..ded60a3f57bca3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-01-10-35-49.gh-issue-120057.YWy81Q.rst @@ -0,0 +1,2 @@ +Replace the ``os.environ.refresh()`` method with a new +:func:`os.reload_environ` function. Patch by Victor Stinner.