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 _checktypevar(typevar: TypeVar, bound: Any) -> None: # See https://www.python.org/dev/peps/pep-0484/#covariance-and-contravariance if len(typevar.__constraints__) > 0: if typevar.__covariant__ == True: if not issubclass(bound, typevar.__constraints__): raise UntypyAttributeError( f"Violation in TypeVar {typevar}: {bound} must be a covariant of one of type {typevar.__constraints__}." ) elif typevar.__contravariant__ == True: found = False for c in typevar.__constraints__: if issubclass(c, bound): found = True if not found: raise UntypyAttributeError( f"Violation in TypeVar {typevar}: {bound} must be a contravariant of one of type {typevar.__constraints__}." ) else: if not bound in typevar.__constraints__: raise UntypyAttributeError( f"Violation in TypeVar {typevar}: {bound} must be a exactly of one of type {typevar.__constraints__}." f"\nYou may want to use TypeVar(..., bound=...) instead.") elif typevar.__bound__ is not None: if not type(typevar.__bound__) is type: raise UntypyAttributeError( f"Bound {bound} of TypeVar {typevar} must be a type.") if not issubclass(bound, typevar.__bound__): raise UntypyAttributeError( f"Violation in TypeVar {typevar}: {bound} must be a subclass of {typevar.__bound__}." )
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 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 __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 __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 build(self): def wrapper(*args, **kwargs): # first is this fn caller = sys._getframe(1) (args, kwargs, bindings) = self.wrap_arguments(lambda n: ArgumentExecutionContext(self, caller, n), args, kwargs) ret = self.inner(*args, **kwargs) ret = self.wrap_return(ret, bindings, ReturnExecutionContext(self)) return ret if inspect.iscoroutine(self.inner): raise UntypyAttributeError("Async functions are currently not supported.") else: w = wrapper setattr(w, '__wrapped__', self.inner) setattr(w, '__name__', self.inner.__name__) setattr(w, '__signature__', self.signature) setattr(w, '__wf', self) # Copy useful attributes # This is need for the detection of abstract classes for attr in ['__isabstractmethod__']: if hasattr(self.inner, attr): setattr(w, attr, getattr(self.inner, attr)) return w
def posthook(self, ret, boundargs, ctx: ExecutionContext): for p in self.postcondition: bindings = {} for name in inspect.signature(p).parameters: if name == "ret": bindings["ret"] = ret elif name in boundargs.arguments: bindings[name] = boundargs.arguments[name] else: raise UntypyAttributeError( f"Did not find argument {name} of postcondition in function.", locations=[ WrappedFunction.find_location(p), WrappedFunction.find_location(self.func), ] ) if not p(**bindings): lsource = find_lambdasource(p) if lsource is not None: expected = f"passing: {lsource}" else: expected = "passing postcondition" given = ret.__repr__() err = UntypyTypeError( given, expected, ).with_note("Failed postcondition").with_frame(Frame( expected, "", declared=WrappedFunction.find_location(p), responsable=None, )) raise ctx.wrap(err)
def prehook(self, boundargs, ctx: WrappedFunctionContextProvider): for p in self.precondition: bindings = {} for name in inspect.signature(p).parameters: if name in boundargs.arguments: bindings[name] = boundargs.arguments[name] else: raise UntypyAttributeError( f"Did not find argument {name} of precondition in function.", locations=[ WrappedFunction.find_location(p), WrappedFunction.find_location(self.func), ] ) if not p(**bindings): lsource = find_lambdasource(p) if lsource is not None: expected = f"passing: {lsource}" else: expected = "passing precondition" err = UntypyTypeError( bindings, expected ) err = err.with_note("Failed precondition.") err = err.with_frame(Frame( expected, "", declared=WrappedFunction.find_location(p), responsable=None, )) raise ctx(0).wrap(err)
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 checkers(self) -> Dict[str, TypeChecker]: if self._checkers is not None: return self._checkers annotations = get_type_hints(self.inner, self.ctx) 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 self.ctx.wrap( UntypyAttributeError(f"Missing annotation for argument '{key}' of function {self.inner.__name__}\n" "Partial annotation are not supported.")) annotation = annotations[key] checker = self.ctx.find_checker(annotation) if checker is None: raise self.ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {annotation}\n" f"\tin argument '{key}'")) else: checkers[key] = checker if self.inner.__name__ in self.method_name_ignore_return: checkers['return'] = SelfChecker() else: if not 'return' in annotations: annotation = None else: annotation = annotations['return'] return_checker = self.ctx.find_checker(annotation) if return_checker is None: raise self.ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {annotation}\n" f"\tin return")) checkers['return'] = return_checker self._checkers = checkers return checkers
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 _find_bound_typevars(clas: type) -> (type, Dict[TypeVar, Any]): if not hasattr(clas, '__args__') or not hasattr(clas, '__origin__'): return (clas, dict()) if not hasattr(clas.__origin__, '__parameters__'): return (clas, dict()) keys = clas.__origin__.__parameters__ values = clas.__args__ if len(keys) != len(values): raise UntypyAttributeError( f"Some unbound Parameters in {clas.__name__}. " f"keys={keys} do not match values={values}.", [ Location(file=inspect.getfile(clas), line_no=inspect.getsourcelines(clas)[1], source_line="".join(inspect.getsourcelines(clas)[0])) ]) return (clas.__origin__, dict(zip(keys, values)))
def build(self): fn = self.inner name = fn.__name__ def wrapper_cls(*args, **kwargs): caller = sys._getframe(1) (args, kwargs, bindings) = self.wrap_arguments( lambda n: ArgumentExecutionContext( wrapper_cls, caller, n, declared=self.declared()), args, kwargs) ret = fn(*args, **kwargs) return self.wrap_return(ret, bindings, ReturnExecutionContext(self)) def wrapper_self(me, *args, **kwargs): if name == '__init__': me.__return_ctx = None me.__inner = self.create_fn() caller = sys._getframe(1) (args, kwargs, bindings) = self.wrap_arguments( lambda n: ArgumentExecutionContext( wrapper_self, caller, n, declared=self.declared()), (me.__inner, *args), kwargs) ret = fn(*args, **kwargs) if me.__return_ctx is None: return self.wrap_return(ret, bindings, ReturnExecutionContext(self)) else: return self.wrap_return(ret, bindings, me.__return_ctx) if inspect.iscoroutine(self.inner): raise UntypyAttributeError( "Async Functions are currently not supported.") else: if 'self' in self.checker: w = wrapper_self else: w = wrapper_cls setattr(w, '__wrapped__', fn) setattr(w, '__name__', fn.__name__) setattr(w, '__signature__', self.signature) setattr(w, '__wf', self) return w
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_checker(self): if self._checker: return self._checker ctx = DefaultCreationContext( typevars=dict(), declared_location=Location.from_code(self.declared), checkedpkgprefixes=self.cfg.checkedprefixes) try: annotation = self.annotation() except NameError as ne: raise ctx.wrap( UntypyNameError( f"{ne}.\nType annotation could not be resolved.")) checker = ctx.find_checker(annotation) if checker is None: raise ctx.wrap( UntypyAttributeError( f"\n\tUnsupported type annotation: {self.annotation}\n")) self._checker = checker return checker
def build(self): def wrapper(*args, **kwargs): # first is this fn caller = sys._getframe(1) (args, kwargs, bindings) = self.wrap_arguments( lambda n: ArgumentExecutionContext(self, caller, n), args, kwargs) ret = self.inner(*args, **kwargs) ret = self.wrap_return(ret, bindings, ReturnExecutionContext(self)) return ret if inspect.iscoroutine(self.inner): raise UntypyAttributeError( "Async functions are currently not supported.") else: w = wrapper setattr(w, '__wrapped__', self.inner) setattr(w, '__name__', self.inner.__name__) setattr(w, '__signature__', self.signature) setattr(w, '__wf', self) return w
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