|
1 | 1 | # -*- coding: utf-8; -*- |
2 | 2 | """Simplistic run-time type checker. |
3 | 3 |
|
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. |
6 | 8 |
|
7 | 9 | We currently provide `isoftype` (cf. `isinstance`), but no `issubtype` (cf. `issubclass`). |
8 | 10 |
|
@@ -42,11 +44,11 @@ def isoftype(value, T): |
42 | 44 | - `TypeVar` |
43 | 45 | - `NewType` (any instance of the underlying actual type will match) |
44 | 46 | - `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]` |
50 | 52 | - `Callable` (argument and return value types currently NOT checked) |
51 | 53 | - `Text` |
52 | 54 |
|
@@ -100,7 +102,6 @@ def isoftype(value, T): |
100 | 102 | # |
101 | 103 | # Python 3.6+: |
102 | 104 | # Generic, NamedTuple, DefaultDict, Counter, ChainMap, Type, |
103 | | - # Mapping, MutableMapping, |
104 | 105 | # KeysView, ItemsView, ValuesView, |
105 | 106 | # Awaitable, Coroutine, AsyncIterable, AsyncIterator, |
106 | 107 | # ContextManager, AsyncContextManager, |
@@ -179,7 +180,7 @@ def isoftype(value, T): |
179 | 180 | return False |
180 | 181 | return all(isoftype(elt, U) for elt, U in zip(value, T.__args__)) |
181 | 182 |
|
182 | | - # Check collection types that allow non-destructive iteration. |
| 183 | + # Check iterable types that allow non-destructive iteration. |
183 | 184 | # |
184 | 185 | # Special-case strings; they match typing.Sequence, but they're not |
185 | 186 | # generics; the class has no `__args__` so this code doesn't apply to |
@@ -216,16 +217,22 @@ def iscollection(statictype, runtimetype): |
216 | 217 | if issubclass(T, statictype): |
217 | 218 | return iscollection(statictype, runtimetype) |
218 | 219 |
|
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): |
221 | 223 | return False |
222 | 224 | 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)) |
224 | 226 | assert len(T.__args__) == 2 |
225 | 227 | if not value: # An empty dict has no key and value types. |
226 | 228 | return False |
227 | 229 | K, V = T.__args__ |
228 | 230 | 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) |
229 | 236 |
|
230 | 237 | if issubclass(T, typing.Callable): |
231 | 238 | if not callable(value): |
|
0 commit comments