summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCharles Burkland <charles.abucher@gmail.com>2020-03-13 16:04:43 (GMT)
committerGitHub <noreply@github.com>2020-03-13 16:04:43 (GMT)
commitd648ef10c5c7659ed3c9f34d5c751dc55e2c6007 (patch)
treee8cbea457b659acde56f062bdc6b09856bd66d61
parent38965ec5411da60d312b59be281f3510d58e0cf1 (diff)
downloadcpython-d648ef10c5c7659ed3c9f34d5c751dc55e2c6007.zip
cpython-d648ef10c5c7659ed3c9f34d5c751dc55e2c6007.tar.gz
cpython-d648ef10c5c7659ed3c9f34d5c751dc55e2c6007.tar.bz2
bpo-36144: Update os.environ and os.environb for PEP 584 (#18911)
-rw-r--r--Doc/library/os.rst6
-rw-r--r--Lib/os.py20
-rw-r--r--Lib/test/test_os.py90
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2020-03-10-19-22-31.bpo-36144.LABm7W.rst2
5 files changed, 118 insertions, 1 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index c9d6fb2..d8bca2f 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -135,6 +135,9 @@ process and user.
``os.environ``, and when one of the :meth:`pop` or :meth:`clear` methods is
called.
+ .. versionchanged:: 3.9
+ Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.
+
.. data:: environb
@@ -148,6 +151,9 @@ process and user.
.. versionadded:: 3.2
+ .. versionchanged:: 3.9
+ Updated to support :pep:`584`'s merge (``|``) and update (``|=``) operators.
+
.. function:: chdir(path)
fchdir(fd)
diff --git a/Lib/os.py b/Lib/os.py
index ab75b94..8459baa 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -659,7 +659,7 @@ def get_exec_path(env=None):
# Change environ to automatically call putenv() and unsetenv()
-from _collections_abc import MutableMapping
+from _collections_abc import MutableMapping, Mapping
class _Environ(MutableMapping):
def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue):
@@ -714,6 +714,24 @@ class _Environ(MutableMapping):
self[key] = value
return self[key]
+ def __ior__(self, other):
+ self.update(other)
+ return self
+
+ def __or__(self, other):
+ if not isinstance(other, Mapping):
+ return NotImplemented
+ new = dict(self)
+ new.update(other)
+ return new
+
+ def __ror__(self, other):
+ if not isinstance(other, Mapping):
+ return NotImplemented
+ new = dict(other)
+ new.update(self)
+ return new
+
def _createenviron():
if name == 'nt':
# Where Env Var Names Must Be UPPERCASE
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 9e3a169..9c96544 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1026,6 +1026,96 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
def test_iter_error_when_changing_os_environ_values(self):
self._test_environ_iteration(os.environ.values())
+ def _test_underlying_process_env(self, var, expected):
+ if not (unix_shell and os.path.exists(unix_shell)):
+ return
+
+ with os.popen(f"{unix_shell} -c 'echo ${var}'") as popen:
+ value = popen.read().strip()
+
+ self.assertEqual(expected, value)
+
+ def test_or_operator(self):
+ overridden_key = '_TEST_VAR_'
+ original_value = 'original_value'
+ os.environ[overridden_key] = original_value
+
+ new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
+ expected = dict(os.environ)
+ expected.update(new_vars_dict)
+
+ actual = os.environ | new_vars_dict
+ self.assertDictEqual(expected, actual)
+ self.assertEqual('3', actual[overridden_key])
+
+ new_vars_items = new_vars_dict.items()
+ self.assertIs(NotImplemented, os.environ.__or__(new_vars_items))
+
+ self._test_underlying_process_env('_A_', '')
+ self._test_underlying_process_env(overridden_key, original_value)
+
+ def test_ior_operator(self):
+ overridden_key = '_TEST_VAR_'
+ os.environ[overridden_key] = 'original_value'
+
+ new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
+ expected = dict(os.environ)
+ expected.update(new_vars_dict)
+
+ os.environ |= new_vars_dict
+ self.assertEqual(expected, os.environ)
+ self.assertEqual('3', os.environ[overridden_key])
+
+ self._test_underlying_process_env('_A_', '1')
+ self._test_underlying_process_env(overridden_key, '3')
+
+ def test_ior_operator_invalid_dicts(self):
+ os_environ_copy = os.environ.copy()
+ with self.assertRaises(TypeError):
+ dict_with_bad_key = {1: '_A_'}
+ os.environ |= dict_with_bad_key
+
+ with self.assertRaises(TypeError):
+ dict_with_bad_val = {'_A_': 1}
+ os.environ |= dict_with_bad_val
+
+ # Check nothing was added.
+ self.assertEqual(os_environ_copy, os.environ)
+
+ def test_ior_operator_key_value_iterable(self):
+ overridden_key = '_TEST_VAR_'
+ os.environ[overridden_key] = 'original_value'
+
+ new_vars_items = (('_A_', '1'), ('_B_', '2'), (overridden_key, '3'))
+ expected = dict(os.environ)
+ expected.update(new_vars_items)
+
+ os.environ |= new_vars_items
+ self.assertEqual(expected, os.environ)
+ self.assertEqual('3', os.environ[overridden_key])
+
+ self._test_underlying_process_env('_A_', '1')
+ self._test_underlying_process_env(overridden_key, '3')
+
+ def test_ror_operator(self):
+ overridden_key = '_TEST_VAR_'
+ original_value = 'original_value'
+ os.environ[overridden_key] = original_value
+
+ new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
+ expected = dict(new_vars_dict)
+ expected.update(os.environ)
+
+ actual = new_vars_dict | os.environ
+ self.assertDictEqual(expected, actual)
+ self.assertEqual(original_value, actual[overridden_key])
+
+ new_vars_items = new_vars_dict.items()
+ self.assertIs(NotImplemented, os.environ.__ror__(new_vars_items))
+
+ self._test_underlying_process_env('_A_', '')
+ self._test_underlying_process_env(overridden_key, original_value)
+
class WalkTests(unittest.TestCase):
"""Tests for os.walk()."""
diff --git a/Misc/ACKS b/Misc/ACKS
index cc5694a..37cf7af 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -240,6 +240,7 @@ Lars Buitinck
Dick Bulterman
Bill Bumgarner
Jimmy Burgett
+Charles Burkland
Edmond Burnett
Tommy Burnette
Roger Burnham
diff --git a/Misc/NEWS.d/next/Library/2020-03-10-19-22-31.bpo-36144.LABm7W.rst b/Misc/NEWS.d/next/Library/2020-03-10-19-22-31.bpo-36144.LABm7W.rst
new file mode 100644
index 0000000..1653971
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-03-10-19-22-31.bpo-36144.LABm7W.rst
@@ -0,0 +1,2 @@
+Updated :data:`os.environ` and :data:`os.environb` to support :pep:`584`'s
+merge (``|``) and update (``|=``) operators.