Skip to content

Commit

Permalink
Use TypeGuard in IsImplementation
Browse files Browse the repository at this point in the history
However due to python/mypy#4717, this breaks
IsImplemenation with errors like this one:

   Only concrete class can be given where "Type[_InterfM3]" is expected  [type-abstract]

Which is unfortunate.
  • Loading branch information
nicoddemus committed May 16, 2023
1 parent 09a9953 commit 4b0291e
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------
Expand Down
9 changes: 5 additions & 4 deletions src/oop_ext/interface/_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions src/oop_ext/interface/_tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 4b0291e

Please sign in to comment.