def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if hasattr(annotation, '__origin__') and hasattr(annotation, '__args__') and annotation.__origin__ in InterfaceMapping: (protocol,) = InterfaceMapping[annotation.__origin__] bindings = protocol.__parameters__ # args of Generic super class origin = annotation.__origin__ inner_checkers = [] for param in annotation.__args__: ch = ctx.find_checker(param) if ch is None: raise UntypyAttributeError(f"Could not resolve annotation {param} inside of {annotation}") inner_checkers.append(ch) if len(inner_checkers) != len(bindings): raise UntypyAttributeError(f"Expected {len(bindings)} type arguments inside of {annotation}") name = f"{origin.__name__}[" + (', '.join(map(lambda t: t.describe(), inner_checkers))) + "]" bindings = dict(zip(bindings, annotation.__args__)) ctx.with_typevars(bindings) template = WrappedType(protocol, ctx.with_typevars(bindings), name=name, implementation_template=origin, declared=ctx.declared_location()) return InterfaceChecker(origin, template, name) else: return None
def find_signature(member, ctx: CreationContext): signature = inspect.signature(member) checkers = {} for key in signature.parameters: if key == 'self': checkers[key] = SelfChecker() else: param = signature.parameters[key] if param.annotation is inspect.Parameter.empty: checkers[key] = AnyChecker() continue checker = ctx.find_checker(param.annotation) if checker is None: checkers[key] = AnyChecker() continue checkers[key] = checker if signature.return_annotation is inspect.Parameter.empty: checkers['return'] = AnyChecker() else: return_checker = ctx.find_checker(signature.return_annotation) if return_checker is None: raise ctx.wrap( UntypyAttributeError( f"\n\tUnsupported Type Annotation: {signature.return_annotation}\n" f"for Return Value of function {member.__name__}\n")) checkers['return'] = return_checker return signature, checkers
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if type(annotation) in [ GeneratorTypeA, GeneratorTypeB ] and annotation.__origin__ == collections.abc.Generator: if len(annotation.__args__) != 3: raise ctx.wrap( UntypyAttributeError( f"Expected 3 type arguments for Generator.")) (yield_checker, send_checker, return_checker) = list( map(lambda a: ctx.find_checker(a), annotation.__args__)) if yield_checker is None: raise ctx.wrap( UntypyAttributeError( f"The Yield Annotation of the Generator could not be resolved." )) if send_checker is None: raise ctx.wrap( UntypyAttributeError( f"The Send Annotation of the Generator could not be resolved." )) if return_checker is None: raise ctx.wrap( UntypyAttributeError( f"The Return Annotation of the Generator could not be resolved." )) return GeneratorChecker(yield_checker, send_checker, return_checker) else: return None
def analyse(item, ctx: CreationContext, e) -> UntypyAttributeError: org = WrappedFunction.find_original(item) source = inspect.getsource(item) fn_ast = ast.parse(source) for node in map_str_to_ast(fn_ast.body[0].args, fn_ast.body[0].returns): for rule in RULES: rule_result = rule(node) if rule_result: # Got a Match (n, message) = rule_result display = DisplayMatrix(source) display.write((n.col_offset - 1, n.lineno), " " + "^" * (n.end_col_offset - n.col_offset) + " - " + message) return ctx.wrap( UntypyAttributeError( f"Type annotation of function '{qualname(org)}' could not be resolved:\n" f"{e}\n" f"\n{display}")) return ctx.wrap( UntypyAttributeError( f"Type annotation of function '{qualname(org)}' could not be resolved:\n" f"{e}\n"))
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if annotation in InterfaceMapping: # Assume Any if no parameters are given (protocol, ) = InterfaceMapping[annotation] bindings = protocol.__parameters__ if len(bindings) == 0: raise AssertionError( f"This is a BUG. {annotation} has no generic params.") # handle Python inconsistency if hasattr(annotation, '__class_getitem__'): return self.create_from( annotation.__class_getitem__(*([Any] * len(bindings))), ctx) elif hasattr(annotation, '__getitem__'): return self.create_from( annotation.__getitem__(*([Any] * len(bindings))), ctx) elif hasattr(annotation, '__origin__') and hasattr( annotation, '__args__') and annotation.__origin__ in InterfaceMapping: (protocol, ) = InterfaceMapping[annotation.__origin__] bindings = protocol.__parameters__ # args of Generic super class origin = annotation.__origin__ inner_checkers = [] for param in annotation.__args__: ch = ctx.find_checker(param) if ch is None: raise UntypyAttributeError( f"Could not resolve annotation {param} inside of {annotation}" ) inner_checkers.append(ch) if len(inner_checkers) != len(bindings): raise UntypyAttributeError( f"Expected {len(bindings)} type arguments inside of {annotation}" ) name = f"{origin.__name__}[" + (', '.join( map(lambda t: t.describe(), inner_checkers))) + "]" bindings = dict(zip(bindings, annotation.__args__)) ctx = ctx.with_typevars(bindings) if type(origin) == type: template = WrappedType(protocol, ctx.with_typevars(bindings), name=name, implementation_template=origin, declared=ctx.declared_location()) return InterfaceChecker(origin, template, name) else: # type(origin) == collection.abc.ABCMeta return ProtocolChecker(protocol, ctx, altname=name) else: return None
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if type(annotation) in [IteratorTypeA, IteratorTypeB] and annotation.__origin__ == collections.abc.Iterator: if len(annotation.__args__) != 1: raise ctx.wrap(UntypyAttributeError(f"Expected 1 type arguments for iterator.")) inner = ctx.find_checker(annotation.__args__[0]) if inner is None: raise ctx.wrap(UntypyAttributeError(f"The inner type of the iterator could not be resolved.")) return IteratorChecker(inner) else: return None
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if (type(annotation) is GenericAlias and annotation.__origin__ == list) or (type(annotation) is type(List[int]) and annotation.__origin__ == list): assert len(annotation.__args__) == 1 inner = ctx.find_checker(annotation.__args__[0]) if inner is None: return None return ListChecker(inner, ctx.declared_location()) else: return None
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if type(annotation) is str: eval_args = [annotation, globals()] local = ctx.eval_context() if local is not None: eval_args.append(local) def resolver(eval_args): return eval(*eval_args) annotation = get_type_hints(eval_args, ctx, resolver=resolver) return ctx.find_checker(annotation)
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if hasattr(annotation, '__metadata__') and hasattr( annotation, '__origin__'): inner = ctx.find_checker(annotation.__origin__) if inner is None: raise ctx.wrap( UntypyAttributeError(f"Could not resolve annotation " f"'{repr(annotation.__origin__)}' " f"inside of '{annotation}'.")) return AnnotatedChecker(annotation, inner, annotation.__metadata__, ctx) else: return None
def __init__(self, annotated, inner: TypeChecker, metadata: Iterator, ctx: CreationContext): self.annotated = annotated self.inner = inner meta = [] info = [] for m in metadata: if callable(m): meta.append(AnnotatedCheckerCallable(self, m)) elif isinstance(m, str): info.append(m) elif hasattr(m, '__contains__'): meta.append(AnnotatedCheckerContainer(self, m)) else: raise ctx.wrap( UntypyAttributeError( f"Unsupported metadata '{repr(m)}' in '{repr(self.annotated)}'.\n" f"Only callables or objects providing __contains__ are allowed." )) self.meta = meta self.info = info if len(info) == 1: self.name = info[0] self.info = [] else: self.name = None
def __init__(self, annotation: type, ctx: CreationContext): (proto, typevars) = _find_bound_typevars(annotation) ctx = ctx.with_typevars(typevars) members = get_proto_members(proto, ctx) self.proto = proto self.members = members self.typevars = typevars self.wrapper_types = dict()
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: # TODO: Support other typevar features if type(annotation) is TypeVar: (found, replacement_annotation) = ctx.resolve_typevar(annotation) if found: inner = ctx.find_checker(replacement_annotation) if inner is not None: return BoundTypeVar(inner, annotation) else: return None else: return UnboundTypeVar(annotation) elif hasattr(annotation, '__args__') and hasattr(annotation.__origin__, '__mro__') and typing.Generic in annotation.__origin__.__mro__: return GenericProtocolChecker(annotation, ctx) else: return None
def get_type_hints(item, ctx: CreationContext, resolver=_default_resolver): try: # SEE: https://www.python.org/dev/peps/pep-0563/#id7 return resolver(item) except NameError as ne: org = WrappedFunction.find_original(item) if inspect.isclass(org): raise ctx.wrap( UntypyNameError( f"{ne}.\nType annotation inside of class '{qualname(org)}' could not be resolved." )) else: raise ctx.wrap( UntypyNameError( f"{ne}.\nType annotation of function '{qualname(org)}' could not be resolved." )) except Exception as e: # Try to find better cause in analyse raise analyse(item, ctx, e)
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if (type(annotation) is TupleType or type(annotation) is TupleTypeB) and annotation.__origin__ == tuple: args = annotation.__args__ if len(args) == 2 and args[1] == Ellipsis: checker = ctx.find_checker(args[0]) if checker is None: return None else: return VariadicTupleChecker(checker) inner = [] for arg in args: checker = ctx.find_checker(arg) if checker is None: return None else: inner.append(checker) return TupleChecker(inner) else: return None
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if type(annotation) is UnionType and len( annotation.__args__ ) == 2 and annotation.__args__[1] is type(None): checker = ctx.find_checker(annotation.__args__[0]) if checker is None: return None else: return OptionalChecker(checker) else: return None
def __init__(self, annotation: type, ctx: CreationContext, *, altname: Optional[str] = None): (proto, typevars) = _find_bound_typevars(annotation) self.ctx = ctx.with_typevars(typevars) self.proto = proto self._members = None self.typevars = typevars self.wrapper_types = dict() self.altname = altname
def WrappedGenericAlias(alias, ctx: CreationContext): typevars = dict(zip(alias.__origin__.__parameters__, alias.__args__)) for key, value in typevars.items(): _checktypevar(key, value) ctx = ctx.with_typevars(typevars) # This WrappedType must also be a generic alias. # So it can be as Typennotation tname = [] for t in typevars: a = ctx.find_checker(typevars[t]) if a is None: tname.append(str(typevars[t])) else: tname.append(a.describe()) wt = WrappedType(alias.__origin__, ctx, name=f"{alias.__origin__.__name__}[" + (', '.join(tname)) + "]") wt.__origin__ = alias.__origin__ wt.__args__ = alias.__args__ return wt
def __init__(self, annotation: Union[CallableTypeOne, CallableTypeTwo], ctx: CreationContext): arguments_ty = annotation.__args__[:-1] return_ty = annotation.__args__[-1] return_checker = ctx.find_checker(return_ty) if return_checker is None: raise ctx.wrap( UntypyAttributeError( f"Return Type Annotation not found. {return_ty}")) argument_checker = [] for arg in arguments_ty: checker = ctx.find_checker(arg) if checker is None: raise ctx.wrap( UntypyAttributeError( f"Argument Type Annotation not found. {arg}")) argument_checker.append(checker) self.return_checker = return_checker self.argument_checker = argument_checker
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if type(annotation) is UnionType: inner = [] for arg in annotation.__args__: checker = ctx.find_checker(arg) if checker is None: return None else: inner.append(checker) return UnionChecker(inner, ctx) else: return None
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: if (type(annotation) is TupleType or type(annotation) is TupleTypeB ) and annotation.__origin__ == tuple: inner = [] for arg in annotation.__args__: checker = ctx.find_checker(arg) if checker is None: return None else: inner.append(checker) return TupleChecker(inner) else: return None
def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]: t = type(annotation) if (t in [SequenceTypeA, SequenceTypeB] and annotation.__origin__ in [Sequence, ABCSequence]) or \ annotation in [Sequence, ABCSequence]: # no args version try: args = annotation.__args__ except AttributeError: args = [] inner = [] elemChecker = None if len(args) == 0: sf = SimpleFactory() inner = [sf.create_from(list, ctx), sf.create_from(tuple, ctx), sf.create_from(str, ctx)] elif len(args) == 1: elemChecker = ctx.find_checker(args[0]) if elemChecker is None: return None inner = [ListChecker(elemChecker, ctx.declared_location()), VariadicTupleChecker(elemChecker)] return SequenceChecker(inner, ctx, elemChecker) else: return None
def __init__(self, inner: Callable, ctx: CreationContext): self.inner = inner self.signature = inspect.signature(inner) # SEE: https://www.python.org/dev/peps/pep-0563/#id7 annotations = typing.get_type_hints(inner, include_extras=True) checkers = {} checked_keys = list(self.signature.parameters) # Remove self and cls from checking if len(checked_keys) > 0 and checked_keys[0] in self.special_args: checkers[checked_keys[0]] = SelfChecker() checked_keys = checked_keys[1:] for key in checked_keys: if self.signature.parameters[ key].annotation is inspect.Parameter.empty: raise ctx.wrap( UntypyAttributeError( f"Missing annotation for argument '{key}' of function {inner.__name__}\n" "Partial annotation are not supported.")) annotation = annotations[key] checker = ctx.find_checker(annotation) if checker is None: raise ctx.wrap( UntypyAttributeError( f"\n\tUnsupported type annotation: {annotation}\n" f"\tin argument '{key}'")) else: checkers[key] = checker if inner.__name__ in self.method_name_ignore_return: checkers['return'] = SelfChecker() else: if not 'return' in annotations: raise ctx.wrap( UntypyAttributeError( f"Missing annotation for return value of function {inner.__name__}\n" "Partial annotation are not supported. Use 'None' or 'NoReturn'" "for specifying no return value.")) annotation = annotations['return'] return_checker = ctx.find_checker(annotation) if return_checker is None: raise ctx.wrap( UntypyAttributeError( f"\n\tUnsupported type annotation: {annotation}\n" f"\tin return")) checkers['return'] = return_checker self.fc = None if hasattr(self.inner, "__fc"): self.fc = getattr(self.inner, "__fc") self.checkers = checkers
def __init__(self, inner: list[TypeChecker], ctx: CreationContext): # especially Protocols must be checked in a specific order. self.inner = sorted(inner, key=lambda t: -t.base_type_priority()) dups = dict() for checker in inner: for base_type in checker.base_type(): if base_type in dups: raise ctx.wrap( UntypyAttributeError( f"{checker.describe()} is in conflict with " f"{dups[base_type].describe()} " f"in {self.describe()}. " f"Types must be distinguishable inside one Union." f"\nNote: Only one Protocol is allowed inside one Union. " f"Classes could implement multiple Protocols by accident." f"\nNote: Multiple Callables or Generics inside one Union are also unsupported." )) else: dups[base_type] = checker
def __init__(self, annotation: type, ctx: CreationContext): self.annotation = annotation self.always_wrap = False # use protocol like wrapping only if there are some signatures if ctx.should_be_inheritance_checked(annotation): if hasattr(annotation, '__patched'): p = ParentProtocolChecker(annotation, ctx) self.parent_checker = p.check_and_wrap else: # annotation is from an wrapped import t = WrappedType(annotation, ctx) def wrap(i, ctx): instance = t.__new__(t) instance._WrappedClassFunction__inner = i instance._WrappedClassFunction__return_ctx = None return instance # TODO: Use only on import_wrapped module # self.always_wrap = True self.parent_checker = wrap else: self.parent_checker = None
def get_proto_members( proto: type, ctx: CreationContext ) -> Dict[str, Tuple[inspect.Signature, dict[str, TypeChecker], FunctionCondition]]: blacklist = [ '__init__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__getattribute__', '__getattr__', '__init_subclass__', '__new__', '__setattr__', '__subclasshook__', '__weakref__', '__abstractmethods__', '__class_getitem__' ] member_dict = {} for [name, member] in inspect.getmembers(proto): if name in blacklist: continue if inspect.isfunction(member): member = WrappedFunction.find_original(member) signature = inspect.signature(member) annotations = typing.get_type_hints(member, include_extras=True) checkers = {} for key in signature.parameters: if key == 'self': checkers[key] = SelfChecker() else: param = signature.parameters[key] if param.annotation is inspect.Parameter.empty: raise ctx.wrap( UntypyAttributeError( f"Missing annotation for argument '{key}' of function {member.__name__} " f"in protocol {proto.__name__}\n")) checker = ctx.find_checker(annotations[key]) if checker is None: raise ctx.wrap( UntypyAttributeError( f"\n\tUnsupported type annotation: {param.annotation}\n" f"for argument '{key}' of function {member.__name__} " f"in protocol {proto.__name__}.\n")) checkers[key] = checker if signature.return_annotation is inspect.Parameter.empty: raise ctx.wrap( UntypyAttributeError( f"Missing annotation for return value of function {member.__name__} " f"in protocol {proto.__name__}. Use 'None' if there is no return value.\n" )) return_annotation = annotations['return'] if return_annotation is proto: # Self as Return Type would led to endless recursion return_checker = SimpleInstanceOfChecker(proto, None) else: return_checker = ctx.find_checker(return_annotation) if return_checker is None: raise ctx.wrap( UntypyAttributeError( f"\n\tUnsupported type annotation: {signature.return_annotation}\n" f"for return value of function {member.__name__} " f"in protocol-like {proto.__name__}.\n")) fc = None if hasattr(member, '__fc'): fc = getattr(member, '__fc') checkers['return'] = return_checker member_dict[name] = (signature, checkers, fc) return member_dict