@@ -214,134 +214,131 @@ def get_args(tp):
214214
215215 # We don't have a match yet, so T might still be one of those meta-utilities
216216 # that hate `issubclass` with a passion.
217- try :
218- if safeissubclass (T , typing .Text ): # https://docs.python.org/3/library/typing.html#typing.Text
219- return isinstance (value , str ) # alias for str
217+ if safeissubclass (T , typing .Text ): # https://docs.python.org/3/library/typing.html#typing.Text
218+ return isinstance (value , str ) # alias for str
220219
221- # Subclass test for Python 3.6 only. Python 3.7+ have typing._GenericAlias for the generics.
222- if safeissubclass (T , typing .Tuple ) or get_origin (T ) is tuple :
223- if not isinstance (value , tuple ):
224- return False
225- # blare `typing.Tuple`, no restrictions on length or element type.
226- if T .__args__ is None :
227- return True
228- # homogeneous element type, arbitrary length
229- if len (T .__args__ ) == 2 and T .__args__ [1 ] is Ellipsis :
230- if not value : # no elements
231- # An empty tuple has no element type, so to make multiple dispatch
232- # behave predictably (so it doesn't guess), we must reject it.
233- return False
234- U = T .__args__ [0 ]
235- return all (isoftype (elt , U ) for elt in value )
236- # heterogeneous element types, exact length
237- if len (value ) != len (T .__args__ ):
220+ # Subclass test for Python 3.6 only. Python 3.7+ have typing._GenericAlias for the generics.
221+ if safeissubclass (T , typing .Tuple ) or get_origin (T ) is tuple :
222+ if not isinstance (value , tuple ):
223+ return False
224+ # blare `typing.Tuple`, no restrictions on length or element type.
225+ if T .__args__ is None :
226+ return True
227+ # homogeneous element type, arbitrary length
228+ if len (T .__args__ ) == 2 and T .__args__ [1 ] is Ellipsis :
229+ if not value : # no elements
230+ # An empty tuple has no element type, so to make multiple dispatch
231+ # behave predictably (so it doesn't guess), we must reject it.
238232 return False
239- return all (isoftype (elt , U ) for elt , U in zip (value , T .__args__ ))
233+ U = T .__args__ [0 ]
234+ return all (isoftype (elt , U ) for elt in value )
235+ # heterogeneous element types, exact length
236+ if len (value ) != len (T .__args__ ):
237+ return False
238+ return all (isoftype (elt , U ) for elt , U in zip (value , T .__args__ ))
240239
241- # Check mapping types that allow non-destructive iteration.
242- def ismapping (statictype , runtimetype ):
240+ # Check mapping types that allow non-destructive iteration.
241+ def ismapping (statictype , runtimetype ):
242+ if not isinstance (value , runtimetype ):
243+ return False
244+ if T .__args__ is None :
245+ raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
246+ assert len (T .__args__ ) == 2
247+ if not value : # An empty dict has no key and value types.
248+ return False
249+ K , V = T .__args__
250+ return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value .items ())
251+ for statictype , runtimetype in ((typing .Dict , dict ),
252+ (typing .MutableMapping , collections .abc .MutableMapping ),
253+ (typing .Mapping , collections .abc .Mapping )):
254+ if safeissubclass (T , statictype ) or get_origin (T ) is runtimetype :
255+ return ismapping (statictype , runtimetype )
256+
257+ # ItemsView is a special-case mapping in that we must not call
258+ # `.items()` on `value`.
259+ if safeissubclass (T , typing .ItemsView ) or get_origin (T ) is collections .abc .ItemsView :
260+ if not isinstance (value , collections .abc .ItemsView ):
261+ return False
262+ if T .__args__ is None :
263+ raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
264+ assert len (T .__args__ ) == 2
265+ if not value : # An empty dict has no key and value types.
266+ return False
267+ K , V = T .__args__
268+ return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value )
269+
270+ # Check iterable types that allow non-destructive iteration.
271+ #
272+ # Special-case strings; they match typing.Sequence, but they're not
273+ # generics; the class has no `__args__` so this code doesn't apply to
274+ # them.
275+ if T not in (str , bytes ):
276+ def iscollection (statictype , runtimetype ):
243277 if not isinstance (value , runtimetype ):
244278 return False
245- if T .__args__ is None :
246- raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
247- assert len (T .__args__ ) == 2
248- if not value : # An empty dict has no key and value types.
279+ if safeissubclass (statictype , typing .ByteString ) or get_origin (statictype ) is collections .abc .ByteString :
280+ # WTF? A ByteString is a Sequence[int], but only statically.
281+ # At run time, the `__args__` are actually empty - it looks
282+ # like a bare Sequence, which is invalid. HACK the special case.
283+ typeargs = (int ,)
284+ else :
285+ typeargs = T .__args__
286+ if typeargs is None :
287+ raise TypeError ("Missing mandatory element type argument of `{}`" .format (statictype ))
288+ # Judging by the docs, List takes one type argument. The rest are similar.
289+ # https://docs.python.org/3/library/typing.html#typing.List
290+ assert len (typeargs ) == 1
291+ if not value : # An empty collection has no element type.
249292 return False
250- K , V = T .__args__
251- return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value .items ())
252- for statictype , runtimetype in ((typing .Dict , dict ),
253- (typing .MutableMapping , collections .abc .MutableMapping ),
254- (typing .Mapping , collections .abc .Mapping )):
293+ U = typeargs [0 ]
294+ return all (isoftype (elt , U ) for elt in value )
295+ for statictype , runtimetype in ((typing .List , list ),
296+ (typing .FrozenSet , frozenset ),
297+ (typing .Set , set ),
298+ (typing .Deque , collections .deque ),
299+ (typing .ByteString , collections .abc .ByteString ), # must check before Sequence
300+ (typing .MutableSet , collections .abc .MutableSet ), # must check mutable first
301+ # because a mutable value has *also* the interface of the immutable variant
302+ # (e.g. MutableSet is a subtype of AbstractSet)
303+ (typing .KeysView , collections .abc .KeysView ),
304+ (typing .ValuesView , collections .abc .ValuesView ),
305+ (typing .MappingView , collections .abc .MappingView ), # MappingView has one type argument so it goes here?
306+ (typing .AbstractSet , collections .abc .Set ),
307+ (typing .MutableSequence , collections .abc .MutableSequence ),
308+ (typing .MappingView , collections .abc .MappingView ),
309+ (typing .Sequence , collections .abc .Sequence )):
255310 if safeissubclass (T , statictype ) or get_origin (T ) is runtimetype :
256- return ismapping (statictype , runtimetype )
311+ return iscollection (statictype , runtimetype )
257312
258- # ItemsView is a special-case mapping in that we must not call
259- # `.items()` on `value`.
260- if safeissubclass (T , typing .ItemsView ) or get_origin (T ) is collections .abc .ItemsView :
261- if not isinstance (value , collections .abc .ItemsView ):
262- return False
263- if T .__args__ is None :
264- raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
265- assert len (T .__args__ ) == 2
266- if not value : # An empty dict has no key and value types.
267- return False
268- K , V = T .__args__
269- return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value )
270-
271- # Check iterable types that allow non-destructive iteration.
272- #
273- # Special-case strings; they match typing.Sequence, but they're not
274- # generics; the class has no `__args__` so this code doesn't apply to
275- # them.
276- if T not in (str , bytes ):
277- def iscollection (statictype , runtimetype ):
278- if not isinstance (value , runtimetype ):
279- return False
280- if safeissubclass (statictype , typing .ByteString ) or get_origin (statictype ) is collections .abc .ByteString :
281- # WTF? A ByteString is a Sequence[int], but only statically.
282- # At run time, the `__args__` are actually empty - it looks
283- # like a bare Sequence, which is invalid. HACK the special case.
284- typeargs = (int ,)
285- else :
286- typeargs = T .__args__
287- if typeargs is None :
288- raise TypeError ("Missing mandatory element type argument of `{}`" .format (statictype ))
289- # Judging by the docs, List takes one type argument. The rest are similar.
290- # https://docs.python.org/3/library/typing.html#typing.List
291- assert len (typeargs ) == 1
292- if not value : # An empty collection has no element type.
293- return False
294- U = typeargs [0 ]
295- return all (isoftype (elt , U ) for elt in value )
296- for statictype , runtimetype in ((typing .List , list ),
297- (typing .FrozenSet , frozenset ),
298- (typing .Set , set ),
299- (typing .Deque , collections .deque ),
300- (typing .ByteString , collections .abc .ByteString ), # must check before Sequence
301- (typing .MutableSet , collections .abc .MutableSet ), # must check mutable first
302- # because a mutable value has *also* the interface of the immutable variant
303- # (e.g. MutableSet is a subtype of AbstractSet)
304- (typing .KeysView , collections .abc .KeysView ),
305- (typing .ValuesView , collections .abc .ValuesView ),
306- (typing .MappingView , collections .abc .MappingView ), # MappingView has one type argument so it goes here?
307- (typing .AbstractSet , collections .abc .Set ),
308- (typing .MutableSequence , collections .abc .MutableSequence ),
309- (typing .MappingView , collections .abc .MappingView ),
310- (typing .Sequence , collections .abc .Sequence )):
311- if safeissubclass (T , statictype ) or get_origin (T ) is runtimetype :
312- return iscollection (statictype , runtimetype )
313-
314- if safeissubclass (T , typing .Callable ) or get_origin (T ) is collections .abc .Callable :
315- if not callable (value ):
316- return False
317- return True
318- # # TODO: analyze Callable[[a0, a1, ...], ret], Callable[..., ret].
319- # if T.__args__ is None: # bare `typing.Callable`, no restrictions on arg/return types.
320- # return True
321- # sig = typing.get_type_hints(value)
322- # *argtypes, rettype = T.__args__
323- # if len(argtypes) == 1 and argtypes[0] is Ellipsis:
324- # pass # argument types not specified
325- # else:
326- # # TODO: we need the names of the positional arguments of the `value` callable here.
327- # for a in argtypes:
328- # # TODO: We need to compare two specs against each other, not a value against T.
329- # # This needs an `issubtype` function.
330- # #
331- # # Note arguments behave contravariantly, while return values behave covariantly:
332- # # - f(x: animal) is a *subtype* of f(x: cat), since any use site that passes
333- # # in a cat (more specific) works fine with a function that accepts any animal
334- # # (more general).
335- # # - g() -> int is a subtype of g() -> Number, because any use site that
336- # # expects a Number (more general) can deal with an int (more specific).
337- # # https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
338- # if not issubtype(???, a):
339- # return False
340- # if not issubtype(rettype, sig["return"]):
341- # return False
342- # return True
343- except TypeError : # probably one of those meta-utilities that hates issubclass.
344- raise # DEBUG
313+ if safeissubclass (T , typing .Callable ) or get_origin (T ) is collections .abc .Callable :
314+ if not callable (value ):
315+ return False
316+ return True
317+ # # TODO: analyze Callable[[a0, a1, ...], ret], Callable[..., ret].
318+ # if T.__args__ is None: # bare `typing.Callable`, no restrictions on arg/return types.
319+ # return True
320+ # sig = typing.get_type_hints(value)
321+ # *argtypes, rettype = T.__args__
322+ # if len(argtypes) == 1 and argtypes[0] is Ellipsis:
323+ # pass # argument types not specified
324+ # else:
325+ # # TODO: we need the names of the positional arguments of the `value` callable here.
326+ # for a in argtypes:
327+ # # TODO: We need to compare two specs against each other, not a value against T.
328+ # # This needs an `issubtype` function.
329+ # #
330+ # # Note arguments behave contravariantly, while return values behave covariantly:
331+ # # - f(x: animal) is a *subtype* of f(x: cat), since any use site that passes
332+ # # in a cat (more specific) works fine with a function that accepts any animal
333+ # # (more general).
334+ # # - g() -> int is a subtype of g() -> Number, because any use site that
335+ # # expects a Number (more general) can deal with an int (more specific).
336+ # # https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
337+ # if not issubtype(???, a):
338+ # return False
339+ # if not issubtype(rettype, sig["return"]):
340+ # return False
341+ # return True
345342
346343 # Catch any `typing` meta-utilities we don't currently support.
347344 if hasattr (T , "__module__" ) and T .__module__ == "typing" :
0 commit comments