diff options
-rw-r--r-- | Doc/library/os.rst | 6 | ||||
-rw-r--r-- | Lib/os.py | 20 | ||||
-rw-r--r-- | Lib/test/test_os.py | 90 | ||||
-rw-r--r-- | Misc/ACKS | 1 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2020-03-10-19-22-31.bpo-36144.LABm7W.rst | 2 |
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) @@ -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().""" @@ -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. |