Skip to content

Commit d648ef1

Browse files
author
Charles Burkland
authored
bpo-36144: Update os.environ and os.environb for PEP 584 (#18911)
1 parent 38965ec commit d648ef1

File tree

5 files changed

+118
-1
lines changed

5 files changed

+118
-1
lines changed

Doc/library/os.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ process and user.
135135
``os.environ``, and when one of the :meth:`pop` or :meth:`clear` methods is
136136
called.
137137

138+
.. versionchanged:: 3.9
139+
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.
140+
138141

139142
.. data:: environb
140143

@@ -148,6 +151,9 @@ process and user.
148151

149152
.. versionadded:: 3.2
150153

154+
.. versionchanged:: 3.9
155+
Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.
156+
151157

152158
.. function:: chdir(path)
153159
fchdir(fd)

Lib/os.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ def get_exec_path(env=None):
659659

660660

661661
# Change environ to automatically call putenv() and unsetenv()
662-
from _collections_abc import MutableMapping
662+
from _collections_abc import MutableMapping, Mapping
663663

664664
class _Environ(MutableMapping):
665665
def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue):
@@ -714,6 +714,24 @@ def setdefault(self, key, value):
714714
self[key] = value
715715
return self[key]
716716

717+
def __ior__(self, other):
718+
self.update(other)
719+
return self
720+
721+
def __or__(self, other):
722+
if not isinstance(other, Mapping):
723+
return NotImplemented
724+
new = dict(self)
725+
new.update(other)
726+
return new
727+
728+
def __ror__(self, other):
729+
if not isinstance(other, Mapping):
730+
return NotImplemented
731+
new = dict(other)
732+
new.update(self)
733+
return new
734+
717735
def _createenviron():
718736
if name == 'nt':
719737
# Where Env Var Names Must Be UPPERCASE

Lib/test/test_os.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,96 @@ def test_iter_error_when_changing_os_environ_items(self):
10261026
def test_iter_error_when_changing_os_environ_values(self):
10271027
self._test_environ_iteration(os.environ.values())
10281028

1029+
def _test_underlying_process_env(self, var, expected):
1030+
if not (unix_shell and os.path.exists(unix_shell)):
1031+
return
1032+
1033+
with os.popen(f"{unix_shell} -c 'echo ${var}'") as popen:
1034+
value = popen.read().strip()
1035+
1036+
self.assertEqual(expected, value)
1037+
1038+
def test_or_operator(self):
1039+
overridden_key = '_TEST_VAR_'
1040+
original_value = 'original_value'
1041+
os.environ[overridden_key] = original_value
1042+
1043+
new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
1044+
expected = dict(os.environ)
1045+
expected.update(new_vars_dict)
1046+
1047+
actual = os.environ | new_vars_dict
1048+
self.assertDictEqual(expected, actual)
1049+
self.assertEqual('3', actual[overridden_key])
1050+
1051+
new_vars_items = new_vars_dict.items()
1052+
self.assertIs(NotImplemented, os.environ.__or__(new_vars_items))
1053+
1054+
self._test_underlying_process_env('_A_', '')
1055+
self._test_underlying_process_env(overridden_key, original_value)
1056+
1057+
def test_ior_operator(self):
1058+
overridden_key = '_TEST_VAR_'
1059+
os.environ[overridden_key] = 'original_value'
1060+
1061+
new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
1062+
expected = dict(os.environ)
1063+
expected.update(new_vars_dict)
1064+
1065+
os.environ |= new_vars_dict
1066+
self.assertEqual(expected, os.environ)
1067+
self.assertEqual('3', os.environ[overridden_key])
1068+
1069+
self._test_underlying_process_env('_A_', '1')
1070+
self._test_underlying_process_env(overridden_key, '3')
1071+
1072+
def test_ior_operator_invalid_dicts(self):
1073+
os_environ_copy = os.environ.copy()
1074+
with self.assertRaises(TypeError):
1075+
dict_with_bad_key = {1: '_A_'}
1076+
os.environ |= dict_with_bad_key
1077+
1078+
with self.assertRaises(TypeError):
1079+
dict_with_bad_val = {'_A_': 1}
1080+
os.environ |= dict_with_bad_val
1081+
1082+
# Check nothing was added.
1083+
self.assertEqual(os_environ_copy, os.environ)
1084+
1085+
def test_ior_operator_key_value_iterable(self):
1086+
overridden_key = '_TEST_VAR_'
1087+
os.environ[overridden_key] = 'original_value'
1088+
1089+
new_vars_items = (('_A_', '1'), ('_B_', '2'), (overridden_key, '3'))
1090+
expected = dict(os.environ)
1091+
expected.update(new_vars_items)
1092+
1093+
os.environ |= new_vars_items
1094+
self.assertEqual(expected, os.environ)
1095+
self.assertEqual('3', os.environ[overridden_key])
1096+
1097+
self._test_underlying_process_env('_A_', '1')
1098+
self._test_underlying_process_env(overridden_key, '3')
1099+
1100+
def test_ror_operator(self):
1101+
overridden_key = '_TEST_VAR_'
1102+
original_value = 'original_value'
1103+
os.environ[overridden_key] = original_value
1104+
1105+
new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
1106+
expected = dict(new_vars_dict)
1107+
expected.update(os.environ)
1108+
1109+
actual = new_vars_dict | os.environ
1110+
self.assertDictEqual(expected, actual)
1111+
self.assertEqual(original_value, actual[overridden_key])
1112+
1113+
new_vars_items = new_vars_dict.items()
1114+
self.assertIs(NotImplemented, os.environ.__ror__(new_vars_items))
1115+
1116+
self._test_underlying_process_env('_A_', '')
1117+
self._test_underlying_process_env(overridden_key, original_value)
1118+
10291119

10301120
class WalkTests(unittest.TestCase):
10311121
"""Tests for os.walk()."""

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ Lars Buitinck
240240
Dick Bulterman
241241
Bill Bumgarner
242242
Jimmy Burgett
243+
Charles Burkland
243244
Edmond Burnett
244245
Tommy Burnette
245246
Roger Burnham
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Updated :data:`os.environ` and :data:`os.environb` to support :pep:`584`'s
2+
merge (``|``) and update (``|=``) operators.

0 commit comments

Comments
 (0)