From e42c0a8db3b7dae1a5774260dc248c8154d8b93a Mon Sep 17 00:00:00 2001 From: Erez Shinan Date: Tue, 12 Sep 2023 16:00:51 +0300 Subject: [PATCH] Fix: Added support for typing.Mutable* and more --- runtype/pytypes.py | 24 ++++++++++++++++++++++++ tests/test_basic.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/runtype/pytypes.py b/runtype/pytypes.py index d372810..b35fd3f 100644 --- a/runtype/pytypes.py +++ b/runtype/pytypes.py @@ -298,10 +298,13 @@ def __repr__(self): Iter = SequenceType(PythonDataType(collections.abc.Iterable)) List = SequenceType(PythonDataType(list)) Sequence = SequenceType(PythonDataType(abc.Sequence)) +MutableSequence = SequenceType(PythonDataType(abc.MutableSequence)) Set = SequenceType(PythonDataType(set)) FrozenSet = SequenceType(PythonDataType(frozenset)) +AbstractSet = SequenceType(PythonDataType(abc.Set)) Dict = DictType(PythonDataType(dict)) Mapping = DictType(PythonDataType(abc.Mapping)) +MutableMapping = DictType(PythonDataType(abc.MutableMapping)) Tuple = TupleType() TupleEllipsis = TupleEllipsisType(PythonDataType(tuple)) # Float = PythonDataType(float) @@ -348,6 +351,11 @@ def __call__(self, min_length=None, max_length=None): return Constraint(self, predicates) + def __le__(self, other): + if isinstance(other, SequenceType): + return self <= other.base and self <= other.item + return super().__le__(other) + class _DateTime(PythonDataType): def cast_from(self, obj): @@ -494,8 +502,12 @@ def _to_canon(self, t): return Tuple elif t is typing.Mapping: # 3.6 return Mapping + elif t is typing.MutableMapping: + return MutableMapping elif t is typing.Sequence: return Sequence + elif t is typing.MutableSequence: + return MutableSequence if origin is None: if isinstance(t, typing.TypeVar): @@ -538,9 +550,21 @@ def _to_canon(self, t): elif origin is abc.Mapping or origin is typing.Mapping: k, v = args return Mapping[to_canon(k), to_canon(v)] + elif origin is abc.MutableMapping or origin is typing.MutableMapping: + k, v = args + return MutableMapping[to_canon(k), to_canon(v)] elif origin is abc.Sequence or origin is typing.Sequence: x ,= args return Sequence[to_canon(x)] + elif origin is abc.MutableSequence or origin is typing.MutableSequence: + x ,= args + return MutableSequence[to_canon(x)] + elif origin is abc.MutableSet or origin is typing.MutableSet: + x ,= args + return Set[to_canon(x)] + elif origin is abc.Set or origin is typing.AbstractSet: + x ,= args + return AbstractSet[to_canon(x)] elif origin is type or origin is typing.Type: # TODO test issubclass on t.__args__ return PythonDataType(type) diff --git a/tests/test_basic.py b/tests/test_basic.py index 68dda6f..f13e4f0 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -109,6 +109,21 @@ def test_issubclass(self): assert issubclass(typing.Tuple[int], typing.Tuple[typing.Union[int, str]]) assert issubclass(typing.Tuple[int, ...], typing.Tuple[typing.Union[int, str], ...]) + def test_issubclass_mutable(self): + assert issubclass(typing.Dict[int, str], typing.MutableMapping[int, str]) + assert issubclass(typing.MutableMapping[int, str], typing.Mapping[int, str]) + + assert issubclass(typing.List[int], typing.MutableSequence[int]) + assert issubclass(typing.MutableSequence[int], typing.Sequence[int]) + + assert issubclass(typing.Set[int], typing.MutableSet[int]) + assert issubclass(typing.MutableSet[int], typing.AbstractSet[int]) + + def test_issubclass_str_sequence(self): + assert not issubclass(str, typing.MutableSequence[int]) + assert not issubclass(str, typing.Sequence[int]) + assert issubclass(str, typing.Sequence[str]) + def test_issubclass_tuple(self): # test class tuple t = int, float