diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d27262e..c6b0234 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,10 @@ ------------------ * Added support for Python 3.10. +* ``interface.IsImplementation`` now uses `TypeGuard`_ so ``mypy`` understands that if it returns ``True``, the + passed object can be considered an implementation of that interface. + +.. _TypeGuard: https://docs.python.org/3/library/typing.html#typing.TypeGuard 2.1.0 (2021-03-19) ------------------ diff --git a/src/oop_ext/interface/_interface.py b/src/oop_ext/interface/_interface.py index d93994a..fd5f902 100644 --- a/src/oop_ext/interface/_interface.py +++ b/src/oop_ext/interface/_interface.py @@ -42,7 +42,6 @@ class MyCalculatorImpl(object): from functools import lru_cache from typing import Any from typing import Callable -from typing import cast from typing import Dict from typing import FrozenSet from typing import Generic @@ -57,6 +56,8 @@ class MyCalculatorImpl(object): from typing import TypeVar from typing import Union +from typing_extensions import TypeGuard + from oop_ext.foundation.cached_method import ImmutableParamsCachedMethod from oop_ext.foundation.decorators import Deprecated from oop_ext.foundation.is_frozen import IsDevelopment @@ -99,7 +100,7 @@ class BadImplementationError(InterfaceError): pass -# InterfaceType should be changed to ``Type[Interface]`` after https://github.com/python/mypy/issues/5374 +# InterfaceType should be changed to ``Type[Interface]`` after https://github.com/python/mypy/issues/4717 # is fixed. InterfaceType = Type[Any] @@ -314,10 +315,10 @@ def _CheckIsInterfaceSubclass(interface: Any) -> None: def IsImplementation( class_or_instance: Any, - interface: InterfaceType, + interface: Type[T], *, requires_declaration: bool = True, -) -> bool: +) -> TypeGuard[T]: """ :param class_or_instance: type or classobj or object diff --git a/src/oop_ext/interface/_tests/test_interface.py b/src/oop_ext/interface/_tests/test_interface.py index 6c0706d..1a072de 100644 --- a/src/oop_ext/interface/_tests/test_interface.py +++ b/src/oop_ext/interface/_tests/test_interface.py @@ -1019,6 +1019,35 @@ def Foo(a: IAcme) -> int: result.assert_ok() +def testIsImplementation(type_checker) -> None: + """ + Check that IsImplementation correctly tells mypy that the type now has that narrowed type + inside the if-block. + """ + type_checker.make_file( + """ + from oop_ext.interface import Interface, IsImplementation + class IAcme(Interface): + def Foo(self, a, b=None) -> int: # type:ignore[empty-body] + ... + + class Acme: + def Foo(self, a, b=None) -> int: + return 40 + a + + def GetIt(a: object) -> int: + if IsImplementation(a, IAcme): + return a.Foo(10) + return 0 + + GetIt(Acme()) + GetIt("hello") + """ + ) + result = type_checker.run() + result.assert_ok() + + def testAttributeTypeChecking(type_checker) -> None: class IFoo(Interface, TypeCheckingSupport): value: int = Attribute(int)