@@ -49,6 +49,7 @@ def isoftype(value, T):
4949 - `FrozenSet[T]`, `AbstractSet[T]`
5050 - `Set[T]`, `MutableSet[T]`
5151 - `Dict[K, V]`, `MutableMapping[K, V]`, `Mapping[K, V]`
52+ - `ItemsView[K, V]`, `KeysView[K]`, `ValuesView[V]`
5253 - `Callable` (argument and return value types currently NOT checked)
5354 - `Text`
5455
@@ -101,20 +102,23 @@ def isoftype(value, T):
101102 # TODO: If you add a feature to the type checker, please update this list.
102103 #
103104 # Python 3.6+:
104- # Generic, NamedTuple, DefaultDict, Counter, ChainMap, Type,
105- # KeysView, ItemsView, ValuesView,
105+ # NamedTuple, DefaultDict, Counter, ChainMap,
106+ # IO, TextIO, BinaryIO,
107+ # Pattern, Match, (regular expressions)
108+ # Generic, Type,
106109 # Awaitable, Coroutine, AsyncIterable, AsyncIterator,
107110 # ContextManager, AsyncContextManager,
108111 # Generator, AsyncGenerator,
109- # IO, TextIO, BinaryIO,
110- # Pattern, Match, (regular expressions)
111112 # NoReturn (callable return value only),
112113 # ClassVar, Final
113114 #
114115 # Python 3.7+: OrderedDict
115116 # Python 3.8+: Protocol, SupportsIndex, TypedDict, Literal
116117 #
117118 # TODO: Do we need to support `typing.ForwardRef`?
119+ # No, if `get_type_hints` already resolves that. Consider our main use case,
120+ # in `unpythonic.dispatch`. And see:
121+ # https://docs.python.org/3/library/typing.html#typing.get_type_hints
118122
119123 # TODO: Python 3.8 adds `typing.get_origin` and `typing.get_args`, which may be useful
120124 # TODO: to analyze generics once we bump our minimum Python to that.
@@ -183,6 +187,36 @@ def isoftype(value, T):
183187 return False
184188 return all (isoftype (elt , U ) for elt , U in zip (value , T .__args__ ))
185189
190+ # Check mapping types that allow non-destructive iteration.
191+ def ismapping (statictype , runtimetype ):
192+ if not isinstance (value , runtimetype ):
193+ return False
194+ if T .__args__ is None :
195+ raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
196+ assert len (T .__args__ ) == 2
197+ if not value : # An empty dict has no key and value types.
198+ return False
199+ K , V = T .__args__
200+ return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value .items ())
201+ for statictype , runtimetype in ((typing .Dict , dict ),
202+ (typing .MutableMapping , collections .abc .MutableMapping ),
203+ (typing .Mapping , collections .abc .Mapping )):
204+ if issubclass (T , statictype ):
205+ return ismapping (statictype , runtimetype )
206+
207+ # ItemsView is a special-case mapping in that we must not call
208+ # `.items()` on `value`.
209+ if issubclass (T , typing .ItemsView ):
210+ if not isinstance (value , collections .abc .ItemsView ):
211+ return False
212+ if T .__args__ is None :
213+ raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
214+ assert len (T .__args__ ) == 2
215+ if not value : # An empty dict has no key and value types.
216+ return False
217+ K , V = T .__args__
218+ return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value )
219+
186220 # Check iterable types that allow non-destructive iteration.
187221 #
188222 # Special-case strings; they match typing.Sequence, but they're not
@@ -216,29 +250,16 @@ def iscollection(statictype, runtimetype):
216250 (typing .MutableSet , collections .abc .MutableSet ), # must check mutable first
217251 # because a mutable value has *also* the interface of the immutable variant
218252 # (e.g. MutableSet is a subtype of AbstractSet)
253+ (typing .KeysView , collections .abc .KeysView ),
254+ (typing .ValuesView , collections .abc .ValuesView ),
255+ (typing .MappingView , collections .abc .MappingView ), # MappingView has one type argument so it goes here?
219256 (typing .AbstractSet , collections .abc .Set ),
220257 (typing .MutableSequence , collections .abc .MutableSequence ),
258+ (typing .MappingView , collections .abc .MappingView ),
221259 (typing .Sequence , collections .abc .Sequence )):
222260 if issubclass (T , statictype ):
223261 return iscollection (statictype , runtimetype )
224262
225- # Check mapping types that allow non-destructive iteration.
226- def ismapping (statictype , runtimetype ):
227- if not isinstance (value , runtimetype ):
228- return False
229- if T .__args__ is None :
230- raise TypeError ("Missing mandatory key, value type arguments of `{}`" .format (statictype ))
231- assert len (T .__args__ ) == 2
232- if not value : # An empty dict has no key and value types.
233- return False
234- K , V = T .__args__
235- return all (isoftype (k , K ) and isoftype (v , V ) for k , v in value .items ())
236- for statictype , runtimetype in ((typing .Dict , dict ),
237- (typing .MutableMapping , collections .abc .MutableMapping ),
238- (typing .Mapping , collections .abc .Mapping )):
239- if issubclass (T , statictype ):
240- return ismapping (statictype , runtimetype )
241-
242263 if issubclass (T , typing .Callable ):
243264 if not callable (value ):
244265 return False
0 commit comments