Skip to content

Commit 1b227f8

Browse files
committed
Update abc module to CPython 3.10.0
1 parent c060f99 commit 1b227f8

File tree

2 files changed

+254
-6
lines changed

2 files changed

+254
-6
lines changed

Lib/abc.py

+66-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ def my_abstract_method(self, ...):
2828
class abstractclassmethod(classmethod):
2929
"""A decorator indicating abstract classmethods.
3030
31-
Deprecated, use 'classmethod' with 'abstractmethod' instead.
31+
Deprecated, use 'classmethod' with 'abstractmethod' instead:
32+
33+
class C(ABC):
34+
@classmethod
35+
@abstractmethod
36+
def my_abstract_classmethod(cls, ...):
37+
...
38+
3239
"""
3340

3441
__isabstractmethod__ = True
@@ -41,7 +48,14 @@ def __init__(self, callable):
4148
class abstractstaticmethod(staticmethod):
4249
"""A decorator indicating abstract staticmethods.
4350
44-
Deprecated, use 'staticmethod' with 'abstractmethod' instead.
51+
Deprecated, use 'staticmethod' with 'abstractmethod' instead:
52+
53+
class C(ABC):
54+
@staticmethod
55+
@abstractmethod
56+
def my_abstract_staticmethod(...):
57+
...
58+
4559
"""
4660

4761
__isabstractmethod__ = True
@@ -54,7 +68,14 @@ def __init__(self, callable):
5468
class abstractproperty(property):
5569
"""A decorator indicating abstract properties.
5670
57-
Deprecated, use 'property' with 'abstractmethod' instead.
71+
Deprecated, use 'property' with 'abstractmethod' instead:
72+
73+
class C(ABC):
74+
@property
75+
@abstractmethod
76+
def my_abstract_property(self):
77+
...
78+
5879
"""
5980

6081
__isabstractmethod__ = True
@@ -64,6 +85,10 @@ class abstractproperty(property):
6485
from _abc import (get_cache_token, _abc_init, _abc_register,
6586
_abc_instancecheck, _abc_subclasscheck, _get_dump,
6687
_reset_registry, _reset_caches)
88+
# TODO: RUSTPYTHON missing _abc module implementation.
89+
except ModuleNotFoundError:
90+
from _py_abc import ABCMeta, get_cache_token
91+
ABCMeta.__module__ = 'abc'
6792
except ImportError:
6893
from _py_abc import ABCMeta, get_cache_token
6994
ABCMeta.__module__ = 'abc'
@@ -122,6 +147,44 @@ def _abc_caches_clear(cls):
122147
_reset_caches(cls)
123148

124149

