Skip to content

Commit

Permalink
Merge pull request #41 from erezsh/typing_type
Browse files Browse the repository at this point in the history
Validation: Added support for typing.Type
  • Loading branch information
erezsh authored Sep 22, 2023
2 parents c09b54a + b3cf4c8 commit fa15cd9
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 3 deletions.
18 changes: 15 additions & 3 deletions runtype/pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def validate_instance(self, obj, sampler=None):
if not isinstance(obj, tuple):
raise TypeMismatchError(obj, self)


# cv_type_checking allows the user to define different behaviors for their objects
# while they are being type-checked.
# This is especially useful if they overrode __hash__ or __eq__ in nonconventional ways.
Expand Down Expand Up @@ -234,7 +235,7 @@ def cast_from(self, obj):


class GenericType(base_types.GenericType, PythonType):
def __init__(self, base, item=Any):
def __init__(self, base: PythonType, item=Any):
return super().__init__(base, item)


Expand Down Expand Up @@ -263,7 +264,7 @@ def cast_from(self, obj):

class DictType(GenericType):

def __init__(self, base, item=Any*Any):
def __init__(self, base: PythonType, item=Any*Any):
super().__init__(base)
if isinstance(item, tuple):
assert len(item) == 2
Expand Down Expand Up @@ -305,6 +306,13 @@ class TupleEllipsisType(SequenceType):
def __repr__(self):
return '%s[%s, ...]' % (self.base, self.item)

class TypeType(GenericType):

def validate_instance(self, obj, sampler=None):
t = type_caster.to_canon(obj)
if not t <= self.item:
raise TypeMismatchError(obj, self)


Object = PythonDataType(object)
Iter = SequenceType(PythonDataType(collections.abc.Iterable))
Expand All @@ -323,6 +331,7 @@ def __repr__(self):
Bytes = PythonDataType(bytes)
Callable = PythonDataType(abc.Callable) # TODO: Generic
Literal = OneOf
Type = TypeType(PythonDataType(type))


class _Number(PythonDataType):
Expand Down Expand Up @@ -578,8 +587,11 @@ def _to_canon(self, t):
x ,= args
return AbstractSet[to_canon(x)]
elif origin is type or origin is typing.Type:
if args:
t ,= args
return Type[self.to_canon(t)]
# TODO test issubclass on t.__args__
return PythonDataType(type)
return Type

raise NotImplementedError("No support for type:", t)

Expand Down
28 changes: 28 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ def __eq__(self, other):
inst = BadEq(True)
assert isa(inst, typing.Literal[1]) == False

def test_type_generic(self):
assert isa(int, typing.Type)
assert isa(int, typing.Type[int])
assert not isa(int, typing.Type[str])
assert isa(typing.List[int], typing.Type[typing.List[int]])
assert not isa(typing.List[int], typing.Type[typing.List[str]])
assert isa(int, typing.Type[object])
assert isa(list, typing.Type[typing.Sequence])

assert issubclass(typing.Type[int], typing.Type[int])
assert not issubclass(typing.Type[int], typing.Type[str])
assert issubclass(typing.Type[list], typing.Type[typing.Sequence])
assert issubclass(typing.Type[typing.List[int]], typing.Type[typing.Sequence[int]])
assert not issubclass(typing.Type[typing.List[int]], typing.Type[typing.Sequence[str]])


class TestDispatch(TestCase):
def setUp(self):
Expand Down Expand Up @@ -581,7 +596,20 @@ def __init__(self, points: list):
p2 = Point([10, 20])
assert p1 == p2

@unittest.skip("not implemented yet")
def test_type_generic(self):
dp = Dispatch()

@dp
def f(t: typing.Type[int]):
return "int"

@dp
def f(t: typing.Type[str]):
return "str"

assert f(int) == "int"
assert f(str) == "str"

def test_sequence(self):
pass
Expand Down

0 comments on commit fa15cd9

Please sign in to comment.