From 16dd3c46890e3bad5492ef0acab22cbc71f023cb Mon Sep 17 00:00:00 2001 From: williamlw999-fb Date: Thu, 21 Apr 2022 17:03:06 -0400 Subject: [PATCH 1/2] Handles type checks for Type[Protocol] Handles type checks for Type[Protocol] --- src/typeguard/_checkers.py | 10 +++++++++- tests/test_checkers.py | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/typeguard/_checkers.py b/src/typeguard/_checkers.py index 3bed1295..cf13d44a 100644 --- a/src/typeguard/_checkers.py +++ b/src/typeguard/_checkers.py @@ -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] = {} @@ -501,6 +503,12 @@ def check_protocol( raise TypeCheckError( f"is not compatible with the {origin_type.__qualname__} protocol" ) + else: + warnings.warn( + "Typeguard cannot check the {} protocol because it is a non-runtime protocol. " + "If you would like to type check this protocol, " + "please use @typing.runtime_checkable".format(origin_type.__qualname__) + ) def check_byteslike( diff --git a/tests/test_checkers.py b/tests/test_checkers.py index 0e6d738e..561ec4c7 100644 --- a/tests/test_checkers.py +++ b/tests/test_checkers.py @@ -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: @@ -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: From bd2982b580f6a88783c155b1ef3f967cfdb03922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 8 May 2022 23:18:52 +0300 Subject: [PATCH 2/2] Update src/typeguard/_checkers.py --- src/typeguard/_checkers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/typeguard/_checkers.py b/src/typeguard/_checkers.py index cf13d44a..3a8f9261 100644 --- a/src/typeguard/_checkers.py +++ b/src/typeguard/_checkers.py @@ -505,9 +505,9 @@ def check_protocol( ) else: warnings.warn( - "Typeguard cannot check the {} protocol because it is a non-runtime protocol. " - "If you would like to type check this protocol, " - "please use @typing.runtime_checkable".format(origin_type.__qualname__) + 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" )