https://github.com/python/cpython/commit/7aff2de62bc28eb23888270b698c6b6915f69b21
commit: 7aff2de62bc28eb23888270b698c6b6915f69b21
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2024-06-10T16:34:17Z
summary:

gh-120057: Add os.environ.refresh() method (#120059)

files:
A Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst
M Doc/library/os.rst
M Doc/whatsnew/3.14.rst
M Lib/os.py
M Lib/test/test_os.py
M Modules/clinic/posixmodule.c.h
M Modules/posixmodule.c

diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index b93b06d4e72afc..360d71e70960c7 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -193,6 +193,10 @@ 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.
@@ -225,6 +229,9 @@ process and user.
    .. 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
 
@@ -561,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() <os.environ>` method.
+
    .. note::
 
       On some platforms, including FreeBSD and macOS, setting ``environ`` may
@@ -809,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() <os.environ>` 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 b2dd80b64a691a..b77ff30a8fbbee 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -92,6 +92,13 @@ ast
 Added :func:`ast.compare` for comparing two ASTs.
 (Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.)
 
+os
+--
+
+* Added 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.
+  (Contributed by Victor Stinner in :gh:`120057`.)
 
 
 Optimizations
diff --git a/Lib/os.py b/Lib/os.py
index 0408e2db79e66e..4b48afb040e565 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):
+            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
         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 2beb9ca8aa6ccb..f93937fb587386 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1298,6 +1298,52 @@ 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()
+        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')
+        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 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() 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()
+            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_value2')
+            self.assertEqual(os.environ['test_env'], 'new_value2')
+
+            os.environb.refresh()
+            self.assertNotIn(b'test_env', os.environb)
+            self.assertNotIn('test_env', os.environ)
 
 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..955be59821ee0c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst
@@ -0,0 +1,4 @@
+Added 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.
+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 5f943d4b1c8085..a8fd5c494769b5 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -16809,6 +16809,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
@@ -17023,6 +17037,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 */
 };
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to