Skip to content

Commit cab8b61

Browse files
committed
new tests: Python 3.7+ compatibility
1 parent c5a0218 commit cab8b61

File tree

2 files changed

+23
-15
lines changed

2 files changed

+23
-15
lines changed

unpythonic/test/test_typecheck.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def runtests():
100100
test[isoftype({17: "cat", 23: "fox", 42: "python"}, typing.Dict[int, str])]
101101
test[not isoftype({"bar": "foo", "tavern": "a place"}, typing.Dict[int, str])]
102102
test[not isoftype(42, typing.Dict[int, str])]
103-
# missing type arguments
104-
test_raises[TypeError, isoftype({"cat": "small cat", "lion": "big cat"}, typing.Dict)]
103+
# no type arguments: any key/value types ok (consistent with Python 3.7+)
104+
test[isoftype({"cat": "animal", "pi": 3.14159, 2.71828: "e"}, typing.Dict)]
105105

106106
# type alias (at run time, this is just an assignment)
107107
with testset("type alias"):
@@ -191,10 +191,10 @@ def runtests():
191191
test[isoftype(d.values(), typing.ValuesView[str])]
192192
test[isoftype(d.items(), typing.ItemsView[int, str])]
193193

194-
# missing mandatory type argument(s)
195-
test_raises[TypeError, isoftype(d.keys(), typing.KeysView)]
196-
test_raises[TypeError, isoftype(d.values(), typing.ValuesView)]
197-
test_raises[TypeError, isoftype(d.items(), typing.ItemsView)]
194+
# no type arguments: any key/value types ok (consistent with Python 3.7+)
195+
test[isoftype(d.keys(), typing.KeysView)]
196+
test[isoftype(d.values(), typing.ValuesView)]
197+
test[isoftype(d.items(), typing.ItemsView)]
198198

199199
test[not isoftype("hello", typing.ItemsView)]
200200
test[not isoftype({}.items(), typing.ItemsView[int, str])] # empty dict has no key, value types

unpythonic/typecheck.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,16 @@ def get_origin(tp):
182182
# Optional normalizes to Union[argtype, NoneType].
183183
# Python 3.6 has the repr, 3.7+ use typing._GenericAlias.
184184
if repr(T.__class__) == "typing.Union" or get_origin(T) is typing.Union:
185-
if T.__args__ is None: # bare `typing.Union`; empty, has no types in it, so no value can match.
185+
if T.__args__ is None: # Python 3.6 bare `typing.Union`; empty, has no types in it, so no value can match.
186186
return False
187187
if not any(isoftype(value, U) for U in T.__args__):
188188
return False
189189
return True
190190

191+
# Python 3.7+ bare typing.Union; empty, has no types in it, so no value can match.
192+
if T is typing.Union: # isinstance(T, typing._SpecialForm) and T._name == "Union":
193+
return False
194+
191195
# TODO: in Python 3.7+, what is the mysterious callable that doesn't have `__qualname__`?
192196
if callable(T) and hasattr(T, "__qualname__") and T.__qualname__ == "NewType.<locals>.new_type":
193197
# This is the best we can do, because the static types created by `typing.NewType`
@@ -253,12 +257,14 @@ def get_origin(tp):
253257
def ismapping(statictype, runtimetype):
254258
if not isinstance(value, runtimetype):
255259
return False
256-
if T.__args__ is None:
257-
raise TypeError("Missing mandatory key, value type arguments of `{}`".format(statictype))
258-
assert len(T.__args__) == 2
260+
if T.__args__ is None: # Python 3.6: consistent behavior with 3.7+, which use unconstrained TypeVar KT, VT.
261+
args = (typing.TypeVar("KT"), typing.TypeVar("VT"))
262+
else:
263+
args = T.__args__
264+
assert len(args) == 2
259265
if not value: # An empty dict has no key and value types.
260266
return False
261-
K, V = T.__args__
267+
K, V = args
262268
return all(isoftype(k, K) and isoftype(v, V) for k, v in value.items())
263269
for statictype, runtimetype in ((typing.Dict, dict),
264270
(typing.MutableMapping, collections.abc.MutableMapping),
@@ -271,12 +277,14 @@ def ismapping(statictype, runtimetype):
271277
if safeissubclass(T, typing.ItemsView) or get_origin(T) is collections.abc.ItemsView:
272278
if not isinstance(value, collections.abc.ItemsView):
273279
return False
274-
if T.__args__ is None:
275-
raise TypeError("Missing mandatory key, value type arguments of `{}`".format(statictype))
276-
assert len(T.__args__) == 2
280+
if T.__args__ is None: # Python 3.6: consistent behavior with 3.7+, which use unconstrained TypeVar KT, VT.
281+
args = (typing.TypeVar("KT"), typing.TypeVar("VT"))
282+
else:
283+
args = T.__args__
284+
assert len(args) == 2
277285
if not value: # An empty dict has no key and value types.
278286
return False
279-
K, V = T.__args__
287+
K, V = args
280288
return all(isoftype(k, K) and isoftype(v, V) for k, v in value)
281289

282290
# Check iterable types that allow non-destructive iteration.

0 commit comments

Comments
 (0)