150+
def update_abstractmethods(cls):
151+
"""Recalculate the set of abstract methods of an abstract class.
152+
153+
If a class has had one of its abstract methods implemented after the
154+
class was created, the method will not be considered implemented until
155+
this function is called. Alternatively, if a new abstract method has been
156+
added to the class, it will only be considered an abstract method of the
157+
class after this function is called.
158+
159+
This function should be called before any use is made of the class,
160+
usually in class decorators that add methods to the subject class.
161+
162+
Returns cls, to allow usage as a class decorator.
163+
164+
If cls is not an instance of ABCMeta, does nothing.
165+
"""
166+
if not hasattr(cls, '__abstractmethods__'):
167+
# We check for __abstractmethods__ here because cls might by a C
168+
# implementation or a python implementation (especially during
169+
# testing), and we want to handle both cases.
170+
return cls
171+
172+
abstracts = set()
173+
# Check the existing abstract methods of the parents, keep only the ones
174+
# that are not implemented.
175+
for scls in cls.__bases__:
176+
for name in getattr(scls, '__abstractmethods__', ()):
177+
value = getattr(cls, name, None)
178+
if getattr(value, "__isabstractmethod__", False):
179+
abstracts.add(name)
180+
# Also add any other newly added abstract methods.
181+
for name, value in cls.__dict__.items():
182+
if getattr(value, "__isabstractmethod__", False):
183+
abstracts.add(name)
184+
cls.__abstractmethods__ = frozenset(abstracts)
185+
return cls
186+
187+
125188
class ABC(metaclass=ABCMeta):
126189
"""Helper class that provides a standard way to create an ABC using
127190
inheritance.

Lib/test/test_abc.py

+188-3
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,25 @@ def foo(): return 4
157157
self.assertEqual(D.foo(), 4)
158158
self.assertEqual(D().foo(), 4)
159159

160-
# TODO: RUSTPYTHON
161-
@unittest.expectedFailure
160+
def test_object_new_with_one_abstractmethod(self):
161+
class C(metaclass=abc_ABCMeta):
162+
@abc.abstractmethod
163+
def method_one(self):
164+
pass
165+
msg = r"class C with abstract method method_one"
166+
self.assertRaisesRegex(TypeError, msg, C)
167+
168+
def test_object_new_with_many_abstractmethods(self):
169+
class C(metaclass=abc_ABCMeta):
170+
@abc.abstractmethod
171+
def method_one(self):
172+
pass
173+
@abc.abstractmethod
174+
def method_two(self):
175+
pass
176+
msg = r"class C with abstract methods method_one, method_two"
177+
self.assertRaisesRegex(TypeError, msg, C)
178+
162179
def test_abstractmethod_integration(self):
163180
for abstractthing in [abc.abstractmethod, abc.abstractproperty,
164181
abc.abstractclassmethod,
@@ -219,6 +236,7 @@ def bar(self):
219236
bar.__isabstractmethod__ = NotBool()
220237
foo = property(bar)
221238

239+
222240
def test_customdescriptors_with_abstractmethod(self):
223241
class Descriptor:
224242
def __init__(self, fget, fset=None):
@@ -318,7 +336,7 @@ class B:
318336
token_old = abc_get_cache_token()
319337
A.register(B)
320338
token_new = abc_get_cache_token()
321-
self.assertNotEqual(token_old, token_new)
339+
self.assertGreater(token_new, token_old)
322340
self.assertTrue(isinstance(b, A))
323341
self.assertTrue(isinstance(b, (A,)))
324342

@@ -451,6 +469,24 @@ class S(metaclass=abc_ABCMeta):
451469
with self.assertRaisesRegex(Exception, exc_msg):
452470
issubclass(int, S)
453471

472+
def test_subclasshook(self):
473+
class A(metaclass=abc.ABCMeta):
474+
@classmethod
475+
def __subclasshook__(cls, C):
476+
if cls is A:
477+
return 'foo' in C.__dict__
478+
return NotImplemented
479+
self.assertFalse(issubclass(A, A))
480+
self.assertFalse(issubclass(A, (A,)))
481+
class B:
482+
foo = 42
483+
self.assertTrue(issubclass(B, A))
484+
self.assertTrue(issubclass(B, (A,)))
485+
class C:
486+
spam = 42
487+
self.assertFalse(issubclass(C, A))
488+
self.assertFalse(issubclass(C, (A,)))
489+
454490
def test_all_new_methods_are_called(self):
455491
class A(metaclass=abc_ABCMeta):
456492
pass
@@ -480,6 +516,155 @@ class C(with_metaclass(abc_ABCMeta, A, B)):
480516
pass
481517
self.assertEqual(C.__class__, abc_ABCMeta)
482518

519+
def test_update_del(self):
520+
class A(metaclass=abc_ABCMeta):
521+
@abc.abstractmethod
522+
def foo(self):
523+
pass
524+
525+
del A.foo
526+
self.assertEqual(A.__abstractmethods__, {'foo'})
527+
self.assertFalse(hasattr(A, 'foo'))
528+
529+
abc.update_abstractmethods(A)
530+
531+
self.assertEqual(A.__abstractmethods__, set())
532+
A()
533+
534+
535+
def test_update_new_abstractmethods(self):
536+
class A(metaclass=abc_ABCMeta):
537+
@abc.abstractmethod
538+
def bar(self):
539+
pass
540+
541+
@abc.abstractmethod
542+
def updated_foo(self):
543+
pass
544+
545+
A.foo = updated_foo
546+
abc.update_abstractmethods(A)
547+
self.assertEqual(A.__abstractmethods__, {'foo', 'bar'})
548+
msg = "class A with abstract methods bar, foo"
549+
self.assertRaisesRegex(TypeError, msg, A)
550+
551+
def test_update_implementation(self):
552+
class A(metaclass=abc_ABCMeta):
553+
@abc.abstractmethod
554+
def foo(self):
555+
pass
556+
557+
class B(A):
558+
pass
559+
560+
msg = "class B with abstract method foo"
561+
self.assertRaisesRegex(TypeError, msg, B)
562+
self.assertEqual(B.__abstractmethods__, {'foo'})
563+
564+
B.foo = lambda self: None
565+
566+
abc.update_abstractmethods(B)
567+
568+
B()
569+
self.assertEqual(B.__abstractmethods__, set())
570+
571+
def test_update_as_decorator(self):
572+
class A(metaclass=abc_ABCMeta):
573+
@abc.abstractmethod
574+
def foo(self):
575+
pass
576+
577+
def class_decorator(cls):
578+
cls.foo = lambda self: None
579+
return cls
580+
581+
@abc.update_abstractmethods
582+
@class_decorator
583+
class B(A):
584+
pass
585+
586+
B()
587+
self.assertEqual(B.__abstractmethods__, set())
588+
589+
def test_update_non_abc(self):
590+
class A:
591+
pass
592+
593+
@abc.abstractmethod
594+
def updated_foo(self):
595+
pass
596+
597+
A.foo = updated_foo
598+
abc.update_abstractmethods(A)
599+
A()
600+
self.assertFalse(hasattr(A, '__abstractmethods__'))
601+
602+
def test_update_del_implementation(self):
603+
class A(metaclass=abc_ABCMeta):
604+
@abc.abstractmethod
605+
def foo(self):
606+
pass
607+
608+
class B(A):
609+
def foo(self):
610+
pass
611+
612+
B()
613+
614+
del B.foo
615+
616+
abc.update_abstractmethods(B)
617+
618+
msg = "class B with abstract method foo"
619+
self.assertRaisesRegex(TypeError, msg, B)
620+
621+
def test_update_layered_implementation(self):
622+
class A(metaclass=abc_ABCMeta):
623+
@abc.abstractmethod
624+
def foo(self):
625+
pass
626+
627+
class B(A):
628+
pass
629+
630+
class C(B):
631+
def foo(self):
632+
pass
633+
634+
C()
635+
636+
del C.foo
637+
638+
abc.update_abstractmethods(C)
639+
640+
msg = "class C with abstract method foo"
641+
self.assertRaisesRegex(TypeError, msg, C)
642+
643+
def test_update_multi_inheritance(self):
644+
class A(metaclass=abc_ABCMeta):
645+
@abc.abstractmethod
646+
def foo(self):
647+
pass
648+
649+
class B(metaclass=abc_ABCMeta):
650+
def foo(self):
651+
pass
652+
653+
class C(B, A):
654+
@abc.abstractmethod
655+
def foo(self):
656+
pass
657+
658+
self.assertEqual(C.__abstractmethods__, {'foo'})
659+
660+
del C.foo
661+
662+
abc.update_abstractmethods(C)
663+
664+
self.assertEqual(C.__abstractmethods__, set())
665+
666+
C()
667+
483668

484669
class TestABCWithInitSubclass(unittest.TestCase):
485670
def test_works_with_init_subclass(self):

0 commit comments

Comments
 (0)