def test_callable(self): samples = [Callable, Callable[..., int], Callable[[int, int], Iterable[str]]] nonsamples = [int, type, 42, [], List[int], Union[callable, Callable[..., int]]] self.sample_test(is_callable_type, samples, nonsamples) class MyClass(Callable[[int], int]): pass self.assertTrue(is_callable_type(MyClass))
def test_callable(self): samples = [Callable, Callable[..., int], Callable[[int, int], Iterable[str]]] nonsamples = [int, type, 42, [], List[int]] if UNION_SUPPORTS_BUILTIN_CALLABLE: nonsamples.append(Union[callable, Callable[..., int]]) self.sample_test(is_callable_type, samples, nonsamples) if SUBCLASSABLE_CALLABLES: class MyClass(Callable[[int], int]): pass self.assertTrue(is_callable_type(MyClass))
def _get_checker(self, expect_tp): if expect_tp is t.Any: return lambda v: True # about type determination of typing objs # see: https://github.com/python/typing/issues/528 elif ti.is_union_type(expect_tp): return lambda v: isinstance(v, expect_tp.__args__) elif ti.is_callable_type(expect_tp): return lambda v: callable(v) elif isinstance(expect_tp, str): return self._get_str_checker(expect_tp) else: return lambda v: isinstance(v, expect_tp)
def _eval_args(args): """Internal helper for get_args.""" res = [] for arg in args: if not isinstance(arg, tuple): res.append(arg) elif is_callable_type(arg[0]): callable_args = _eval_args(arg[1:]) if len(arg) == 2: res.append(Callable[[], callable_args[0]]) elif arg[1] is Ellipsis: res.append(Callable[..., callable_args[1]]) else: res.append(Callable[[*callable_args[:-1]], callable_args[-1]]) else: res.append(type(arg[0]).__getitem__(arg[0], _eval_args(arg[1:]))) return tuple(res)
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_impractical_type(python_type: PythonType) -> bool: # can't write code and pass it around in JSON! return (is_callable_type(python_type) or type(python_type) == enum.EnumMeta)
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