Skip to content
Merged
Show file tree
Hide file tree
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
10 changes: 9 additions & 1 deletion src/typeguard/_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,9 @@ def check_class(
return

expected_class = args[0]
if isinstance(expected_class, TypeVar):
if getattr(expected_class, "_is_protocol", False):
check_protocol(value, expected_class, (), memo)
elif isinstance(expected_class, TypeVar):
check_typevar(value, expected_class, (), memo, subclass_check=True)
elif get_origin(expected_class) is Union:
errors: Dict[str, TypeCheckError] = {}
Expand Down Expand Up @@ -501,6 +503,12 @@ def check_protocol(
raise TypeCheckError(
f"is not compatible with the {origin_type.__qualname__} protocol"
)
else:
warnings.warn(
f"Typeguard cannot check the {origin_type.__qualname__} protocol because "
f"it is a non-runtime protocol. If you would like to type check this "
f"protocol, please use @typing.runtime_checkable"
)


def check_byteslike(
Expand Down
35 changes: 30 additions & 5 deletions tests/test_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,24 +681,46 @@ def test_text_real_file(self, tmp_path: Path):


class TestProtocol:
@pytest.mark.parametrize("protocol_cls", [RuntimeProtocol, StaticProtocol])
def test_protocol(self, protocol_cls):
def test_protocol(self):
class Foo:
member = 1

def meth(self) -> None:
pass

check_type(Foo(), protocol_cls)
check_type(Foo(), RuntimeProtocol)
check_type(Foo, Type[RuntimeProtocol])

def test_non_method_members(self):
def test_protocol_warns_on_static(self):
class Foo:
member = 1

def meth(self) -> None:
pass

check_type(Foo(), RuntimeProtocol)
with pytest.warns(
UserWarning, match=r"Typeguard cannot check the StaticProtocol protocol.*"
):
check_type(Foo(), StaticProtocol)

with pytest.warns(
UserWarning, match=r"Typeguard cannot check the StaticProtocol protocol.*"
):
check_type(Foo, Type[StaticProtocol])

def test_fail_non_method_members(self):
class Foo:
val = 1

def meth(self) -> None:
pass

pytest.raises(TypeCheckError, check_type, Foo(), RuntimeProtocol).match(
"value is not compatible with the RuntimeProtocol protocol"
)
pytest.raises(TypeCheckError, check_type, Foo, Type[RuntimeProtocol]).match(
"value is not compatible with the RuntimeProtocol protocol"
)

def test_fail(self):
class Foo:
Expand All @@ -708,6 +730,9 @@ def meth2(self) -> None:
pytest.raises(TypeCheckError, check_type, Foo(), RuntimeProtocol).match(
"value is not compatible with the RuntimeProtocol protocol"
)
pytest.raises(TypeCheckError, check_type, Foo, Type[RuntimeProtocol]).match(
"value is not compatible with the RuntimeProtocol protocol"
)


class TestMock:
Expand Down