10000 gh-120057: Add os.environ.refresh() method (#120059) · python/cpython@7aff2de · GitHub
[go: up one dir, main page]

Skip to content

Commit 7aff2de

Browse files
authored
gh-120057: Add os.environ.refresh() method (#120059)
1 parent 56c3815 commit 7aff2de

File tree

7 files changed

+124
-4
lines changed

7 files changed

+124
-4
lines changed

Doc/library/os.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ process and user.
193193
to the environment made after this time are not reflected in :data:`os.environ`,
194194
except for changes made by modifying :data:`os.environ` directly.
195195

196+
The :meth:`!os.environ.refresh()` method updates :data:`os.environ` with
197+
changes to the environment made by :func:`os.putenv`, by
198+
:func:`os.unsetenv`, or made outside Python in the same process.
199+
196200
This mapping may be used to modify the environment as well as query the
197201
environment. :func:`putenv` will be called automatically when the mapping
198202
is modified.
@@ -225,6 +229,9 @@ process and user.
225229
.. versionchanged:: 3.9
226230
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.
227231

232+
.. versionchanged:: 3.14
233+
Added the :meth:`!os.environ.refresh()` method.
234+
228235

229236
.. data:: environb
230237

@@ -561,6 +568,8 @@ process and user.
561568
of :data:`os.environ`. This also applies to :func:`getenv` and :func:`getenvb`, which
562569
respectively use :data:`os.environ` and :data:`os.environb` in their implementations.
563570

571+
See also the :data:`os.environ.refresh() <os.environ>` method.
572+
564573
.. note::
565574

566575
On some platforms, including FreeBSD and macOS, setting ``environ`` may
@@ -809,6 +818,8 @@ process and user.
809818
don't update :data:`os.environ`, so it is actually preferable to delete items of
810819
:data:`os.environ`.
811820

821+
See also the :data:`os.environ.refresh() <os.environ>` method.
822+
812823
.. audit-event:: os.unsetenv key os.unsetenv
813824

814825
.. versionchanged:: 3.9

Doc/whatsnew/3.14.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ ast
9292
Added :func:`ast.compare` for comparing two ASTs.
9393
(Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.)
9494

95+
os
96+
--
97+
98+
* Added the :data:`os.environ.refresh() <os.environ>` method to update
99+
:data:`os.environ` with changes to the environment made by :func:`os.putenv`,
100+
by :func:`os.unsetenv`, or made outside Python in the same process.
101+
(Contributed by Victor Stinner in :gh:`120057`.)
95102

96103

97104
Optimizations

Lib/os.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ def _get_exports_list(module):
6464
from posix import _have_functions
6565
except ImportError:
6666
pass
67+
try:
68+
from posix import _create_environ
69+
except ImportError:
70+
pass
6771

6872
import posix
6973
__all__.extend(_get_exports_list(posix))
@@ -88,6 +92,10 @@ def _get_exports_list(module):
8892
from nt import _have_functions
8993
except ImportError:
9094
pass
95+
try:
96+
from nt import _create_environ
97+
except ImportError:
98+
pass
9199

92100
else:
93101
raise ImportError('no os specific module found')
@@ -773,7 +781,18 @@ def __ror__(self, other):
773781
new.update(self)
774782
return new
775783

776-
def _createenviron():
784+
if _exists("_create_environ"):
785+
def refresh(self):
786+
data = _create_environ()
787+
if name == 'nt':
788+
data = {self.encodekey(key): value
789+
for key, value in data.items()}
790+
791+
# modify in-place to keep os.environb in sync
792+
self._data.clear()
793+
self._data.update(data)
794+
795+
def _create_environ_mapping():
777796
if name == 'nt':
778797
# Where Env Var Names Must Be UPPERCASE
779798
def check_str(value):
@@ -803,8 +822,8 @@ def decode(value):
803822
encode, decode)
804823

805824
# unicode environ
806-
environ = _createenviron()
807-
del _createenviron
825+
environ = _create_environ_mapping()
826+
del _create_environ_mapping
808827

809828

810829
def getenv(key, default=None):

Lib/test/test_os.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,52 @@ def test_ror_operator(self):
12981298
self._test_underlying_process_env('_A_', '')
12991299
self._test_underlying_process_env(overridden_key, original_value)
13001300

1301+
def test_refresh(self):
1302+
# Test os.environ.refresh()
1303+
has_environb = hasattr(os, 'environb')
1304+
1305+
# Test with putenv() which doesn't update os.environ
1306+
os.environ['test_env'] = 'python_value'
1307+
os.putenv("test_env", "new_value")
1308+
self.assertEqual(os.environ['test_env'], 'python_value')
1309+
if has_environb:
1310+
self.assertEqual(os.environb[b'test_env'], b'python_value')
1311+
1312+
os.environ.refresh()
1313+
self.assertEqual(os.environ['test_env'], 'new_value')
1314+
if has_environb:
1315+
self.assertEqual(os.environb[b'test_env'], b'new_value')
1316+
1317+
# Test with unsetenv() which doesn't update os.environ
1318+
os.unsetenv('test_env')
1319+
self.assertEqual(os.environ['test_env'], 'new_value')
1320+
if has_environb:
1321+
self.assertEqual(os.environb[b'test_env'], b'new_value')
1322+
1323+
os.environ.refresh()
1324+
self.assertNotIn('test_env', os.environ)
1325+
if has_environb:
1326+
self.assertNotIn(b'test_env', os.environb)
1327+
1328+
if has_environb:
1329+
# test os.environb.refresh() with putenv()
1330+
os.environb[b'test_env'] = b'python_value2'
1331+
os.putenv("test_env", "new_value2")
1332+
self.assertEqual(os.environb[b'test_env'], b'python_value2')
1333+
self.assertEqual(os.environ['test_env'], 'python_value2')
1334+
1335+
os.environb.refresh()
1336+
self.assertEqual(os.environb[b'test_env'], b'new_value2')
1337+
self.assertEqual(os.environ['test_env'], 'new_value2')
1338+
1339+
# test os.environb.refresh() with unsetenv()
1340+
os.unsetenv('test_env')
1341+
self.assertEqual(os.environb[b'test_env'], b'new_value2')
1342+
self.assertEqual(os.environ['test_env'], 'new_value2')
1343+
1344+
os.environb.refresh()
1345+
self.assertNotIn(b'test_env', os.environb)
1346+
self.assertNotIn('test_env', os.environ)
13011347

13021348
class WalkTests(unittest.TestCase):
13031349
"""Tests for os.walk()."""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Added the :data:`os.environ.refresh() <os.environ>` method to update
2+
:data:`os.environ` with changes to the environment made by :func:`os.putenv`,
3+
by :func:`os.unsetenv`, or made outside Python in the same process.
4+
Patch by Victor Stinner.

Modules/clinic/posixmodule.c.h

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16809,6 +16809,20 @@ os__is_inputhook_installed_impl(PyObject *module)
1680916809
return PyBool_FromLong(PyOS_InputHook != NULL);
1681016810
}
1681116811

16812+
/*[clinic input]
16813+
os._create_environ
16814+
16815+
Create the environment dictionary.
16816+
[clinic start generated code]*/
16817+
16818+
static PyObject *
16819+
os__create_environ_impl(PyObject *module)
16820+
/*[clinic end generated code: output=19d9039ab14f8ad4 input=a4c05686b34635e8]*/
16821+
{
16822+
return convertenviron();
16823+
}
16824+
16825+
1681216826
static PyMethodDef posix_methods[] = {
1681316827

1681416828
OS_STAT_METHODDEF
@@ -17023,6 +17037,7 @@ static PyMethodDef posix_methods[] = {
1702317037
OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF
1702417038
OS__INPUTHOOK_METHODDEF
1702517039
OS__IS_INPUTHOOK_INSTALLED_METHODDEF
17040+
OS__CREATE_ENVIRON_METHODDEF
1702617041
{NULL, NULL} /* Sentinel */
1702717042
};
1702817043

0 commit comments

Comments
 (0)
0