]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-36144: Update os.environ and os.environb for PEP 584 (#18911)
authorCharles Burkland <charles.abucher@gmail.com>
Fri, 13 Mar 2020 16:04:43 +0000 (09:04 -0700)
committerGitHub <noreply@github.com>
Fri, 13 Mar 2020 16:04:43 +0000 (09:04 -0700)
Doc/library/os.rst
Lib/os.py
Lib/test/test_os.py
Misc/ACKS
Misc/NEWS.d/next/Library/2020-03-10-19-22-31.bpo-36144.LABm7W.rst [new file with mode: 0644]

index c9d6fb232c0a8e0614991a3337395f2b6ad9a24f..d8bca2f950a9e64f5801f869502bf6b9947fc1e2 100644 (file)
@@ -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)
index ab75b94d4fe45fe2d101a7c6b134b997424b95cf..8459baa2e4dfdaa7d39791c4ccf965161e2920e0 100644 (file)
--- 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
index 9e3a1695dfb34ec162c810ca1c8cbbea0f9f76ae..9c965444f04b232f87bf2eebb18b174d042a629e 100644 (file)
@@ -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()."""
index cc5694af008b3253bb15fa016769764d59df6c09..37cf7af0c2ec517f24bc762fb118120aa604ea46 100644 (file)
--- 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 (file)
index 0000000..1653971
--- /dev/null
@@ -0,0 +1,2 @@
+Updated :data:`os.environ` and :data:`os.environb` to support :pep:`584`'s
+merge (``|``) and update (``|=``) operators.