def issubclass(sub: Type, super: Union[Type, Tuple[Type, ...]]) -> bool: """ Alternative issubclass implementation that interpretes instances of NewType for the first argument as their super type. """ if typing_inspect.is_new_type(sub): return issubclass(sub.__supertype__, super) return builtins.issubclass(sub, super)
def _get_object_path_function(obj: types.FunctionType) -> Optional[str]: if typing_inspect.is_new_type(obj): return None return '.'.join(( obj.__module__, obj.__qualname__, ))
def _repr(val: t.Any) -> str: assert val is not None if types.is_none_type(val): return 'NoneType' elif ti.is_literal_type(val): return str(val) elif ti.is_new_type(val): nested_type = val.__supertype__ return f'{_qualified_name(val)}[{get_repr(nested_type)}]' elif ti.is_typevar(val): tv_constraints = ti.get_constraints(val) tv_bound = ti.get_bound(val) if tv_constraints: constraints_repr = (get_repr(tt) for tt in tv_constraints) return f'typing.TypeVar(?, {", ".join(constraints_repr)})' elif tv_bound: return get_repr(tv_bound) else: return 'typing.Any' elif ti.is_optional_type(val): optional_args = ti.get_args(val, True)[:-1] nested_union = len(optional_args) > 1 optional_reprs = (get_repr(tt) for tt in optional_args) if nested_union: return f'typing.Optional[typing.Union[{", ".join(optional_reprs)}]]' else: return f'typing.Optional[{", ".join(optional_reprs)}]' elif ti.is_union_type(val): union_reprs = (get_repr(tt) for tt in ti.get_args(val, True)) return f'typing.Union[{", ".join(union_reprs)}]' elif ti.is_generic_type(val): attr_name = val._name generic_reprs = (get_repr(tt) for tt in ti.get_args(val, evaluate=True)) return f'typing.{attr_name}[{", ".join(generic_reprs)}]' else: val_name = _qualified_name(val) maybe_td_entries = getattr(val, '__annotations__', {}).copy() if maybe_td_entries: # we are dealing with typed dict # that's quite lovely td_keys = sorted(maybe_td_entries.keys()) internal_members_repr = ', '.join( '{key}: {type}'.format(key=k, type=get_repr(maybe_td_entries.get(k))) for k in td_keys ) return f'{val_name}{{{internal_members_repr}}}' elif 'TypedDict' == getattr(val, '__name__', ''): return 'typing_extensions.TypedDict' else: return val_name
def _maybe_node_for_newtype( typ: Union[NewType, Any], overrides: OverridesT, memo: MemoType, forward_refs: ForwardRefs ) -> Tuple[Optional[schema.nodes.SchemaNode], MemoType, ForwardRefs]: """ newtypes do not change the underlying runtime data type that is used in calls like isinstance(), therefore it's just enough for us to find a schema node of the underlying type """ rv = None if insp.is_new_type(typ): return decide_node_type(typ.__supertype__, overrides, memo, forward_refs) return rv, memo, forward_refs
def _get_object_name_from_function(obj: types.FunctionType) -> str: if typing_inspect.is_new_type(obj): # NewType is a function with __supertype__ attribute assigned to it. supertype = get_object_name(obj.__supertype__) return f'NewType {obj.__name__}({supertype})' # We only print the module in which function is. We do not print the whole # module path. module_name = obj.__module__.split('.')[-1] return '{}.{}'.format( module_name, obj.__qualname__, )
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 _analyze_http_code( operation: oas.OASOperation, rt_http_code: t.Optional[t.Type[t.Any]], ) -> t.Set[exceptions.Error]: if rt_http_code is None: # if there's no http_code in return Response # this is permitted only if there's single response defined in # OAS responses. User needs to set it otherwise how can we tell if # everything is correct if len(operation.responses) != 1: logger.opt(lazy=True).error( 'Operation {id} handler skips return.http_code but it is impossible ' ' with {count_of_ops} responses due to ambiguity.', id=lambda: operation.id, count_of_ops=lambda: len(operation.responses), ) return { exceptions.Error( param_name='return.http_code', reason='missing', ) } return set() elif ti.is_literal_type(rt_http_code): # this is acceptable. Literals hold particular values inside of them # if user wants to have it that way -> go ahead. # axion however will not validate a specific values in Literal. # this is by design and due to: # - error responses that axion implements via exceptions literal_types = types.literal_types(rt_http_code) if not all( issubclass(lmt, model.HTTP_CODE_TYPE) for lmt in literal_types): return { exceptions.Error( param_name='return.http_code', reason=exceptions.CustomReason( f'expected {repr(te.Literal)}[int]'), ), } return set() elif ti.is_new_type(rt_http_code): # not quite sure why user would like to alias that # but it is not a problem for axion as long `NewType` embedded type # is fine return _analyze_http_code(operation, rt_http_code.__supertype__) elif issubclass(rt_http_code, bool): # yeah, Python rocks -> bool is subclass of an int # not quite sure wh that happens, perhaps someone sometime # will answer that question return { exceptions.Error( param_name='return.http_code', reason=exceptions.IncorrectTypeReason( expected=[model.HTTP_CODE_TYPE], actual=bool, ), ), } else: try: assert issubclass(rt_http_code, model.HTTP_CODE_TYPE) return set() except (AssertionError, TypeError): ... return { exceptions.Error( param_name='return.http_code', reason=exceptions.IncorrectTypeReason( actual=rt_http_code, expected=[ type(None), model.HTTP_CODE_TYPE, t.NewType('HttpCode', model.HTTP_CODE_TYPE), te.Literal, ], ), ), }
def is_numpy_1_darray(type_: type) -> bool: return typing_inspect.is_new_type( type_) and type_.__name__ == "Numpy1DArray"
def _validate_type_arg(self, arg: str, arg_type: Type, *, strict: bool = True, allow_none_type: bool = False, in_url: bool = False) -> None: """Validate the given type arg recursively :param arg: The name of the argument :param arg_type: The annotated type fo the argument :param strict: If true, does not allow `Any` :param allow_none_type: If true, allow `None` as the type for this argument :param in_url: This argument is passed in the URL """ if typing_inspect.is_new_type(arg_type): return self._validate_type_arg( arg, arg_type.__supertype__, strict=strict, allow_none_type=allow_none_type, in_url=in_url, ) if arg_type is Any: if strict: raise InvalidMethodDefinition( f"Invalid type for argument {arg}: Any type is not allowed in strict mode" ) return if typing_inspect.is_union_type(arg_type): # Make sure there is only one list and one dict in the union, otherwise we cannot process the arguments cnt: Dict[str, int] = defaultdict(lambda: 0) for sub_arg in typing_inspect.get_args(arg_type, evaluate=True): self._validate_type_arg(arg, sub_arg, strict=strict, allow_none_type=allow_none_type, in_url=in_url) if typing_inspect.is_generic_type(sub_arg): # there is a difference between python 3.6 and >=3.7 if hasattr(sub_arg, "__name__"): cnt[sub_arg.__name__] += 1 else: cnt[sub_arg._name] += 1 for name, n in cnt.items(): if n > 1: raise InvalidMethodDefinition( f"Union of argument {arg} can contain only one generic {name}" ) elif typing_inspect.is_generic_type(arg_type): orig = typing_inspect.get_origin(arg_type) assert orig is not None # Make mypy happy if not types.issubclass(orig, (list, dict)): raise InvalidMethodDefinition( f"Type {arg_type} of argument {arg} can only be generic List or Dict" ) args = typing_inspect.get_args(arg_type, evaluate=True) if len(args) == 0: raise InvalidMethodDefinition( f"Type {arg_type} of argument {arg} must be have a subtype plain List or Dict is not allowed." ) elif len(args) == 1: # A generic list unsubscripted_arg = typing_inspect.get_origin( args[0]) if typing_inspect.get_origin(args[0]) else args[0] assert unsubscripted_arg is not None # Make mypy happy if in_url and (types.issubclass(unsubscripted_arg, dict) or types.issubclass(unsubscripted_arg, list)): raise InvalidMethodDefinition( f"Type {arg_type} of argument {arg} is not allowed for {self.operation}, " f"lists of dictionaries and lists of lists are not supported for GET requests" ) self._validate_type_arg(arg, args[0], strict=strict, allow_none_type=allow_none_type, in_url=in_url) elif len(args) == 2: # Generic Dict if not types.issubclass(args[0], str): raise InvalidMethodDefinition( f"Type {arg_type} of argument {arg} must be a Dict with str keys and not {args[0].__name__}" ) unsubscripted_dict_value_arg = (typing_inspect.get_origin( args[1]) if typing_inspect.get_origin(args[1]) else args[1]) assert unsubscripted_dict_value_arg is not None # Make mypy happy if in_url and (typing_inspect.is_union_type(args[1]) or types.issubclass( unsubscripted_dict_value_arg, dict)): raise InvalidMethodDefinition( f"Type {arg_type} of argument {arg} is not allowed for {self.operation}, " f"nested dictionaries and union types for dictionary values are not supported for GET requests" ) self._validate_type_arg(arg, args[1], strict=strict, allow_none_type=True, in_url=in_url) elif len(args) > 2: raise InvalidMethodDefinition( f"Failed to validate type {arg_type} of argument {arg}.") elif not in_url and types.issubclass(arg_type, VALID_SIMPLE_ARG_TYPES): pass elif in_url and types.issubclass(arg_type, VALID_URL_ARG_TYPES): pass elif allow_none_type and types.issubclass(arg_type, type(None)): # A check for optional arguments pass else: valid_types = ", ".join( [x.__name__ for x in VALID_SIMPLE_ARG_TYPES]) raise InvalidMethodDefinition( f"Type {arg_type.__name__} of argument {arg} must be a either {valid_types} or a List of these types or a " "Dict with str keys and values of these types.")