diff --git a/runtype/pytypes.py b/runtype/pytypes.py index 0dfde89..b96a506 100644 --- a/runtype/pytypes.py +++ b/runtype/pytypes.py @@ -574,7 +574,7 @@ def _to_canon(self, t): return Tuple if Ellipsis in args: if len(args) != 2 or args[0] == Ellipsis: - raise ValueError("Tuple with '...'' expected to be of the exact form: tuple[t, ...].") + raise ValueError("Tuple with '...' expected to be of the exact form: tuple[t, ...].") return TupleEllipsis[to_canon(args[0])] return ProductType([to_canon(x) for x in args]) @@ -584,7 +584,6 @@ def _to_canon(self, t): return SumType(res) elif origin is abc.Callable or origin is typing.Callable: return Callable[ProductType(to_canon(x) for x in args[:-1]), to_canon(args[-1])] - return Callable # TODO elif origin is typing.Literal: return OneOf(args) elif origin is abc.Mapping or origin is typing.Mapping: @@ -611,6 +610,8 @@ def _to_canon(self, t): return Type[self.to_canon(t)] # TODO test issubclass on t.__args__ return Type + elif isinstance(t, typing._GenericAlias): + return self._to_canon(origin) raise NotImplementedError("No support for type:", t) diff --git a/runtype/validation.py b/runtype/validation.py index 2a3a56c..9d68599 100644 --- a/runtype/validation.py +++ b/runtype/validation.py @@ -1,5 +1,6 @@ "User-facing API for validation" +from typing import Any, Tuple, Union, Type from functools import wraps from .common import CHECK_TYPES @@ -23,21 +24,16 @@ def is_subtype(t1, t2): return ct1 <= ct2 -def isa(obj, t): +def isa(obj: Any, t: Union[Type[Any], Tuple[Type[Any], ...]]) -> bool: """Tests if 'obj' is of type 't' Behaves like Python's isinstance, but supports the ``typing`` module and constraints. """ ct = type_caster.to_canon(t) return ct.test_instance(obj) - # try: - # ensure_isa(obj, t) - # return True - # except TypeMismatchError: - # return False -def assert_isa(obj, t): +def assert_isa(obj: Any, t: Union[Type[Any], Tuple[Type[Any], ...]]): """Ensure 'obj' is of type 't'. Otherwise, throws a TypeError Does nothing if Python is run with -O. (like the assert statement) @@ -54,7 +50,7 @@ def assert_isa(obj, t): -def issubclass(t1, t2): +def issubclass(t1: Type[Any], t2: Union[Type[Any], Tuple[Type[Any], ...]]) -> bool: """Test if t1 is a subclass of t2 Parameters: diff --git a/tests/test_basic.py b/tests/test_basic.py index 91e2eb2..b737227 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -4,7 +4,7 @@ import sys import typing -from typing import Any, List, Dict, Tuple, Union, Optional, Callable, Set, FrozenSet, Sequence, Type +from typing import Any, List, Dict, Tuple, Union, Optional, Callable, Set, FrozenSet, Sequence, Type, TypeVar, Generic from collections.abc import Iterable from dataclasses import FrozenInstanceError, field @@ -717,6 +717,17 @@ class B: def a(self, points: list): ... + def test_generic(self): + _Leaf_T = TypeVar("_Leaf_T") + class Tree(Generic[_Leaf_T]): + pass + + @multidispatch + def f(t: Tree[int]): + pass + + f(Tree()) + class TestDataclass(TestCase): def setUp(self):