From 5dc6d5582abf8f6a32e0a8664484471ea6484261 Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Fri, 23 Feb 2024 20:13:41 -0500 Subject: [PATCH 1/7] Update _collections_abc.py to 3.12.2 --- Lib/_collections_abc.py | 74 +++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index e96e4c3535..601107d2d8 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -6,6 +6,32 @@ Unit tests are in test_collections. """ +############ Maintenance notes ######################################### +# +# ABCs are different from other standard library modules in that they +# specify compliance tests. In general, once an ABC has been published, +# new methods (either abstract or concrete) cannot be added. +# +# Though classes that inherit from an ABC would automatically receive a +# new mixin method, registered classes would become non-compliant and +# violate the contract promised by ``isinstance(someobj, SomeABC)``. +# +# Though irritating, the correct procedure for adding new abstract or +# mixin methods is to create a new ABC as a subclass of the previous +# ABC. For example, union(), intersection(), and difference() cannot +# be added to Set but could go into a new ABC that extends Set. +# +# Because they are so hard to change, new ABCs should have their APIs +# carefully thought through prior to publication. +# +# Since ABCMeta only checks for the presence of methods, it is possible +# to alter the signature of a method by adding optional arguments +# or changing parameters names. This is still a bit dubious but at +# least it won't cause isinstance() to return an incorrect result. +# +# +####################################################################### + from abc import ABCMeta, abstractmethod import sys @@ -23,7 +49,7 @@ def _f(): pass "Mapping", "MutableMapping", "MappingView", "KeysView", "ItemsView", "ValuesView", "Sequence", "MutableSequence", - "ByteString", + "ByteString", "Buffer", ] # This module has been renamed from collections.abc to _collections_abc to @@ -413,6 +439,21 @@ def __subclasshook__(cls, C): return NotImplemented +class Buffer(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __buffer__(self, flags: int, /) -> memoryview: + raise NotImplementedError + + @classmethod + def __subclasshook__(cls, C): + if cls is Buffer: + return _check_methods(C, "__buffer__") + return NotImplemented + + class _CallableGenericAlias(GenericAlias): """ Represent `Callable[argtypes, resulttype]`. @@ -455,15 +496,8 @@ def __getitem__(self, item): # rather than the default types.GenericAlias object. Most of the # code is copied from typing's _GenericAlias and the builtin # types.GenericAlias. - if not isinstance(item, tuple): item = (item,) - # A special case in PEP 612 where if X = Callable[P, int], - # then X[int, str] == X[[int, str]]. - if (len(self.__parameters__) == 1 - and _is_param_expr(self.__parameters__[0]) - and item and not _is_param_expr(item[0])): - item = (item,) new_args = super().__getitem__(item).__args__ @@ -491,9 +525,8 @@ def _type_repr(obj): Copied from :mod:`typing` since collections.abc shouldn't depend on that module. + (Keep this roughly in sync with the typing version.) """ - if isinstance(obj, GenericAlias): - return repr(obj) if isinstance(obj, type): if obj.__module__ == 'builtins': return obj.__qualname__ @@ -1038,8 +1071,27 @@ def count(self, value): Sequence.register(range) Sequence.register(memoryview) +class _DeprecateByteStringMeta(ABCMeta): + def __new__(cls, name, bases, namespace, **kwargs): + if name != "ByteString": + import warnings + + warnings._deprecated( + "collections.abc.ByteString", + remove=(3, 14), + ) + return super().__new__(cls, name, bases, namespace, **kwargs) + + def __instancecheck__(cls, instance): + import warnings + + warnings._deprecated( + "collections.abc.ByteString", + remove=(3, 14), + ) + return super().__instancecheck__(instance) -class ByteString(Sequence): +class ByteString(Sequence, metaclass=_DeprecateByteStringMeta): """This unifies bytes and bytearray. XXX Should add all their methods. From bcb35919a4772dc93f87ac9306524c3afb43dbc1 Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Fri, 23 Feb 2024 20:14:41 -0500 Subject: [PATCH 2/7] Update collections/__init__.py to 3.12.2 --- Lib/collections/__init__.py | 45 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 59a2d520fe..f7348ee918 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -45,6 +45,11 @@ else: _collections_abc.MutableSequence.register(deque) +try: + from _collections import _deque_iterator +except ImportError: + pass + try: from _collections import defaultdict except ImportError: @@ -94,17 +99,19 @@ class OrderedDict(dict): # Individual links are kept alive by the hard reference in self.__map. # Those hard references disappear when a key is deleted from an OrderedDict. + def __new__(cls, /, *args, **kwds): + "Create the ordered dict object and set up the underlying structures." + self = dict.__new__(cls) + self.__hardroot = _Link() + self.__root = root = _proxy(self.__hardroot) + root.prev = root.next = root + self.__map = {} + return self + def __init__(self, other=(), /, **kwds): '''Initialize an ordered dictionary. The signature is the same as regular dictionaries. Keyword argument order is preserved. ''' - try: - self.__root - except AttributeError: - self.__hardroot = _Link() - self.__root = root = _proxy(self.__hardroot) - root.prev = root.next = root - self.__map = {} self.__update(other, **kwds) def __setitem__(self, key, value, @@ -271,7 +278,7 @@ def __repr__(self): 'od.__repr__() <==> repr(od)' if not self: return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self.items())) + return '%s(%r)' % (self.__class__.__name__, dict(self.items())) def __reduce__(self): 'Return state information for pickling' @@ -511,9 +518,12 @@ def __getnewargs__(self): # specified a particular module. if module is None: try: - module = _sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass + module = _sys._getframemodulename(1) or '__main__' + except AttributeError: + try: + module = _sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass if module is not None: result.__module__ = module @@ -1015,8 +1025,8 @@ def __len__(self): def __iter__(self): d = {} - for mapping in reversed(self.maps): - d.update(dict.fromkeys(mapping)) # reuses stored hash values if possible + for mapping in map(dict.fromkeys, reversed(self.maps)): + d |= mapping # reuses stored hash values if possible return iter(d) def __contains__(self, key): @@ -1136,10 +1146,17 @@ def __delitem__(self, key): def __iter__(self): return iter(self.data) - # Modify __contains__ to work correctly when __missing__ is present + # Modify __contains__ and get() to work like dict + # does when __missing__ is present. def __contains__(self, key): return key in self.data + def get(self, key, default=None): + if key in self: + return self[key] + return default + + # Now, add the methods in dicts but not in MutableMapping def __repr__(self): return repr(self.data) From 0bd8c2504c51d64e8351a55153266e0059dd8137 Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Fri, 23 Feb 2024 20:16:16 -0500 Subject: [PATCH 3/7] Update test_collections.py to 3.12.2 --- Lib/test/test_collections.py | 41 ++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 50e7282d17..d70490f153 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -25,7 +25,7 @@ from collections.abc import Set, MutableSet from collections.abc import Mapping, MutableMapping, KeysView, ItemsView, ValuesView from collections.abc import Sequence, MutableSequence -from collections.abc import ByteString +from collections.abc import ByteString, Buffer class TestUserObjects(unittest.TestCase): @@ -71,6 +71,14 @@ def test_dict_copy(self): obj[123] = "abc" self._copy_test(obj) + def test_dict_missing(self): + class A(UserDict): + def __missing__(self, key): + return 456 + self.assertEqual(A()[123], 456) + # get() ignores __missing__ on dict + self.assertIs(A().get(123), None) + ################################################################################ ### ChainMap (helper class for configparser and the string module) @@ -539,7 +547,7 @@ def test_odd_sizes(self): self.assertEqual(Dot(1)._replace(d=999), (999,)) self.assertEqual(Dot(1)._fields, ('d',)) - n = 5000 + n = support.EXCEEDS_RECURSION_LIMIT names = list(set(''.join([choice(string.ascii_letters) for j in range(10)]) for i in range(n))) n = len(names) @@ -1629,7 +1637,7 @@ def test_Set_from_iterable(self): class SetUsingInstanceFromIterable(MutableSet): def __init__(self, values, created_by): if not created_by: - raise ValueError(f'created_by must be specified') + raise ValueError('created_by must be specified') self.created_by = created_by self._values = set(values) @@ -1949,13 +1957,34 @@ def assert_index_same(seq1, seq2, index_args): def test_ByteString(self): for sample in [bytes, bytearray]: - self.assertIsInstance(sample(), ByteString) + with self.assertWarns(DeprecationWarning): + self.assertIsInstance(sample(), ByteString) self.assertTrue(issubclass(sample, ByteString)) for sample in [str, list, tuple]: - self.assertNotIsInstance(sample(), ByteString) + with self.assertWarns(DeprecationWarning): + self.assertNotIsInstance(sample(), ByteString) self.assertFalse(issubclass(sample, ByteString)) - self.assertNotIsInstance(memoryview(b""), ByteString) + with self.assertWarns(DeprecationWarning): + self.assertNotIsInstance(memoryview(b""), ByteString) self.assertFalse(issubclass(memoryview, ByteString)) + with self.assertWarns(DeprecationWarning): + self.validate_abstract_methods(ByteString, '__getitem__', '__len__') + + with self.assertWarns(DeprecationWarning): + class X(ByteString): pass + + with self.assertWarns(DeprecationWarning): + # No metaclass conflict + class Z(ByteString, Awaitable): pass + + def test_Buffer(self): + for sample in [bytes, bytearray, memoryview]: + self.assertIsInstance(sample(b"x"), Buffer) + self.assertTrue(issubclass(sample, Buffer)) + for sample in [str, list, tuple]: + self.assertNotIsInstance(sample(), Buffer) + self.assertFalse(issubclass(sample, Buffer)) + self.validate_abstract_methods(Buffer, '__buffer__') # TODO: RUSTPYTHON @unittest.expectedFailure From 9481df23e1219f7df851770fcddb1a1f08f85f38 Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Sat, 24 Feb 2024 11:02:41 -0500 Subject: [PATCH 4/7] Disable test_Buffer This test will not work until the `__buffer__` and `__release_buffer__` methods are implemented on the appropriate builtin types, which is outside the current scope. --- Lib/test/test_collections.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index d70490f153..ecd574ab83 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1977,6 +1977,10 @@ class X(ByteString): pass # No metaclass conflict class Z(ByteString, Awaitable): pass + # TODO: RUSTPYTHON + # Need to implement __buffer__ and __release_buffer__ + # https://docs.python.org/3.13/reference/datamodel.html#emulating-buffer-types + @unittest.expectedFailure def test_Buffer(self): for sample in [bytes, bytearray, memoryview]: self.assertIsInstance(sample(b"x"), Buffer) From c9ec4507ad2dd1e309753a38f31cfe75ae8d89d9 Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Sat, 24 Feb 2024 11:58:23 -0500 Subject: [PATCH 5/7] Disable broken test_repr in test_typing.py This should be resolved when `typing.py` and `test_typing.py` are updated to 3.12. --- Lib/test/test_typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index abcc03ce2d..3140ff9028 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -513,6 +513,8 @@ def f(): with self.assertRaises(TypeError): self.assertNotIsInstance(None, Callable[[], Any]) + # TODO: RUSTPYTHON update typing to 3.12 + @unittest.expectedFailure def test_repr(self): Callable = self.Callable fullname = f'{Callable.__module__}.Callable' From 6cd9e5442765340ec64a1280dd75756d0a309498 Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Sat, 24 Feb 2024 12:00:07 -0500 Subject: [PATCH 6/7] Update test_ordered_dict.py to 3.12.2 --- Lib/test/test_ordered_dict.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py index cfb87d7829..942748bd91 100644 --- a/Lib/test/test_ordered_dict.py +++ b/Lib/test/test_ordered_dict.py @@ -122,6 +122,17 @@ def items(self): self.OrderedDict(Spam()) self.assertEqual(calls, ['keys']) + def test_overridden_init(self): + # Sync-up pure Python OD class with C class where + # a consistent internal state is created in __new__ + # rather than __init__. + OrderedDict = self.OrderedDict + class ODNI(OrderedDict): + def __init__(*args, **kwargs): + pass + od = ODNI() + od['a'] = 1 # This used to fail because __init__ was bypassed + def test_fromkeys(self): OrderedDict = self.OrderedDict od = OrderedDict.fromkeys('abc') @@ -370,7 +381,7 @@ def test_repr(self): OrderedDict = self.OrderedDict od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]) self.assertEqual(repr(od), - "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])") + "OrderedDict({'c': 1, 'b': 2, 'a': 3, 'd': 4, 'e': 5, 'f': 6})") self.assertEqual(eval(repr(od)), od) self.assertEqual(repr(OrderedDict()), "OrderedDict()") @@ -380,7 +391,7 @@ def test_repr_recursive(self): od = OrderedDict.fromkeys('abc') od['x'] = od self.assertEqual(repr(od), - "OrderedDict([('a', None), ('b', None), ('c', None), ('x', ...)])") + "OrderedDict({'a': None, 'b': None, 'c': None, 'x': ...})") def test_repr_recursive_values(self): OrderedDict = self.OrderedDict From 407f251866f437987bb86419bbd672b9327db4ea Mon Sep 17 00:00:00 2001 From: Daniel Chiquito Date: Sat, 24 Feb 2024 13:33:17 -0500 Subject: [PATCH 7/7] Un-skip passing typing test I missed that the typing test I disabled was on a base test class. Moving the expected failure to the subclass allows the passing test to pass. --- Lib/test/test_typing.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3140ff9028..95fd3748e6 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -513,8 +513,6 @@ def f(): with self.assertRaises(TypeError): self.assertNotIsInstance(None, Callable[[], Any]) - # TODO: RUSTPYTHON update typing to 3.12 - @unittest.expectedFailure def test_repr(self): Callable = self.Callable fullname = f'{Callable.__module__}.Callable' @@ -707,6 +705,11 @@ def test_paramspec(self): # TODO: RUSTPYTHON, remove when this passes def test_concatenate(self): # TODO: RUSTPYTHON, remove when this passes super().test_concatenate() # TODO: RUSTPYTHON, remove when this passes + # TODO: RUSTPYTHON might be fixed by updating typing to 3.12 + @unittest.expectedFailure + def test_repr(self): # TODO: RUSTPYTHON, remove when this passes + super().test_repr() # TODO: RUSTPYTHON, remove when this passes + class LiteralTests(BaseTestCase): def test_basics(self):