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 */ };