summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/os.rst11
-rw-r--r--Doc/whatsnew/3.14.rst7
-rw-r--r--Lib/os.py25
-rw-r--r--Lib/test/test_os.py46
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-04-18-53-10.gh-issue-120057.RSD9_Z.rst4
-rw-r--r--Modules/clinic/posixmodule.c.h20
-rw-r--r--Modules/posixmodule.c15
7 files changed, 124 insertions, 4 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index b93b06d..360d71e 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 b2dd80b..b77ff30 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 0408e2d..4b48afb 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -64,6 +64,10 @@ if 'posix' in _names:
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 @@ elif 'nt' in _names:
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 @@ class _Environ(MutableMapping):
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 _createenviron():
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 2beb9ca..f93937f 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1298,6 +1298,52 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
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 0000000..955be59
--- /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 69fc178..07b28fe 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 5f943d4..a8fd5c4 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 */
};