def _rewrite(self, typ: type): if isinstance(typ, _Any): return typ arg_classes = getattr(typ, '__args__', []) if not arg_classes: return typ if typing_inspect.is_generic_type(typ) or typing_inspect.is_tuple_type( typ): gorg = typing_inspect.get_origin(typ) processed_args = [] for arg_class in arg_classes: processed = self._rewrite(arg_class) if processed is NOT_SIMPLIFIED: processed = arg_class processed_args.append(processed) return gorg[tuple(processed_args)] processed_args = [] for arg_class in arg_classes: rewritten = self._rewrite(arg_class) if rewritten is NOT_SIMPLIFIED: processed_args.append(arg_class) continue processed_args.append(rewritten) new_union = make_union(processed_args) new_union = self.union_rewriter.rewrite_Union(new_union) return new_union
def rewrite(self, typ: type): if isinstance(typ, _Any): return typ arg_classes = getattr(typ, '__args__', None) if not arg_classes: return typ if typing_inspect.is_tuple_type(typ): processed_args = [] for arg_class in arg_classes: processed_args.append(self.rewrite(arg_class)) tup = Tuple[tuple(processed_args)] return self._simplify_big_tuple(tup) if hasattr(typ, '_gorg'): processed_args = [] for arg_class in arg_classes: processed_args.append(self.rewrite(arg_class)) simplified = typ._gorg[tuple(processed_args)] return simplified processed_args = [] for arg_class in arg_classes: rewritten = self.rewrite(arg_class) if not isinstance(rewritten, _Any): processed_args.append(rewritten) if not processed_args: return Any new_union = make_union(processed_args) return new_union
def test_tuple(self): samples = [Tuple, Tuple[str, int], Tuple[Iterable, ...]] nonsamples = [int, tuple, 42, List[int], NamedTuple('N', [('x', int)])] self.sample_test(is_tuple_type, samples, nonsamples) class MyClass(Tuple[str, int]): pass self.assertTrue(is_tuple_type(MyClass))
def simplify_tuples(tup1, tup2, two_element_transformers): for t in [tup1, tup2]: if not typing_inspect.is_tuple_type(t): return NOT_SIMPLIFIED args = t.__args__ if not args: return NOT_SIMPLIFIED transformed = [] merge_used = False for i, (obj1_arg, obj2_arg) in enumerate(zip(tup1.__args__, tup2.__args__)): simplified = False for transformer in [collapse_unions, *two_element_transformers]: transformed_obj = transformer(obj1_arg, obj2_arg) if transformed_obj is not NOT_SIMPLIFIED: transformed.append(transformed_obj) simplified = True break if not simplified: if not merge_used: transformed.append(make_union([obj1_arg, obj2_arg])) merge_used = True else: return NOT_SIMPLIFIED return Tuple[tuple(transformed)]
def _check_annotation(f_type, f_fullname, f_default): if typing_inspect.is_tuple_type(f_type): if f_default is not None: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'default is defined for tuple type') f_default = tuple elif typing_inspect.is_union_type(f_type): for t in typing_inspect.get_args(f_type, evaluate=True): _check_annotation(t, f_fullname, f_default) elif typing_inspect.is_generic_type(f_type): if f_default is not None: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'default is defined for container type ' f'{f_type!r}') ot = typing_inspect.get_origin(f_type) if ot is None: raise RuntimeError( f'cannot find origin of a generic type {f_type}') if ot in (list, List, collections.abc.Sequence): f_default = list elif ot in (set, Set): f_default = set elif ot in (frozenset, FrozenSet): f_default = frozenset elif ot in (dict, Dict): f_default = dict else: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'{f_type!r} is not supported') elif f_type is not None: if f_type is Any: f_type = object if not isinstance(f_type, type): raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'{f_type!r} is not a type') if typeutils.is_container_type(f_type): if f_default is not None: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'default is defined for container type ' f'{f_type!r}') # Make sure that we can actually construct an empty # version of this type before we decide it is the default. try: f_type() except TypeError: pass else: f_default = f_type return f_default
def test_tuple(self): samples = [Tuple, Tuple[str, int], Tuple[Iterable, ...]] if PY39: samples.append(tuple[int, str]) nonsamples = [int, tuple, 42, List[int], NamedTuple('N', [('x', int)])] self.sample_test(is_tuple_type, samples, nonsamples) if SUBCLASSABLE_TUPLES: class MyClass(Tuple[str, int]): pass self.assertTrue(is_tuple_type(MyClass))
def __call__(self, tp): if inspect.isclass(tp): return self.iter_mro(tp) if ti.is_optional_type(tp): return self.iter_optional(tp) if ti.is_tuple_type(tp): return self.iter_generic(tp) if ti.is_union_type(tp): return self.iter_generic(tp) if ti.is_generic_type(tp): return self.iter_generic(tp)
def decode_generic_tuple(decoder, typ, json_value): if not inspect.is_tuple_type(typ): return Unsupported check_type(list, json_value) items_types = inspect.get_args(typ) if len(items_types) != len(json_value): raise JsonError( f'Expected list of size: {len(items_types)}, found tuple of size: {len(json_value)}, value: {json_value}' ) return tuple( map(lambda item, item_type: decoder.decode(item_type, item), json_value, items_types))
def encode_generic_tuple(encoder, typ, value): if not inspect.is_tuple_type(typ): return Unsupported check_type(tuple, value) items_types = inspect.get_args(typ) if len(items_types) != len(value): raise JsonError( f'Expected tuple of size: {len(items_types)}, found tuple of size: {len(value)}, value: {value}' ) return tuple( map(lambda item, item_type: encoder.encode(item, item_type), value, items_types))
def _typing2st(self, container, param_name, p_annotation, args, default, meta=None): if tp.is_tuple_type(p_annotation): if not has_default(default): default = [None] * len(args) tuple_values = [] with container: cols = st.beta_columns([1, 6] * len(args)) for i, (type_, param_default) in enumerate(zip(args, default)): if i != 0: meta = "AND" p_name = f'{param_name} [{i}]' param_type = type_.__name__ ret = self.basic2st(cols, p_name, param_type, i, param_default, meta) tuple_values.append(ret) return tuple(tuple_values) elif tp.is_union_type(p_annotation): with container: return_value = None # st.write(param_name) # st.write(default) if not has_default(default): default = None for i, type_ in enumerate(args): cols = st.beta_columns([1, 6] * len(args)) if i != 0: meta = "OR" p_name = f'{param_name} [{i}]' if is_typing(type_): nargs = tp.get_args(type_) if not default: default = [None] * len(nargs) ret = self._typing2st( p_name, type_, nargs, default, meta) # Not supporting nested defaults? else: param_type = type_.__name__ ret = self.basic2st(cols, p_name, param_type, i, default, meta) if ret is not None and return_value is None: return_value = ret return return_value
def _simplify_big_tuple(self, typ: Tuple): if not typing_inspect.is_tuple_type(typ): return typ arg_classes = typ.__args__ if not arg_classes: return typ if len(arg_classes) > self.max_members and len(set(arg_classes)) == 1: return Tuple[arg_classes[0], ...] if len(arg_classes) > self.max_members: return Tuple[Any, ...] return typ
def _check_annotation(f_type, f_fullname, f_default): if typing_inspect.is_tuple_type(f_type): if f_default is not None: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'default is defined for tuple type') f_default = tuple elif typing_inspect.is_union_type(f_type): for t in typing_inspect.get_args(f_type, evaluate=True): _check_annotation(t, f_fullname, f_default) elif typing_inspect.is_generic_type(f_type): if f_default is not None: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'default is defined for container type ' f'{f_type!r}') ot = typing_inspect.get_origin(f_type) if ot is None: raise RuntimeError( f'cannot find origin of a generic type {f_type}') if ot in (list, typing.List): f_default = list elif ot in (set, typing.Set): f_default = set elif ot in (frozenset, typing.FrozenSet): f_default = frozenset elif ot in (dict, typing.Dict): f_default = dict else: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'{f_type!r} is not supported') elif f_type is not None: if not isinstance(f_type, type): raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'{f_type!r} is not a type') if typeutils.is_container_type(f_type): if f_default is not None: raise RuntimeError(f'invalid type annotation on {f_fullname}: ' f'default is defined for container type ' f'{f_type!r}') f_default = f_type return f_default
def _check_type_real(type_, value, raise_error): if type_ is None: return if typing_inspect.is_union_type(type_): for t in typing_inspect.get_args(type_, evaluate=True): try: _check_type(t, value, raise_error) except TypeError: pass else: break else: raise_error(str(type_), value) elif typing_inspect.is_tuple_type(type_): _check_tuple_type(type_, value, raise_error, tuple) elif typing_inspect.is_generic_type(type_): ot = typing_inspect.get_origin(type_) if ot in (list, List, collections.abc.Sequence): _check_container_type(type_, value, raise_error, list) elif ot in (set, Set): _check_container_type(type_, value, raise_error, set) elif ot in (frozenset, FrozenSet): _check_container_type(type_, value, raise_error, frozenset) elif ot in (dict, Dict): _check_mapping_type(type_, value, raise_error, dict) elif ot is not None: raise TypeError(f'unsupported typing type: {type_!r}') elif type_ is not Any: if value is not None and not isinstance(value, type_): raise_error(type_.__name__, value)
def get(cls, type_or_hint, *, is_argument: bool = False) -> "TypeChecker": # This ensures the validity of the type passed (see typing documentation for info) type_or_hint = is_valid_type(type_or_hint, "Invalid type.", is_argument) if type_or_hint is Any: return AnyTypeChecker() if is_type(type_or_hint): return TypeTypeChecker.make(type_or_hint, is_argument) if is_literal_type(type_or_hint): return LiteralTypeChecker.make(type_or_hint, is_argument) if is_generic_type(type_or_hint): origin = get_origin(type_or_hint) if issubclass(origin, MappingCol): return MappingTypeChecker.make(type_or_hint, is_argument) if issubclass(origin, Collection): return CollectionTypeChecker.make(type_or_hint, is_argument) # CONSIDER: how to cater for exhaustible generators? if issubclass(origin, Iterable): raise NotImplementedError( "No type-checker is setup for iterables that exhaust.") return GenericTypeChecker.make(type_or_hint, is_argument) if is_tuple_type(type_or_hint): return TupleTypeChecker.make(type_or_hint, is_argument) if is_callable_type(type_or_hint): return CallableTypeChecker.make(type_or_hint, is_argument) if isclass(type_or_hint): if is_typed_dict(type_or_hint): return TypedDictChecker.make(type_or_hint, is_argument) return ConcreteTypeChecker.make(type_or_hint, is_argument) if is_union_type(type_or_hint): return UnionTypeChecker.make(type_or_hint, is_argument) if is_typevar(type_or_hint): bound_type = get_bound(type_or_hint) if bound_type: return cls.get(bound_type) constraints = get_constraints(type_or_hint) if constraints: union_type_checkers = tuple( cls.get(type_) for type_ in constraints) return UnionTypeChecker(Union.__getitem__(constraints), union_type_checkers) else: return AnyTypeChecker() if is_new_type(type_or_hint): super_type = getattr(type_or_hint, "__supertype__", None) if super_type is None: raise TypeError( f"No supertype for NewType: {type_or_hint}. This is not allowed." ) return cls.get(super_type) if is_forward_ref(type_or_hint): return ForwardTypeChecker.make(type_or_hint, is_argument=is_argument) if is_classvar(type_or_hint): var_type = get_args(type_or_hint, evaluate=True)[0] return cls.get(var_type) raise NotImplementedError( f"No {TypeChecker.__qualname__} is available for type or hint: '{type_or_hint}'" )
def is_typing(type_): return (tp.is_union_type(type_) or tp.is_tuple_type(type_))
def object_actions(type_def: Type[Generic], tree: AST) -> Dict[str, ObjectAction]: ret: Dict[str, ObjectAction] = {} # ...inspect.ismethod does not work on un-initialized classes for method_name, method_def in iterate_over_actions(type_def): meta: ActionMetadata = method_def.__action__ # type: ignore data = ObjectAction(name=method_name, meta=meta) if method_name in type_def.CANCEL_MAPPING: meta.cancellable = True try: if not method_def.__doc__: doc = {} else: doc = parse_docstring(method_def.__doc__) doc_short = doc["short_description"] if doc_short: data.description = doc_short signature = inspect.signature(method_def) # sig.parameters is OrderedDict try: method_tree = find_function(method_name, tree) except SourceException: # function is probably defined in predecessor, will be added later continue hints = get_type_hints(method_def) # standard (unordered) dict if "an" not in signature.parameters.keys(): raise IgnoreActionException("Action is missing 'an' parameter.") try: if hints["an"] != Optional[str]: raise IgnoreActionException("Parameter 'an' has invalid type annotation.") except KeyError: raise IgnoreActionException("Parameter 'an' is missing type annotation.") parameter_names_without_self = list(signature.parameters.keys())[1:] if not parameter_names_without_self or parameter_names_without_self[-1] != "an": raise IgnoreActionException("The 'an' parameter have to be the last one.") # handle return try: return_ttype = hints["return"] except KeyError: raise IgnoreActionException("Action is missing return type annotation.") # ...just ignore NoneType for returns if return_ttype != type(None): # noqa: E721 if typing_inspect.is_tuple_type(return_ttype): for arg in typing_inspect.get_args(return_ttype): resolved_param = plugin_from_type(arg) if resolved_param is None: raise IgnoreActionException("None in return tuple is not supported.") data.returns.append(resolved_param.type_name()) else: # TODO resolving needed for e.g. enums - add possible values to action metadata somewhere? data.returns = [plugin_from_type(return_ttype).type_name()] for name in parameter_names_without_self[:-1]: # omit also an try: ttype = hints[name] except KeyError: raise IgnoreActionException(f"Parameter {name} is missing type annotation.") param_type = plugin_from_type(ttype) assert param_type is not None args = ParameterMeta(name=name, type=param_type.type_name()) try: param_type.meta(args, method_def, method_tree) except ParameterPluginException as e: raise IgnoreActionException(e) from e if name in type_def.DYNAMIC_PARAMS: args.dynamic_value = True dvp = type_def.DYNAMIC_PARAMS[name][1] if dvp: args.dynamic_value_parents = dvp def_val = signature.parameters[name].default if def_val is not inspect.Parameter.empty: args.default_value = param_type.value_to_json(def_val) try: args.description = doc["params"][name].strip() except KeyError: pass data.parameters.append(args) except Arcor2Exception as e: data.disabled = True data.problem = str(e) glob.logger.warn(f"Disabling action {method_name} of {type_def.__name__}. {str(e)}") ret[data.name] = data return ret
def _populate_parser(func, parser, parsers, short, strict_kwonly): sig = _public_signature(func) doc = _parse_function_docstring(func) hints = typing.get_type_hints(func) or {} # Py2 backport returns None. parser.description = doc.text types = dict((name, _get_type(func, name, doc, hints)) for name, param in sig.parameters.items()) positionals = set(name for name, param in sig.parameters.items() if ((param.default is param.empty or strict_kwonly) and not types[name].container and param.kind != param.KEYWORD_ONLY)) if short is None: count_initials = Counter(name[0] for name in sig.parameters if name not in positionals) short = dict( (name.replace('_', '-'), name[0]) for name in sig.parameters if name not in positionals and count_initials[name[0]] == 1) for name, param in sig.parameters.items(): kwargs = {} if name in doc.params: help_ = doc.params[name].text if help_ is not None: kwargs['help'] = help_.replace('%', '%%') type_ = types[name] if param.kind == param.VAR_KEYWORD: raise ValueError('**kwargs not supported') hasdefault = param.default is not param.empty default = param.default if hasdefault else SUPPRESS required = not hasdefault and param.kind != param.VAR_POSITIONAL positional = name in positionals if type_.type == bool and not positional and not type_.container: # Special case: just add parameterless --name and --no-name flags. if default == SUPPRESS: # Work around lack of "required non-exclusive group" in # argparse by checking for that case ourselves. default = _SUPPRESS_BOOL_DEFAULT _add_argument(parser, name, short, action='store_true', default=default, # Add help if available. **kwargs) _add_argument(parser, 'no-' + name, short, action='store_false', dest=name) continue if positional: kwargs['_positional'] = True if param.default is not param.empty: kwargs['nargs'] = '?' kwargs['default'] = default if param.kind == param.VAR_POSITIONAL: kwargs['nargs'] = '*' # This is purely to override the displayed default of None. # Ideally we wouldn't want to show a default at all. kwargs['default'] = [] else: kwargs['required'] = required kwargs['default'] = default if type_.container: assert type_.container == list kwargs['nargs'] = '*' if param.kind == param.VAR_POSITIONAL: kwargs['action'] = 'append' kwargs['default'] = [] make_tuple = member_types = None if ti.is_tuple_type(type_.type): make_tuple = tuple member_types = ti.get_args(type_.type) kwargs['nargs'] = len(member_types) kwargs['action'] = _make_store_tuple_action_class( tuple, member_types, parsers) elif (inspect.isclass(type_.type) and issubclass(type_.type, tuple) and hasattr(type_.type, '_fields') and hasattr(type_.type, '_field_types')): # Before Py3.6, `_field_types` does not preserve order, so retrieve # the order from `_fields`. member_types = tuple(type_.type._field_types[field] for field in type_.type._fields) kwargs['nargs'] = len(member_types) kwargs['action'] = _make_store_tuple_action_class( lambda args, type_=type_: type_.type(*args), member_types, parsers) if not positional: # http://bugs.python.org/issue14074 kwargs['metavar'] = type_.type._fields elif inspect.isclass(type_.type) and issubclass(type_.type, Enum): # Want these to behave like argparse choices. kwargs['choices'] = _ValueOrderedDict( (x.name, x) for x in type_.type) kwargs['type'] = _enum_getter(type_.type) else: kwargs['type'] = _get_parser(type_.type, parsers) _add_argument(parser, name, short, **kwargs)
def _type_from_runtime(val, ctx): if isinstance(val, str): return _eval_forward_ref(val, ctx) elif isinstance(val, tuple): # This happens under some Python versions for types # nested in tuples, e.g. on 3.6: # > typing_inspect.get_args(Union[Set[int], List[str]]) # ((typing.Set, int), (typing.List, str)) origin = val[0] if len(val) == 2: args = (val[1], ) else: args = val[1:] return _value_of_origin_args(origin, args, val, ctx) elif typing_inspect.is_literal_type(val): args = typing_inspect.get_args(val) if len(args) == 0: return KnownValue(args[0]) else: return unite_values(*[KnownValue(arg) for arg in args]) elif typing_inspect.is_union_type(val): args = typing_inspect.get_args(val) return unite_values(*[_type_from_runtime(arg, ctx) for arg in args]) elif typing_inspect.is_tuple_type(val): args = typing_inspect.get_args(val) if not args: return TypedValue(tuple) elif len(args) == 2 and args[1] is Ellipsis: return GenericValue(tuple, [_type_from_runtime(args[0], ctx)]) else: args_vals = [_type_from_runtime(arg, ctx) for arg in args] return SequenceIncompleteValue(tuple, args_vals) elif is_instance_of_typing_name(val, "_TypedDictMeta"): return TypedDictValue({ key: _type_from_runtime(value, ctx) for key, value in val.__annotations__.items() }) elif typing_inspect.is_callable_type(val): return TypedValue(Callable) elif typing_inspect.is_generic_type(val): origin = typing_inspect.get_origin(val) args = typing_inspect.get_args(val) return _value_of_origin_args(origin, args, val, ctx) elif GenericAlias is not None and isinstance(val, GenericAlias): origin = get_origin(val) args = get_args(val) return GenericValue(origin, [_type_from_runtime(arg, ctx) for arg in args]) elif isinstance(val, type): if val is type(None): return KnownValue(None) return TypedValue(val) elif val is None: return KnownValue(None) elif is_typing_name(val, "NoReturn"): return NO_RETURN_VALUE elif val is typing.Any: return UNRESOLVED_VALUE elif hasattr(val, "__supertype__"): if isinstance(val.__supertype__, type): # NewType return NewTypeValue(val) elif typing_inspect.is_tuple_type(val.__supertype__): # TODO figure out how to make NewTypes over tuples work return UNRESOLVED_VALUE else: ctx.show_error("Invalid NewType %s" % (val, )) return UNRESOLVED_VALUE elif typing_inspect.is_typevar(val): # TypeVar; not supported yet return UNRESOLVED_VALUE elif typing_inspect.is_classvar(val): return UNRESOLVED_VALUE elif is_instance_of_typing_name( val, "_ForwardRef") or is_instance_of_typing_name( val, "ForwardRef"): # This has issues because the forward ref may be defined in a different file, in # which case we don't know which names are valid in it. with qcore.override(ctx, "suppress_undefined_name", True): return UNRESOLVED_VALUE elif val is Ellipsis: # valid in Callable[..., ] return UNRESOLVED_VALUE elif is_instance_of_typing_name(val, "_TypeAlias"): # typing.Pattern and Match, which are not normal generic types for some reason return GenericValue(val.impl_type, [_type_from_runtime(val.type_var, ctx)]) else: origin = get_origin(val) if origin is not None: return TypedValue(origin) ctx.show_error("Invalid type annotation %s" % (val, )) return UNRESOLVED_VALUE
def _extract_collection_base_type( collection_object_type, exception_if_none: bool = True, resolve_fwd_refs: bool = True) -> Tuple[Type, Optional[Type]]: """ Utility method to extract the base item type from a collection/iterable item type. Throws * a TypeError if the collection_object_type a Dict with non-string keys. * an AttributeError if the collection_object_type is actually not a collection * a TypeInformationRequiredError if somehow the inner type can't be found from the collection type (either if dict, list, set, tuple were used instead of their typing module equivalents (Dict, List, Set, Tuple), or if the latter were specified without inner content types (as in Dict instead of Dict[str, Foo]) :param collection_object_type: :return: a tuple containing the collection's content type (which may itself be a Tuple in case of a Tuple) and the collection's content key type for dicts (or None) """ contents_item_type = None contents_key_type = None check_var(collection_object_type, var_types=type, var_name='collection_object_type') is_tuple = False if is_tuple_type( collection_object_type ): # Tuple is a special construct, is_generic_type does not work is_tuple = True # --old: hack into typing module # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None: # contents_item_type = collection_object_type.__args__ # --new : using typing_inspect # __args = get_last_args(collection_object_type) # this one works even in typevar+config cases such as t = Tuple[int, Tuple[T, T]][Optional[int]] __args = get_args(collection_object_type, evaluate=True) if len(__args) > 0: contents_item_type = __args elif issubclass(collection_object_type, Mapping): # Dictionary-like if is_generic_type(collection_object_type): # --old: hack into typing module # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None: # contents_key_type, contents_item_type = collection_object_type.__args__ # --new : using typing_inspect # __args = get_last_args(collection_object_type) # this one works even in typevar+config cases such as d = Dict[int, Tuple[T, T]][Optional[int]] __args = get_args(collection_object_type, evaluate=True) if len(__args) > 0: contents_key_type, contents_item_type = __args if not issubclass(contents_key_type, str): raise TypeError( 'Collection object has type Dict, but its PEP484 type hints declare ' 'keys as being of type ' + str(contents_key_type) + ' which is not supported. Only ' 'str keys are supported at the moment, since we use them as item names' ) elif issubclass( collection_object_type, Iterable): # List or Set. Should we rather use Container here ? if is_generic_type(collection_object_type): # --old: hack into typing module # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None: # contents_item_type = collection_object_type.__args__[0] # --new : using typing_inspect # __args = get_last_args(collection_object_type) # this one works even in typevar+config cases such as i = Iterable[Tuple[T, T]][Optional[int]] __args = get_args(collection_object_type, evaluate=True) if len(__args) > 0: contents_item_type, = __args elif issubclass(collection_object_type, dict) or issubclass(collection_object_type, list)\ or issubclass(collection_object_type, tuple) or issubclass(collection_object_type, set): # the error is now handled below with the other under-specified types situations pass else: # Not a collection raise AttributeError( 'Cannot extract collection base type, object type ' + str(collection_object_type) + ' is not a collection') # Finally return if something was found, otherwise tell it try: if contents_item_type is None or contents_item_type is Parameter.empty: # Empty type hints raise TypeInformationRequiredError.create_for_collection_items( collection_object_type, contents_item_type) elif is_tuple: # --- tuple: Iterate on all sub-types resolved = [] for t in contents_item_type: # Check for empty type hints if contents_item_type is None or contents_item_type is Parameter.empty: raise TypeInformationRequiredError.create_for_collection_items( collection_object_type, t) # Resolve any forward references if needed if resolve_fwd_refs: t = resolve_forward_ref(t) resolved.append(t) # Final type hint compliance if not is_valid_pep484_type_hint(t): raise InvalidPEP484TypeHint.create_for_collection_items( collection_object_type, t) if resolve_fwd_refs: contents_item_type = tuple(resolved) else: # --- Not a tuple # resolve any forward references first if resolve_fwd_refs: contents_item_type = resolve_forward_ref(contents_item_type) # check validity then if not is_valid_pep484_type_hint(contents_item_type): # Invalid type hints raise InvalidPEP484TypeHint.create_for_collection_items( collection_object_type, contents_item_type) except TypeInformationRequiredError as e: # only raise it if the flag says it if exception_if_none: raise e.with_traceback(e.__traceback__) return contents_item_type, contents_key_type