Skip to content

Commit 762da5c

Browse files
committed
Type checker: add Mapping, MutableMapping
1 parent 662966a commit 762da5c

1 file changed

Lines changed: 19 additions & 12 deletions

File tree

unpythonic/typecheck.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# -*- coding: utf-8; -*-
22
"""Simplistic run-time type checker.
33
4-
This implements just a minimal feature set needed for checking function arguments in
5-
typical uses of multiple dispatch (see `unpythonic.dispatch`).
4+
This implements just a minimal feature set needed for checking function
5+
arguments in typical uses of multiple dispatch (see `unpythonic.dispatch`).
6+
That said, this DOES support many (but not all) features of the `typing` stdlib
7+
module.
68
79
We currently provide `isoftype` (cf. `isinstance`), but no `issubtype` (cf. `issubclass`).
810
@@ -42,11 +44,11 @@ def isoftype(value, T):
4244
- `TypeVar`
4345
- `NewType` (any instance of the underlying actual type will match)
4446
- `Union[T1, T2, ..., TN]`
45-
- `Tuple`, `Tuple[T, ...]`, `Tuple[T1, T2, ..., TN]`
46-
- `List[T]`
47-
- `FrozenSet[T]`
48-
- `Set[T]`
49-
- `Dict[K, V]`
47+
- `Tuple`, `Tuple[T, ...]`, `Tuple[T1, T2, ..., TN]`, `Sequence[T]`
48+
- `List[T]`, `MutableSequence[T]`
49+
- `FrozenSet[T]`, `AbstractSet[T]`
50+
- `Set[T]`, `MutableSet[T]`
51+
- `Dict[K, V]`, `MutableMapping[K, V]`, `Mapping[K, V]`
5052
- `Callable` (argument and return value types currently NOT checked)
5153
- `Text`
5254
@@ -100,7 +102,6 @@ def isoftype(value, T):
100102
#
101103
# Python 3.6+:
102104
# Generic, NamedTuple, DefaultDict, Counter, ChainMap, Type,
103-
# Mapping, MutableMapping,
104105
# KeysView, ItemsView, ValuesView,
105106
# Awaitable, Coroutine, AsyncIterable, AsyncIterator,
106107
# ContextManager, AsyncContextManager,
@@ -179,7 +180,7 @@ def isoftype(value, T):
179180
return False
180181
return all(isoftype(elt, U) for elt, U in zip(value, T.__args__))
181182

182-
# Check collection types that allow non-destructive iteration.
183+
# Check iterable types that allow non-destructive iteration.
183184
#
184185
# Special-case strings; they match typing.Sequence, but they're not
185186
# generics; the class has no `__args__` so this code doesn't apply to
@@ -216,16 +217,22 @@ def iscollection(statictype, runtimetype):
216217
if issubclass(T, statictype):
217218
return iscollection(statictype, runtimetype)
218219

219-
if issubclass(T, typing.Dict):
220-
if not isinstance(value, dict):
220+
# Check mapping types that allow non-destructive iteration.
221+
def ismapping(statictype, runtimetype):
222+
if not isinstance(value, runtimetype):
221223
return False
222224
if T.__args__ is None:
223-
raise TypeError("Missing mandatory key, value type arguments of `typing.Dict`")
225+
raise TypeError("Missing mandatory key, value type arguments of `{}`".format(statictype))
224226
assert len(T.__args__) == 2
225227
if not value: # An empty dict has no key and value types.
226228
return False
227229
K, V = T.__args__
228230
return all(isoftype(k, K) and isoftype(v, V) for k, v in value.items())
231+
for statictype, runtimetype in ((typing.Dict, dict),
232+
(typing.Mapping, collections.abc.Mapping),
233+
(typing.MutableMapping, collections.abc.MutableMapping)):
234+
if issubclass(T, statictype):
235+
return ismapping(statictype, runtimetype)
229236

230237
if issubclass(T, typing.Callable):
231238
if not callable(value):

0 commit comments

Comments
 (0)