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 __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 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 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 __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 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 __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 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, 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 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