From 416ee9e094d6cadc14b93b116d94c7cad166c0e0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 27 Oct 2020 18:00:03 -0400 Subject: [PATCH 01/14] Add test capturing missed expectation with uname_result._replace. --- Lib/test/test_platform.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index b5d21e54610e3d..bec100360d8d97 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -176,6 +176,14 @@ def test_uname_cast_to_tuple(self): ) self.assertEqual(tuple(res), expected) + def test_uname_replace(self): + res = platform.uname() + new = res._replace( + system='system', node='node', release='release', + version='version', machine='machine', processor='processor') + self.assertEqual(new.system, 'system') + self.assertEqual(new.processor, 'processor') + @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") def test_uname_processor(self): """ From 68b4f2f1952e1eb420ed76afeba133de893658b0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 27 Oct 2020 18:33:26 -0400 Subject: [PATCH 02/14] bpo-42163: Override uname_result._make to allow uname_result._replace to work (for everything but 'processor'. --- Lib/platform.py | 7 +++++++ Lib/test/test_platform.py | 9 +++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index e9f50ab622d316..0e8ee63eb1958f 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -797,6 +797,13 @@ def __iter__(self): (self.processor,) ) + @classmethod + def _make(cls, iterable): + result = tuple.__new__(cls, itertools.islice(iterable, 5)) + if len(result) != 6: + raise TypeError(f'Expected 5 arguments, got {len(result)}') + return result + def __getitem__(self, key): return tuple(iter(self))[key] diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index bec100360d8d97..5f64dff8dc7124 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -180,9 +180,14 @@ def test_uname_replace(self): res = platform.uname() new = res._replace( system='system', node='node', release='release', - version='version', machine='machine', processor='processor') + version='version', machine='machine') self.assertEqual(new.system, 'system') - self.assertEqual(new.processor, 'processor') + self.assertEqual(new.node, 'node') + self.assertEqual(new.release, 'release') + self.assertEqual(new.version, 'version') + self.assertEqual(new.machine, 'machine') + # processor cannot be replaced + self.assertEqual(new.processor, res.processor) @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") def test_uname_processor(self): From 8686b7c411bd304b48044776434f2f1a9145b5a5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 27 Oct 2020 18:36:35 -0400 Subject: [PATCH 03/14] Replace hard-coded length with one derived from the definition. --- Lib/platform.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 0e8ee63eb1958f..99822a7fa88382 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -799,9 +799,11 @@ def __iter__(self): @classmethod def _make(cls, iterable): - result = tuple.__new__(cls, itertools.islice(iterable, 5)) - if len(result) != 6: - raise TypeError(f'Expected 5 arguments, got {len(result)}') + num_fields = len(cls._fields) + result = tuple.__new__(cls, itertools.islice(iterable, num_fields)) + if len(result) != num_fields + 1: + msg = f'Expected {num_fields} arguments, got {len(result)}' + raise TypeError(msg) return result def __getitem__(self, key): From 1f1b732e9bcda6ac34c15c08514f94e77029f7a2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 08:56:56 -0400 Subject: [PATCH 04/14] Add test capturing missed expectation with copy/deepcopy on namedtuple (bpo-42189). --- Lib/test/test_platform.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 5f64dff8dc7124..0d698878507973 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -1,4 +1,5 @@ import os +import copy import platform import subprocess import sys @@ -189,6 +190,10 @@ def test_uname_replace(self): # processor cannot be replaced self.assertEqual(new.processor, res.processor) + def test_uname_deepcopy(self): + res = platform.uname() + self.assertEqual(copy.deepcopy(res), res) + @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") def test_uname_processor(self): """ From c00bf122801b9a81c722125cb085cfc764d2e10a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 09:03:03 -0400 Subject: [PATCH 05/14] bpo-42189: Exclude processor parameter when constructing uname_result. --- Lib/platform.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/platform.py b/Lib/platform.py index 99822a7fa88382..e35bf2e4c7498c 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -782,11 +782,16 @@ class uname_result( ): """ A uname_result that's largely compatible with a - simple namedtuple except that 'platform' is + simple namedtuple except that 'processor' is resolved late and cached to avoid calling "uname" except when needed. """ + def __new__(cls, *args): + # exclude 'processor' arg + args = args[:5] + args[6:] + return super().__new__(cls, *args) + @functools.cached_property def processor(self): return _unknown_as_blank(_Processor.get()) From 27a524d7afe650d0be2550065e876b8d0e940e0f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 09:06:47 -0400 Subject: [PATCH 06/14] In _make, rely on __new__ to strip processor. --- Lib/platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/platform.py b/Lib/platform.py index e35bf2e4c7498c..456f01ad917ad9 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -804,8 +804,9 @@ def __iter__(self): @classmethod def _make(cls, iterable): + # override factory to affect length check num_fields = len(cls._fields) - result = tuple.__new__(cls, itertools.islice(iterable, num_fields)) + result = cls.__new__(cls, *iterable) if len(result) != num_fields + 1: msg = f'Expected {num_fields} arguments, got {len(result)}' raise TypeError(msg) From c41b6d70adf9c803e9bdb5aa06f2b3cc0c2e815f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 09:23:00 -0400 Subject: [PATCH 07/14] Add blurb. --- .../NEWS.d/next/Library/2020-10-29-09-22-56.bpo-42163.O4VcCY.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2020-10-29-09-22-56.bpo-42163.O4VcCY.rst diff --git a/Misc/NEWS.d/next/Library/2020-10-29-09-22-56.bpo-42163.O4VcCY.rst b/Misc/NEWS.d/next/Library/2020-10-29-09-22-56.bpo-42163.O4VcCY.rst new file mode 100644 index 00000000000000..0c357eb4ac1daf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-10-29-09-22-56.bpo-42163.O4VcCY.rst @@ -0,0 +1 @@ +Restore compatibility for ``uname_result`` around deepcopy and _replace. From 55d310e6fa566f2baf09ede380d3d758a5305d82 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 21:15:16 -0400 Subject: [PATCH 08/14] iter is not necessary here. --- Lib/platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/platform.py b/Lib/platform.py index 456f01ad917ad9..f019164b4176f1 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -813,7 +813,7 @@ def _make(cls, iterable): return result def __getitem__(self, key): - return tuple(iter(self))[key] + return tuple(self)[key] def __len__(self): return len(tuple(iter(self))) From e0394cd2b2a43aa361b1d7d78167f02c3592e9ad Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 21:17:21 -0400 Subject: [PATCH 09/14] Rely on num_fields in __new__ --- Lib/platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/platform.py b/Lib/platform.py index f019164b4176f1..07f3e27174b18a 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -789,7 +789,8 @@ class uname_result( def __new__(cls, *args): # exclude 'processor' arg - args = args[:5] + args[6:] + num_fields = len(cls._fields) + args = args[:num_fields] + args[num_fields + 1:] return super().__new__(cls, *args) @functools.cached_property From 8c9d514a8ea96c4c1f4fdd103a737d70e8603041 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 21:20:21 -0400 Subject: [PATCH 10/14] Add test for slices on uname --- Lib/test/test_platform.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 0d698878507973..891173b3ae27b2 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -194,6 +194,12 @@ def test_uname_deepcopy(self): res = platform.uname() self.assertEqual(copy.deepcopy(res), res) + def test_uname_slices(self): + res = platform.uname() + expected = tuple(res) + self.assertEqual(res[:], expected) + self.assertEqual(res[:5], expected[:5]) + @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") def test_uname_processor(self): """ From 0b14530233070fd8f121099ff52af0f62e8a2749 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 29 Oct 2020 21:28:34 -0400 Subject: [PATCH 11/14] Add test for copy and pickle. Co-authored-by: Serhiy Storchaka --- Lib/test/test_platform.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 891173b3ae27b2..b8905f7680a49e 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -190,9 +190,17 @@ def test_uname_replace(self): # processor cannot be replaced self.assertEqual(new.processor, res.processor) - def test_uname_deepcopy(self): - res = platform.uname() - self.assertEqual(copy.deepcopy(res), res) + def test_uname_copy(self): + uname = platform.uname() + self.assertEqual(copy.copy(uname), uname) + self.assertEqual(copy.deepcopy(uname), uname) + + def test_uname_pickle(self): + uname = platform.uname() + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(uname, proto) + self.assertEqual(pickle.loads(pickled), uname) def test_uname_slices(self): res = platform.uname() From 53ddff170e8f4a1de04d28eab185b8b526e85f88 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 15 Dec 2020 20:58:29 -0500 Subject: [PATCH 12/14] import pickle --- Lib/test/test_platform.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index b8905f7680a49e..267836533c473b 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -1,5 +1,6 @@ import os import copy +import pickle import platform import subprocess import sys From 632bd7e5a61bd2e5188435b41d7dc393e4013290 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 16 Dec 2020 20:27:32 -0500 Subject: [PATCH 13/14] Fix equality test after pickling. --- Lib/platform.py | 15 +++++++++------ Lib/test/test_platform.py | 7 ++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 07f3e27174b18a..92e2adf3079a69 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -787,12 +787,6 @@ class uname_result( except when needed. """ - def __new__(cls, *args): - # exclude 'processor' arg - num_fields = len(cls._fields) - args = args[:num_fields] + args[num_fields + 1:] - return super().__new__(cls, *args) - @functools.cached_property def processor(self): return _unknown_as_blank(_Processor.get()) @@ -819,6 +813,15 @@ def __getitem__(self, key): def __len__(self): return len(tuple(iter(self))) + def __new__(cls, *args): + # exclude 'processor' arg + num_fields = len(cls._fields) + args = args[:num_fields] + args[num_fields + 1:] + return super().__new__(cls, *args) + + def __reduce__(self): + return uname_result, tuple(self) + _uname_cache = None diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 267836533c473b..a7ca891f533d6a 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -197,11 +197,12 @@ def test_uname_copy(self): self.assertEqual(copy.deepcopy(uname), uname) def test_uname_pickle(self): - uname = platform.uname() + orig = platform.uname() for proto in range(pickle.HIGHEST_PROTOCOL + 1): with self.subTest(protocol=proto): - pickled = pickle.dumps(uname, proto) - self.assertEqual(pickle.loads(pickled), uname) + pickled = pickle.dumps(orig, proto) + restored = pickle.loads(pickled) + self.assertEqual(restored, orig) def test_uname_slices(self): res = platform.uname() From 2a08a8ba79af9bf599db2f2af37785b9d3eefed0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 16 Dec 2020 20:37:52 -0500 Subject: [PATCH 14/14] Simply rely on __reduce__ for pickling. --- Lib/platform.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 92e2adf3079a69..6258827d0e41f8 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -813,14 +813,8 @@ def __getitem__(self, key): def __len__(self): return len(tuple(iter(self))) - def __new__(cls, *args): - # exclude 'processor' arg - num_fields = len(cls._fields) - args = args[:num_fields] + args[num_fields + 1:] - return super().__new__(cls, *args) - def __reduce__(self): - return uname_result, tuple(self) + return uname_result, tuple(self)[:len(self._fields)] _uname_cache = None