|
4 | 4 | from functools import lru_cache, wraps
|
5 | 5 | import inspect
|
6 | 6 | import itertools
|
| 7 | +import gc |
7 | 8 | import pickle
|
8 | 9 | import re
|
9 | 10 | import sys
|
@@ -2643,6 +2644,104 @@ def x(self): ...
|
2643 | 2644 | with self.assertRaises(TypeError):
|
2644 | 2645 | issubclass(PG, PG[int])
|
2645 | 2646 |
|
| 2647 | + only_classes_allowed = r"issubclass\(\) arg 1 must be a class" |
| 2648 | + |
| 2649 | + with self.assertRaisesRegex(TypeError, only_classes_allowed): |
| 2650 | + issubclass(1, P) |
| 2651 | + with self.assertRaisesRegex(TypeError, only_classes_allowed): |
| 2652 | + issubclass(1, PG) |
| 2653 | + with self.assertRaisesRegex(TypeError, only_classes_allowed): |
| 2654 | + issubclass(1, BadP) |
| 2655 | + with self.assertRaisesRegex(TypeError, only_classes_allowed): |
| 2656 | + issubclass(1, BadPG) |
| 2657 | + |
| 2658 | + def test_implicit_issubclass_between_two_protocols(self): |
| 2659 | + @runtime_checkable |
| 2660 | + class CallableMembersProto(Protocol): |
| 2661 | + def meth(self): ... |
| 2662 | + |
| 2663 | + # All the below protocols should be considered "subclasses" |
| 2664 | + # of CallableMembersProto at runtime, |
| 2665 | + # even though none of them explicitly subclass CallableMembersProto |
| 2666 | + |
| 2667 | + class IdenticalProto(Protocol): |
| 2668 | + def meth(self): ... |
| 2669 | + |
| 2670 | + class SupersetProto(Protocol): |
| 2671 | + def meth(self): ... |
| 2672 | + def meth2(self): ... |
| 2673 | + |
| 2674 | + class NonCallableMembersProto(Protocol): |
| 2675 | + meth: Callable[[], None] |
| 2676 | + |
| 2677 | + class NonCallableMembersSupersetProto(Protocol): |
| 2678 | + meth: Callable[[], None] |
| 2679 | + meth2: Callable[[str, int], bool] |
| 2680 | + |
| 2681 | + class MixedMembersProto1(Protocol): |
| 2682 | + meth: Callable[[], None] |
| 2683 | + def meth2(self): ... |
| 2684 | + |
| 2685 | + class MixedMembersProto2(Protocol): |
| 2686 | + def meth(self): ... |
| 2687 | + meth2: Callable[[str, int], bool] |
| 2688 | + |
| 2689 | + for proto in ( |
| 2690 | + IdenticalProto, SupersetProto, NonCallableMembersProto, |
| 2691 | + NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2 |
| 2692 | + ): |
| 2693 | + with self.subTest(proto=proto.__name__): |
| 2694 | + self.assertIsSubclass(proto, CallableMembersProto) |
| 2695 | + |
| 2696 | + # These two shouldn't be considered subclasses of CallableMembersProto, however, |
| 2697 | + # since they don't have the `meth` protocol member |
| 2698 | + |
| 2699 | + class EmptyProtocol(Protocol): ... |
| 2700 | + class UnrelatedProtocol(Protocol): |
| 2701 | + def wut(self): ... |
| 2702 | + |
| 2703 | + self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto) |
| 2704 | + self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto) |
| 2705 | + |
| 2706 | + # These aren't protocols at all (despite having annotations), |
| 2707 | + # so they should only be considered subclasses of CallableMembersProto |
| 2708 | + # if they *actually have an attribute* matching the `meth` member |
| 2709 | + # (just having an annotation is insufficient) |
| 2710 | + |
| 2711 | + class AnnotatedButNotAProtocol: |
| 2712 | + meth: Callable[[], None] |
| 2713 | + |
| 2714 | + class NotAProtocolButAnImplicitSubclass: |
| 2715 | + def meth(self): pass |
| 2716 | + |
| 2717 | + class NotAProtocolButAnImplicitSubclass2: |
| 2718 | + meth: Callable[[], None] |
| 2719 | + def meth(self): pass |
| 2720 | + |
| 2721 | + class NotAProtocolButAnImplicitSubclass3: |
| 2722 | + meth: Callable[[], None] |
| 2723 | + meth2: Callable[[int, str], bool] |
| 2724 | + def meth(self): pass |
| 2725 | + def meth(self, x, y): return True |
| 2726 | + |
| 2727 | + self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto) |
| 2728 | + self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto) |
| 2729 | + self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto) |
| 2730 | + self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto) |
| 2731 | + |
| 2732 | + def test_isinstance_checks_not_at_whim_of_gc(self): |
| 2733 | + self.addCleanup(gc.enable) |
| 2734 | + gc.disable() |
| 2735 | + |
| 2736 | + with self.assertRaisesRegex( |
| 2737 | + TypeError, |
| 2738 | + "Protocols can only inherit from other protocols" |
| 2739 | + ): |
| 2740 | + class Foo(collections.abc.Mapping, Protocol): |
| 2741 | + pass |
| 2742 | + |
| 2743 | + self.assertNotIsInstance([], collections.abc.Mapping) |
| 2744 | + |
2646 | 2745 | def test_protocols_issubclass_non_callable(self):
|
2647 | 2746 | class C:
|
2648 | 2747 | x = 1
|
|
0 commit comments