Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.11] gh-105834: Backport new tests for typing.Protocol (#105835) #105860

Merged
merged 2 commits into from
Jun 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from functools import lru_cache, wraps
import inspect
import itertools
import gc
import pickle
import re
import sys
Expand Down Expand Up @@ -2643,6 +2644,104 @@ def x(self): ...
with self.assertRaises(TypeError):
issubclass(PG, PG[int])

only_classes_allowed = r"issubclass\(\) arg 1 must be a class"

with self.assertRaisesRegex(TypeError, only_classes_allowed):
issubclass(1, P)
with self.assertRaisesRegex(TypeError, only_classes_allowed):
issubclass(1, PG)
with self.assertRaisesRegex(TypeError, only_classes_allowed):
issubclass(1, BadP)
with self.assertRaisesRegex(TypeError, only_classes_allowed):
issubclass(1, BadPG)

def test_implicit_issubclass_between_two_protocols(self):
@runtime_checkable
class CallableMembersProto(Protocol):
def meth(self): ...

# All the below protocols should be considered "subclasses"
# of CallableMembersProto at runtime,
# even though none of them explicitly subclass CallableMembersProto

class IdenticalProto(Protocol):
def meth(self): ...

class SupersetProto(Protocol):
def meth(self): ...
def meth2(self): ...

class NonCallableMembersProto(Protocol):
meth: Callable[[], None]

class NonCallableMembersSupersetProto(Protocol):
meth: Callable[[], None]
meth2: Callable[[str, int], bool]

class MixedMembersProto1(Protocol):
meth: Callable[[], None]
def meth2(self): ...

class MixedMembersProto2(Protocol):
def meth(self): ...
meth2: Callable[[str, int], bool]

for proto in (
IdenticalProto, SupersetProto, NonCallableMembersProto,
NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2
):
with self.subTest(proto=proto.__name__):
self.assertIsSubclass(proto, CallableMembersProto)

# These two shouldn't be considered subclasses of CallableMembersProto, however,
# since they don't have the `meth` protocol member

class EmptyProtocol(Protocol): ...
class UnrelatedProtocol(Protocol):
def wut(self): ...

self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto)
self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto)

# These aren't protocols at all (despite having annotations),
# so they should only be considered subclasses of CallableMembersProto
# if they *actually have an attribute* matching the `meth` member
# (just having an annotation is insufficient)

class AnnotatedButNotAProtocol:
meth: Callable[[], None]

class NotAProtocolButAnImplicitSubclass:
def meth(self): pass

class NotAProtocolButAnImplicitSubclass2:
meth: Callable[[], None]
def meth(self): pass

class NotAProtocolButAnImplicitSubclass3:
meth: Callable[[], None]
meth2: Callable[[int, str], bool]
def meth(self): pass
def meth(self, x, y): return True

self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto)
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto)
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto)
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto)

def test_isinstance_checks_not_at_whim_of_gc(self):
self.addCleanup(gc.enable)
gc.disable()

with self.assertRaisesRegex(
TypeError,
"Protocols can only inherit from other protocols"
):
class Foo(collections.abc.Mapping, Protocol):
pass

self.assertNotIsInstance([], collections.abc.Mapping)

def test_protocols_issubclass_non_callable(self):
class C:
x = 1
Expand Down