From fbfb02ac903d37b774f61fc5a6ddc5ebfaaffafb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jun 2024 18:46:23 +0200 Subject: [PATCH 01/13] gh-120057: Add os.environ.refresh() method --- Doc/library/os.rst | 6 +++++ Doc/whatsnew/3.14.rst | 6 +++++ Lib/os.py | 25 ++++++++++++++++--- Lib/test/test_os.py | 16 ++++++++++++ ...-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst | 3 +++ Modules/clinic/posixmodule.c.h | 20 ++++++++++++++- Modules/posixmodule.c | 15 +++++++++++ 7 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index b93b06d4e72afc..2b1e1de9a831e1 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -193,6 +193,9 @@ 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 outside Python. + 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. @@ -222,6 +225,9 @@ process and user. :data:`os.environ`, and when one of the :meth:`pop` or :meth:`clear` methods is called. + .. versionchanged:: 3.14 + Added the :meth:`!os.environ.refresh()` method. + .. versionchanged:: 3.9 Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b2dd80b64a691a..5579800e5c17d0 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -92,6 +92,12 @@ ast Added :func:`ast.compare` for comparing two ASTs. (Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.) +os +-- + +* Added the :meth:`os.environ.refresh() ` method to update + :data:`os.environ` with environment changes made outside Python. + (Contributed by Victor Stinner in :gh:`120057`.) Optimizations diff --git a/Lib/os.py b/Lib/os.py index 0408e2db79e66e..df212c06d985f9 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -64,6 +64,10 @@ def _get_exports_list(module): from posix import _have_functions except ImportError: pass + try: + from posix import _create_environ + except ImportError: + pass import posix __all__.extend(_get_exports_list(posix)) @@ -88,6 +92,10 @@ def _get_exports_list(module): from nt import _have_functions except ImportError: pass + try: + from nt import _create_environ + except ImportError: + pass else: raise ImportError('no os specific module found') @@ -773,7 +781,18 @@ def __ror__(self, other): new.update(self) return new -def _createenviron(): + if _exists("_create_environ"): + def refresh(self): + environ = _create_environ() + if name == 'nt': + data = {} + for key, value in environ.items(): + data[encodekey(key)] = value + else: + data = environ + self._data = data + +def _create_environ_mapping(): if name == 'nt': # Where Env Var Names Must Be UPPERCASE def check_str(value): @@ -803,8 +822,8 @@ def decode(value): encode, decode) # unicode environ -environ = _createenviron() -del _createenviron +environ = _create_environ_mapping() +del _create_environ_mapping def getenv(key, default=None): diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index de5a86f676c4d5..38a327038fc1d1 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1298,6 +1298,22 @@ def test_ror_operator(self): self._test_underlying_process_env('_A_', '') self._test_underlying_process_env(overridden_key, original_value) + @unittest.skipUnless(hasattr(os.environ, 'refresh'), + 'need os.environ.refresh()') + def test_refresh(self): + # Use putenv() which doesn't update os.environ + try: + from posix import putenv + except ImportError: + from nt import putenv + + os.environ['test_env'] = 'python_value' + putenv("test_env", "new_value") + self.assertEqual(os.environ['test_env'], 'python_value') + + os.environ.refresh() + self.assertEqual(os.environ['test_env'], 'new_value') + class WalkTests(unittest.TestCase): """Tests for os.walk().""" diff --git a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst new file mode 100644 index 00000000000000..76916a7a4314ff --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst @@ -0,0 +1,3 @@ +Added the :meth:`os.environ.refresh() ` method to update +:data:`os.environ` with environment changes made outside Python. Patch by +Victor Stinner. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 69fc178331c09c..07b28fef3a57ea 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -12152,6 +12152,24 @@ os__is_inputhook_installed(PyObject *module, PyObject *Py_UNUSED(ignored)) return os__is_inputhook_installed_impl(module); } +PyDoc_STRVAR(os__create_environ__doc__, +"_create_environ($module, /)\n" +"--\n" +"\n" +"Create the environment dictionary."); + +#define OS__CREATE_ENVIRON_METHODDEF \ + {"_create_environ", (PyCFunction)os__create_environ, METH_NOARGS, os__create_environ__doc__}, + +static PyObject * +os__create_environ_impl(PyObject *module); + +static PyObject * +os__create_environ(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return os__create_environ_impl(module); +} + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -12819,4 +12837,4 @@ os__is_inputhook_installed(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=faaa5e5ffb7b165d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5ae2e5ffcd9c8a84 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 386e942d53f539..01e951040d548c 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16816,6 +16816,20 @@ os__is_inputhook_installed_impl(PyObject *module) return PyBool_FromLong(PyOS_InputHook != NULL); } +/*[clinic input] +os._create_environ + +Create the environment dictionary. +[clinic start generated code]*/ + +static PyObject * +os__create_environ_impl(PyObject *module) +/*[clinic end generated code: output=19d9039ab14f8ad4 input=a4c05686b34635e8]*/ +{ + return convertenviron(); +} + + static PyMethodDef posix_methods[] = { OS_STAT_METHODDEF @@ -17030,6 +17044,7 @@ static PyMethodDef posix_methods[] = { OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF OS__INPUTHOOK_METHODDEF OS__IS_INPUTHOOK_INSTALLED_METHODDEF + OS__CREATE_ENVIRON_METHODDEF {NULL, NULL} /* Sentinel */ }; From cdd9fecdf8c189ce73fffa65229b4f9052cce851 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 09:39:30 +0200 Subject: [PATCH 02/13] Fix os.environb --- Lib/os.py | 5 ++++- Lib/test/test_os.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/os.py b/Lib/os.py index df212c06d985f9..9804136aa4bac4 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -790,7 +790,10 @@ def refresh(self): data[encodekey(key)] = value else: data = environ - self._data = data + + # modify in-place to keep os.environb in sync + self._data.clear() + self._data.update(data) def _create_environ_mapping(): if name == 'nt': diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 38a327038fc1d1..74df151747f64b 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1310,9 +1310,13 @@ def test_refresh(self): os.environ['test_env'] = 'python_value' putenv("test_env", "new_value") self.assertEqual(os.environ['test_env'], 'python_value') + if hasattr(os, 'environb'): + self.assertEqual(os.environb[b'test_env'], b'python_value') os.environ.refresh() self.assertEqual(os.environ['test_env'], 'new_value') + if hasattr(os, 'environb'): + self.assertEqual(os.environb[b'test_env'], b'new_value') class WalkTests(unittest.TestCase): From 1afb8e50e05ccdee0e6b55b5862cc132caeea230 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 09:41:26 +0200 Subject: [PATCH 03/13] Fix doc --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2b1e1de9a831e1..73a94007a20697 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -193,7 +193,7 @@ 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 + The :meth:`!os.environ.refresh()` method updates :data:`os.environ` with changes to the environment made outside Python. This mapping may be used to modify the environment as well as query the From 283bdb2cc48bfa340ab1ad977fa83efb3628d3da Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 10:16:08 +0200 Subject: [PATCH 04/13] Fix Windows --- Lib/os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/os.py b/Lib/os.py index 9804136aa4bac4..7c8caf0a548558 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -787,7 +787,7 @@ def refresh(self): if name == 'nt': data = {} for key, value in environ.items(): - data[encodekey(key)] = value + data[self.encodekey(key)] = value else: data = environ From e6665b6c6f76dfde939cfb04bf05bfd1beb04411 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 11:19:07 +0200 Subject: [PATCH 05/13] add "in the same process" --- Doc/library/os.rst | 3 ++- Doc/whatsnew/3.14.rst | 3 ++- .../Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 73a94007a20697..30a3816afb511c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -194,7 +194,8 @@ process and user. 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 outside Python. + :data:`os.environ` with changes to the environment made in the same process + outside Python. This mapping may be used to modify the environment as well as query the environment. :func:`putenv` will be called automatically when the mapping diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 5579800e5c17d0..d462a3d2b123c2 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -96,7 +96,8 @@ os -- * Added the :meth:`os.environ.refresh() ` method to update - :data:`os.environ` with environment changes made outside Python. + :data:`os.environ` with environment changes made in the same process outside + Python. (Contributed by Victor Stinner in :gh:`120057`.) diff --git a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst index 76916a7a4314ff..836bcd12822562 100644 --- a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst +++ b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst @@ -1,3 +1,3 @@ Added the :meth:`os.environ.refresh() ` method to update -:data:`os.environ` with environment changes made outside Python. Patch by -Victor Stinner. +:data:`os.environ` with environment changes made in the same process outside +Python. Patch by Victor Stinner. From 12b1a7204a068a14c60b4dddcf4cf3ee93fa217f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 12:25:00 +0200 Subject: [PATCH 06/13] doc: Put 3.14 after 3.9 --- Doc/library/os.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 30a3816afb511c..8ff0191cc3bba5 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -226,12 +226,12 @@ process and user. :data:`os.environ`, and when one of the :meth:`pop` or :meth:`clear` methods is called. - .. versionchanged:: 3.14 - Added the :meth:`!os.environ.refresh()` method. - .. 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 From 3fbb1349a4390c0bde394e8b438a6e036b49d81a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 14:55:16 +0200 Subject: [PATCH 07/13] Address Serhiy's review --- Lib/os.py | 5 ++--- Lib/test/test_os.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index 7c8caf0a548558..67f4caeaaea605 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -785,9 +785,8 @@ def __ror__(self, other): def refresh(self): environ = _create_environ() if name == 'nt': - data = {} - for key, value in environ.items(): - data[self.encodekey(key)] = value + data = {self.encodekey(key): value + for key, value in environ.items()} else: data = environ diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 74df151747f64b..746c0b21e90a85 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1318,6 +1318,16 @@ def test_refresh(self): if hasattr(os, 'environb'): self.assertEqual(os.environb[b'test_env'], b'new_value') + if hasattr(os, 'environb'): + os.environb[b'test_env'] = b'python_value2' + 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() + self.assertEqual(os.environb[b'test_env'], b'new_value2') + self.assertEqual(os.environ['test_env'], 'new_value2') + class WalkTests(unittest.TestCase): """Tests for os.walk().""" From ea656f03674abaa49ef72579f4399dbee88de1f5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 15:15:02 +0200 Subject: [PATCH 08/13] Tests: add has_environb --- Lib/test/test_os.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 746c0b21e90a85..59bd9e998bf9bf 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1302,6 +1302,7 @@ def test_ror_operator(self): 'need os.environ.refresh()') def test_refresh(self): # Use putenv() which doesn't update os.environ + has_environb = hasattr(os, 'environb') try: from posix import putenv except ImportError: @@ -1310,15 +1311,16 @@ def test_refresh(self): os.environ['test_env'] = 'python_value' putenv("test_env", "new_value") self.assertEqual(os.environ['test_env'], 'python_value') - if hasattr(os, 'environb'): + if has_environb: self.assertEqual(os.environb[b'test_env'], b'python_value') os.environ.refresh() self.assertEqual(os.environ['test_env'], 'new_value') - if hasattr(os, 'environb'): + if has_environb: self.assertEqual(os.environb[b'test_env'], b'new_value') - if hasattr(os, 'environb'): + if has_environb: + # test os.environb.refresh() os.environb[b'test_env'] = b'python_value2' putenv("test_env", "new_value2") self.assertEqual(os.environb[b'test_env'], b'python_value2') From 558617cc70b0562b5bd72238ce2261b532636242 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 15:41:12 +0200 Subject: [PATCH 09/13] Doc: add "or by os.putenv()" Test: use directly os.putenv(). --- Doc/library/os.rst | 2 +- Doc/whatsnew/3.14.rst | 2 +- Lib/test/test_os.py | 11 ++++------- .../2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 8ff0191cc3bba5..c12dc8bb1fc9d6 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -195,7 +195,7 @@ process and user. The :meth:`!os.environ.refresh()` method updates :data:`os.environ` with changes to the environment made in the same process - outside Python. + outside Python or by :func:`os.putenv`. This mapping may be used to modify the environment as well as query the environment. :func:`putenv` will be called automatically when the mapping diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index d462a3d2b123c2..3517ed31b00df6 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -97,7 +97,7 @@ os * Added the :meth:`os.environ.refresh() ` method to update :data:`os.environ` with environment changes made in the same process outside - Python. + Python or by :func:`os.putenv`. (Contributed by Victor Stinner in :gh:`120057`.) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 59bd9e998bf9bf..62c5418e50e47a 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1301,15 +1301,12 @@ def test_ror_operator(self): @unittest.skipUnless(hasattr(os.environ, 'refresh'), 'need os.environ.refresh()') def test_refresh(self): - # Use putenv() which doesn't update os.environ + # Test os.environ.refresh() with putenv() which doesn't update + # os.environ has_environb = hasattr(os, 'environb') - try: - from posix import putenv - except ImportError: - from nt import putenv os.environ['test_env'] = 'python_value' - putenv("test_env", "new_value") + os.putenv("test_env", "new_value") self.assertEqual(os.environ['test_env'], 'python_value') if has_environb: self.assertEqual(os.environb[b'test_env'], b'python_value') @@ -1322,7 +1319,7 @@ def test_refresh(self): if has_environb: # test os.environb.refresh() os.environb[b'test_env'] = b'python_value2' - putenv("test_env", "new_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') diff --git a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst index 836bcd12822562..dc5fde76ec87cd 100644 --- a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst +++ b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst @@ -1,3 +1,3 @@ Added the :meth:`os.environ.refresh() ` method to update :data:`os.environ` with environment changes made in the same process outside -Python. Patch by Victor Stinner. +Python or by :func:`os.putenv`. Patch by Victor Stinner. From c140148f176a45c9d22215ff6bcffdb634e37df7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 17:21:25 +0200 Subject: [PATCH 10/13] Address also Serhiy's review * Test os.unsetenv() * Document os.unsetenv() * Minor refactoring --- Doc/library/os.rst | 6 +++--- Doc/whatsnew/3.14.rst | 4 ++-- Lib/os.py | 6 ++---- Lib/test/test_os.py | 17 +++++++++++++---- ...24-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst | 5 +++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index c12dc8bb1fc9d6..285964eb923b36 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -193,9 +193,9 @@ 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 in the same process - outside Python or by :func:`os.putenv`. + 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 diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 3517ed31b00df6..029f1019ff377b 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -96,8 +96,8 @@ os -- * Added the :meth:`os.environ.refresh() ` method to update - :data:`os.environ` with environment changes made in the same process outside - Python or by :func:`os.putenv`. + :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. (Contributed by Victor Stinner in :gh:`120057`.) diff --git a/Lib/os.py b/Lib/os.py index 67f4caeaaea605..4b48afb040e565 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -783,12 +783,10 @@ def __ror__(self, other): if _exists("_create_environ"): def refresh(self): - environ = _create_environ() + data = _create_environ() if name == 'nt': data = {self.encodekey(key): value - for key, value in environ.items()} - else: - data = environ + for key, value in data.items()} # modify in-place to keep os.environb in sync self._data.clear() diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 62c5418e50e47a..148777cd8139dd 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1298,13 +1298,11 @@ def test_ror_operator(self): self._test_underlying_process_env('_A_', '') self._test_underlying_process_env(overridden_key, original_value) - @unittest.skipUnless(hasattr(os.environ, 'refresh'), - 'need os.environ.refresh()') def test_refresh(self): - # Test os.environ.refresh() with putenv() which doesn't update - # os.environ + # Test os.environ.refresh() has_environb = hasattr(os, 'environb') + # Test with putenv() which doesn't update os.environ os.environ['test_env'] = 'python_value' os.putenv("test_env", "new_value") self.assertEqual(os.environ['test_env'], 'python_value') @@ -1316,6 +1314,17 @@ def test_refresh(self): if has_environb: self.assertEqual(os.environb[b'test_env'], b'new_value') + # Test with unsetenv() which doesn't update os.environ + os.unsetenv('test_env') + self.assertEqual(os.environ['test_env'], 'new_value') + if has_environb: + self.assertEqual(os.environb[b'test_env'], b'new_value') + + os.environ.refresh() + self.assertNotIn('test_env', os.environ) + if has_environb: + self.assertNotIn(b'test_env', os.environb) + if has_environb: # test os.environb.refresh() os.environb[b'test_env'] = b'python_value2' diff --git a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst index dc5fde76ec87cd..5a30f67ce97809 100644 --- a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst +++ b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst @@ -1,3 +1,4 @@ Added the :meth:`os.environ.refresh() ` method to update -:data:`os.environ` with environment changes made in the same process outside -Python or by :func:`os.putenv`. Patch by Victor Stinner. +: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. +Patch by Victor Stinner. From 64c415d746d4269157e4804c2af696a0e8c4fb3e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 17:27:00 +0200 Subject: [PATCH 11/13] doc: add links to refresh method --- Doc/library/os.rst | 4 ++++ Doc/whatsnew/3.14.rst | 2 +- .../Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 285964eb923b36..360d71e70960c7 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -568,6 +568,8 @@ 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. + .. note:: On some platforms, including FreeBSD and macOS, setting ``environ`` may @@ -816,6 +818,8 @@ 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. + .. audit-event:: os.unsetenv key os.unsetenv .. versionchanged:: 3.9 diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 029f1019ff377b..b77ff30a8fbbee 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -95,7 +95,7 @@ Added :func:`ast.compare` for comparing two ASTs. os -- -* Added the :meth:`os.environ.refresh() ` method to update +* Added 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. (Contributed by Victor Stinner in :gh:`120057`.) diff --git a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst index 5a30f67ce97809..955be59821ee0c 100644 --- a/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst +++ b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst @@ -1,4 +1,4 @@ -Added the :meth:`os.environ.refresh() ` method to update +Added 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. Patch by Victor Stinner. From 6aaadc3bb655fd0db8ffa400538b0c731c1b00dd Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 17:39:44 +0200 Subject: [PATCH 12/13] Add test on os.environb.refresh() with unsetenv() --- Lib/test/test_os.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 148777cd8139dd..00116473efb71d 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1326,7 +1326,7 @@ def test_refresh(self): self.assertNotIn(b'test_env', os.environb) if has_environb: - # test os.environb.refresh() + # test os.environb.refresh() 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') @@ -1336,6 +1336,14 @@ def test_refresh(self): self.assertEqual(os.environb[b'test_env'], b'new_value2') self.assertEqual(os.environ['test_env'], 'new_value2') + # test os.environb.refresh() with unsetenv() + os.unsetenv('test_env') + self.assertEqual(os.environb[b'test_env'], b'new_value') + self.assertEqual(os.environ['test_env'], 'new_value') + + os.environb.refresh() + self.assertNotIn(b'test_env', os.environb) + self.assertNotIn('test_env', os.environ) class WalkTests(unittest.TestCase): """Tests for os.walk().""" From ae1ab625d5b81d6d7309b1b604df4685c4c8f12f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 10 Jun 2024 18:07:36 +0200 Subject: [PATCH 13/13] Fix test_refresh() --- Lib/test/test_os.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 00116473efb71d..7cd56e979c6645 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1338,8 +1338,8 @@ def test_refresh(self): # test os.environb.refresh() with unsetenv() os.unsetenv('test_env') - self.assertEqual(os.environb[b'test_env'], b'new_value') - self.assertEqual(os.environ['test_env'], 'new_value') + self.assertEqual(os.environb[b'test_env'], b'new_value2') + self.assertEqual(os.environ['test_env'], 'new_value2') os.environb.refresh() self.assertNotIn(b'test_env', os.environb)