Skip to content

Commit

Permalink
Backport recent improvements to the error message when trying to call…
Browse files Browse the repository at this point in the history
… `issubclass()` against a protocol with non-method members (#304)
  • Loading branch information
AlexWaygood authored Nov 29, 2023
1 parent e4d9d8b commit 18ae2b3
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
is called on all objects that define `__set_name__` and exist in the values
of the `NamedTuple` class's class dictionary. Patch by Alex Waygood,
backporting https://github.com/python/cpython/pull/111876.
- Improve the error message when trying to call `issubclass()` against a
`Protocol` that has non-method members. Patch by Alex Waygood (backporting
https://github.com/python/cpython/pull/112344, by Randolph Scholz).

# Release 4.8.0 (September 17, 2023)

Expand Down
17 changes: 17 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3446,6 +3446,23 @@ def method(self) -> None: ...
self.assertIsInstance(Foo(), ProtocolWithMixedMembers)
self.assertNotIsInstance(42, ProtocolWithMixedMembers)

@skip_if_early_py313_alpha
def test_protocol_issubclass_error_message(self):
class Vec2D(Protocol):
x: float
y: float

def square_norm(self) -> float:
return self.x ** 2 + self.y ** 2

self.assertEqual(Vec2D.__protocol_attrs__, {'x', 'y', 'square_norm'})
expected_error_message = (
"Protocols with non-method members don't support issubclass()."
" Non-method members: 'x', 'y'."
)
with self.assertRaisesRegex(TypeError, re.escape(expected_error_message)):
issubclass(int, Vec2D)


class Point2DGeneric(Generic[T], TypedDict):
a: T
Expand Down
7 changes: 6 additions & 1 deletion src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,13 @@ def __subclasscheck__(cls, other):
not cls.__callable_proto_members_only__
and cls.__dict__.get("__subclasshook__") is _proto_hook
):
non_method_attrs = sorted(
attr for attr in cls.__protocol_attrs__
if not callable(getattr(cls, attr, None))
)
raise TypeError(
"Protocols with non-method members don't support issubclass()"
"Protocols with non-method members don't support issubclass()."
f" Non-method members: {str(non_method_attrs)[1:-1]}."
)
if not getattr(cls, '_is_runtime_protocol', False):
raise TypeError(
Expand Down

0 comments on commit 18ae2b3

Please sign in to comment.