2424import collections
2525import typing
2626
27+ try :
28+ _MyGenericAlias = typing ._GenericAlias
29+ except AttributeError : # Python 3.6 and earlier
30+ class _MyGenericAlias : # unused, but must be a class to support isinstance() check.
31+ pass
32+
2733__all__ = ["isoftype" ]
2834
2935def isoftype (value , T ):
@@ -92,14 +98,9 @@ def isoftype(value, T):
9298 if T is typing .Any :
9399 return True
94100
95- def repr_matches (U , what ):
96- # Python 3.6 has e.g. "typing.TypeVar" as the repr,
97- # but Python 3.7+ adds the "<class '...'>" around it.
98- r = repr (U .__class__ )
99- return r == what or r == "<class '{}'>" .format (what )
100-
101101 # AnyStr normalizes to TypeVar("AnyStr", str, bytes)
102- if repr_matches (T , "typing.TypeVar" ):
102+ # Python 3.6 has "typing.TypeVar" as the repr, but Python 3.7+ adds the "<class '...'>" around it.
103+ if repr (T .__class__ ) == "typing.TypeVar" or repr (T .__class__ ) == "<class 'typing.TypeVar'>" :
103104 if not T .__constraints__ : # just an abstract type name
104105 return True
105106 return any (isoftype (value , U ) for U in T .__constraints__ )
@@ -127,20 +128,58 @@ def repr_matches(U, what):
127128 # in `unpythonic.dispatch`. And see:
128129 # https://docs.python.org/3/library/typing.html#typing.get_type_hints
129130
130- # TODO: Python 3.8 adds `typing.get_origin` and `typing.get_args`, which may be useful
131- # TODO: to analyze generics once we bump our minimum Python to that.
131+ # TODO: Python 3.8 adds `typing.get_origin` and `typing.get_args`:
132132 # https://docs.python.org/3/library/typing.html#typing.get_origin
133- # if repr(T).startswith("typing.Generic["):
134- # pass
135-
136- if repr_matches (T , "typing.Union" ): # Optional normalizes to Union[argtype, NoneType].
133+ # TODO: We replicate them here so that we can use them in 3.7.
134+ # TODO: Delete the local copies once we start requiring Python 3.8.
135+ #
136+ # Used under the PSF license. Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
137+ # 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; All Rights Reserved
138+ # https://github.com/python/cpython/blob/3.8/LICENSE
139+ def get_origin (tp ):
140+ """Get the unsubscripted version of a type.
141+ This supports generic types, Callable, Tuple, Union, Literal, Final and ClassVar.
142+ Return None for unsupported types. Examples::
143+ get_origin(Literal[42]) is Literal
144+ get_origin(int) is None
145+ get_origin(ClassVar[int]) is ClassVar
146+ get_origin(Generic) is Generic
147+ get_origin(Generic[T]) is Generic
148+ get_origin(Union[T, int]) is Union
149+ get_origin(List[Tuple[T, T]][int]) == list
150+ """
151+ if isinstance (tp , _MyGenericAlias ):
152+ return tp .__origin__
153+ if tp is typing .Generic :
154+ return typing .Generic
155+ return None
156+ def get_args (tp ):
157+ """Get type arguments with all substitutions performed.
158+ For unions, basic simplifications used by Union constructor are performed.
159+ Examples::
160+ get_args(Dict[str, int]) == (str, int)
161+ get_args(int) == ()
162+ get_args(Union[int, Union[T, int], str][int]) == (int, str)
163+ get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
164+ get_args(Callable[[], T][int]) == ([], int)
165+ """
166+ if isinstance (tp , _MyGenericAlias ) and not tp ._special :
167+ res = tp .__args__
168+ if get_origin (tp ) is collections .abc .Callable and res [0 ] is not Ellipsis :
169+ res = (list (res [:- 1 ]), res [- 1 ])
170+ return res
171+ return ()
172+
173+ # Optional normalizes to Union[argtype, NoneType].
174+ # Python 3.6 has the repr, 3.7+ use typing._GenericAlias.
175+ if repr (T .__class__ ) == "typing.Union" or get_origin (T ) is typing .Union :
137176 if T .__args__ is None : # bare `typing.Union`; empty, has no types in it, so no value can match.
138177 return False
139178 if not any (isoftype (value , U ) for U in T .__args__ ):
140179 return False
141180 return True
142181
143- # TODO: in Python 3.7+, what is this mysterious callable that doesn't have `__qualname__`?
182+ # TODO: in Python 3.7+, what is the mysterious callable that doesn't have `__qualname__`?
144183 if callable (T ) and hasattr (T , "__qualname__" ) and T .__qualname__ == "NewType.<locals>.new_type" :
145184 # This is the best we can do, because the static types created by `typing.NewType`
146185 # have a constructor that discards the type information at runtime:
@@ -176,7 +215,8 @@ def repr_matches(U, what):
176215 if issubclass (T , typing .Text ): # https://docs.python.org/3/library/typing.html#typing.Text
177216 return isinstance (value , str ) # alias for str
178217
179- if issubclass (T , typing .Tuple ):
218+ # Subclass test for Python 3.6 only. Python 3.7+ have typing._GenericAlias for the generics.
219+ if issubclass (T , typing .Tuple ) or get_origin (T ) is tuple :
180220 if not isinstance (value , tuple ):
181221 return False
182222 # bare `typing.Tuple`, no restrictions on length or element type.
@@ -209,12 +249,12 @@ def ismapping(statictype, runtimetype):
209249 for statictype , runtimetype in ((typing .Dict , dict ),
210250 (typing .MutableMapping , collections .abc .MutableMapping ),
211251 (typing .Mapping , collections .abc .Mapping )):
212- if issubclass (T , statictype ):
252+ if issubclass (T , statictype ) or get_origin ( T ) is runtimetype :
213253 return ismapping (statictype , runtimetype )
214254
215255 # ItemsView is a special-case mapping in that we must not call
216256 # `.items()` on `value`.
217- if issubclass (T , typing .ItemsView ):
257+ if issubclass (T , typing .ItemsView ) or get_origin ( T ) is collections . abc . ItemsView :
218258 if not isinstance (value , collections .abc .ItemsView ):
219259 return False
220260 if T .__args__ is None :
@@ -234,10 +274,10 @@ def ismapping(statictype, runtimetype):
234274 def iscollection (statictype , runtimetype ):
235275 if not isinstance (value , runtimetype ):
236276 return False
237- if issubclass (statictype , typing .ByteString ):
277+ if issubclass (statictype , typing .ByteString ) or get_origin ( statictype ) is collections . abc . ByteString :
238278 # WTF? A ByteString is a Sequence[int], but only statically.
239279 # At run time, the `__args__` are actually empty - it looks
240- # like a bare Sequence, which is invalid. Hack the special case.
280+ # like a bare Sequence, which is invalid. HACK the special case.
241281 typeargs = (int ,)
242282 else :
243283 typeargs = T .__args__
@@ -265,10 +305,10 @@ def iscollection(statictype, runtimetype):
265305 (typing .MutableSequence , collections .abc .MutableSequence ),
266306 (typing .MappingView , collections .abc .MappingView ),
267307 (typing .Sequence , collections .abc .Sequence )):
268- if issubclass (T , statictype ):
308+ if issubclass (T , statictype ) or get_origin ( T ) is runtimetype :
269309 return iscollection (statictype , runtimetype )
270310
271- if issubclass (T , typing .Callable ):
311+ if issubclass (T , typing .Callable ) or get_origin ( T ) is collections . abc . Callable :
272312 if not callable (value ):
273313 return False
274314 return True
0 commit comments