def _get_type_for_expr(expr: Expression, api: SemanticAnalyzerPluginInterface): if isinstance(expr, NameExpr): # guarding agains invalid nodes, still have to figure out why this happens # but sometimes mypy crashes because the internal node of the named type # is actually a Var node, which is unexpected, so we do a naive guard here # and raise an exception for it. if expr.fullname: sym = api.lookup_fully_qualified_or_none(expr.fullname) if sym and isinstance(sym.node, Var): raise InvalidNodeTypeException() return api.named_type(expr.name) if isinstance(expr, IndexExpr): type_ = _get_type_for_expr(expr.base, api) type_.args = (_get_type_for_expr(expr.index, api), ) return type_ if isinstance(expr, MemberExpr): if expr.fullname: return api.named_type(expr.fullname) else: raise InvalidNodeTypeException() raise ValueError(f"Unsupported expression {type(expr)}")
def add_kw_props(cls: ClassDef, kw_class: ClassDef, api: SemanticAnalyzerPluginInterface) -> None: try: ans_cls_expr = kw_class.keywords['ans_type'] kw_set_expr = kw_class.keywords['extra_kws'] except KeyError: api.fail( 'Both `ans_type` and `extra_kws` are required to subclass ' 'KeywordsMixin', kw_class, code=CALL_ARG) return if not isinstance(ans_cls_expr, RefExpr): print('Wrong instance type for ans_type argument') return ans_cls_node = ans_cls_expr.node if not isinstance(ans_cls_node, TypeInfo): print('Got incorrect type for Ansible type node') return if not (isinstance(kw_set_expr, SetExpr) or is_empty_set_expr(kw_set_expr)): print('Wrong instance type for extra_kws') return # Can be an empty set, where we don't need to add any more properties. if isinstance(kw_set_expr, SetExpr): for kw_expr in kw_set_expr.items: add_property(cls.info, ans_cls_node, kw_expr, api) # Also add one for name add_property(cls.info, ans_cls_node, StrExpr('name'), api)
def _extract_python_type_from_typeengine( api: SemanticAnalyzerPluginInterface, node: TypeInfo, type_args: Sequence[Expression], ) -> ProperType: if node.fullname == "sqlalchemy.sql.sqltypes.Enum" and type_args: first_arg = type_args[0] if isinstance(first_arg, NameExpr) and isinstance( first_arg.node, TypeInfo): for base_ in first_arg.node.mro: if base_.fullname == "enum.Enum": return Instance(first_arg.node, []) # TODO: support other pep-435 types here else: return api.named_type("__builtins__.str", []) assert node.has_base("sqlalchemy.sql.type_api.TypeEngine"), ( "could not extract Python type from node: %s" % node) type_engine_sym = api.lookup_fully_qualified_or_none( "sqlalchemy.sql.type_api.TypeEngine") assert type_engine_sym is not None and isinstance(type_engine_sym.node, TypeInfo) type_engine = map_instance_to_supertype( Instance(node, []), type_engine_sym.node, ) return get_proper_type(type_engine.args[-1])
def check_class_type(sym: Optional[SymbolTableNode], api: SemanticAnalyzerPluginInterface, ctx: Context) -> bool: if sym is None or not isinstance(sym.node, TypeInfo): api.fail('Expected a class', ctx, code=MISC) return False return True
def _adjust_interface_function( self, api: SemanticAnalyzerPluginInterface, class_info: TypeInfo, func_def: FuncDef, ) -> Statement: if func_def.arg_names and func_def.arg_names[0] == "self": # reveal the common mistake of leaving "self" arguments in the # interface api.fail("Interface methods should not have 'self' argument", func_def) else: selftype = Instance(class_info, [], line=class_info.line, column=class_info.column) selfarg = Argument(Var("self", None), selftype, None, ARG_POS) if isinstance(func_def.type, CallableType): func_def.type.arg_names.insert(0, "self") func_def.type.arg_kinds.insert(0, ARG_POS) func_def.type.arg_types.insert(0, selftype) func_def.arg_names.insert(0, "self") func_def.arg_kinds.insert(0, ARG_POS) func_def.arguments.insert(0, selfarg) return func_def
def add_property(cls_node: TypeInfo, ans_cls_node: TypeInfo, prop_node: Expression, api: SemanticAnalyzerPluginInterface) -> None: """Add a property.""" if not isinstance(prop_node, StrExpr): api.fail('Keyword must be a string literal', prop_node, code=VALID_TYPE) return prop_name = prop_node.value try: ans_type = ans_cls_node[prop_name].type except KeyError: api.fail( f'Attribute `{prop_name}` does not exist in ' f'{ans_cls_node.name} or its parents', prop_node, code=ATTR_DEFINED) return prop_type = get_transformed_type(cls_node, ans_type, prop_node, api) if prop_type is None: return if not has_default(cls_node, prop_node, api) and prop_type is not None: prop_type = make_optional(prop_type) new_prop = Var(prop_name, api.anal_type(prop_type)) new_prop.info = cls_node new_prop.is_initialized_in_class = True new_prop.is_property = True cls_node.names[prop_name] = SymbolTableNode(MDEF, new_prop)
def _interface_classdefs_for_meta_classdef( semanal: SemanticAnalyzerPluginInterface, classdef: ClassDef) -> List[ClassDef]: for statement in classdef.defs.body: if isinstance(statement, AssignmentStmt) and any( isinstance(lval, NameExpr) and lval.name == "interfaces" for lval in statement.lvalues): if not isinstance(statement.rvalue, TupleExpr): semanal.fail( '"interfaces" attribute in Meta class must be a tuple type', statement, ) return [] # Loop through tuple and add defintions of graphene `Interface`s to the final list. interface_defs: List[ClassDef] = [] for item in statement.rvalue.items: if (isinstance(item, RefExpr) and isinstance(item.node, TypeInfo) and isinstance(item.node.defn, ClassDef)): interface_defs.append(item.node.defn) return interface_defs return []
def _apply_type_to_mapped_statement( api: SemanticAnalyzerPluginInterface, stmt: AssignmentStmt, lvalue: NameExpr, left_hand_explicit_type: Optional[Union[Instance, UnionType]], python_type_for_type: Union[Instance, UnionType], ) -> None: """Apply the Mapped[<type>] annotation and right hand object to a declarative assignment statement. This converts a Python declarative class statement such as:: class User(Base): # ... attrname = Column(Integer) To one that describes the final Python behavior to Mypy:: class User(Base): # ... attrname : Mapped[Optional[int]] = <meaningless temp node> """ descriptor = api.modules["sqlalchemy.orm.attributes"].names["Mapped"] left_node = lvalue.node inst = Instance(descriptor.node, [python_type_for_type]) if left_hand_explicit_type is not None: left_node.type = Instance(descriptor.node, [left_hand_explicit_type]) else: lvalue.is_inferred_def = False left_node.type = inst # so to have it skip the right side totally, we can do this: # stmt.rvalue = TempNode(AnyType(TypeOfAny.special_form)) # however, if we instead manufacture a new node that uses the old # one, then we can still get type checking for the call itself, # e.g. the Column, relationship() call, etc. # rewrite the node as: # <attr> : Mapped[<typ>] = # _sa_Mapped._empty_constructor(<original CallExpr from rvalue>) # the original right-hand side is maintained so it gets type checked # internally api.add_symbol_table_node("_sa_Mapped", descriptor) column_descriptor = nodes.NameExpr("_sa_Mapped") column_descriptor.fullname = "sqlalchemy.orm.Mapped" mm = nodes.MemberExpr(column_descriptor, "_empty_constructor") orig_call_expr = stmt.rvalue stmt.rvalue = CallExpr( mm, [orig_call_expr], [nodes.ARG_POS], ["arg1"], )
def get_transformed_type( cls_node: TypeInfo, ans_type: Optional[Type], prop_node: StrExpr, api: SemanticAnalyzerPluginInterface) -> Optional[Type]: transformer_name = f'_transform_{prop_node.value}' transformer = cls_node.get(transformer_name) if transformer is None: return ans_type transformer_type: Optional[Type] if isinstance(transformer.node, Decorator): transformer_type = transformer.node.func.type elif isinstance(transformer.node, FuncDef): transformer_type = transformer.node.type elif (isinstance(transformer.node, Var) and (transformer.node.is_ready or transformer.node.is_final)): if transformer.node.is_ready: transformer_type = transformer.node.type else: transformer_type = find_redef_origin(cls_node, transformer_name) if transformer_type is None: api.fail(f'Cannot resolve type of `{transformer_name}`', transformer.node, code=MISC) return None else: api.fail( f'Cannot handle transformer `{transformer_name}` of type ' + transformer.node.__class__.__name__, transformer.node if transformer.node is not None else cls_node, code=MISC) return None if not isinstance(transformer_type, CallableType): api.fail(f'Cannot infer type of `{transformer_name}`', transformer.node, code=MISC) return None if len(transformer_type.arg_types) != 2: api.fail( f'Expected exactly 2 arguments for {transformer_name}:' 'self and source object', transformer.node, code=CALL_ARG) return None transformer_type = api.anal_type(transformer_type) if not isinstance(transformer_type, CallableType): return None ret_type = bind_type_var(cls_node, transformer_type.ret_type) return api.anal_type(ret_type)
def apply_type_to_mapped_statement( api: SemanticAnalyzerPluginInterface, stmt: AssignmentStmt, lvalue: NameExpr, left_hand_explicit_type: Optional[ProperType], python_type_for_type: Optional[ProperType], ) -> None: """Apply the Mapped[<type>] annotation and right hand object to a declarative assignment statement. This converts a Python declarative class statement such as:: class User(Base): # ... attrname = Column(Integer) To one that describes the final Python behavior to Mypy:: class User(Base): # ... attrname : Mapped[Optional[int]] = <meaningless temp node> """ left_node = lvalue.node assert isinstance(left_node, Var) if left_hand_explicit_type is not None: left_node.type = api.named_type( "__sa_Mapped", [left_hand_explicit_type] ) else: lvalue.is_inferred_def = False left_node.type = api.named_type( "__sa_Mapped", [] if python_type_for_type is None else [python_type_for_type], ) # so to have it skip the right side totally, we can do this: # stmt.rvalue = TempNode(AnyType(TypeOfAny.special_form)) # however, if we instead manufacture a new node that uses the old # one, then we can still get type checking for the call itself, # e.g. the Column, relationship() call, etc. # rewrite the node as: # <attr> : Mapped[<typ>] = # _sa_Mapped._empty_constructor(<original CallExpr from rvalue>) # the original right-hand side is maintained so it gets type checked # internally stmt.rvalue = util.expr_to_mapped_constructor(stmt.rvalue)
def infer_type_from_left_hand_type_only( api: SemanticAnalyzerPluginInterface, node: Var, left_hand_explicit_type: Optional[ProperType], ) -> Optional[ProperType]: """Determine the type based on explicit annotation only. if no annotation were present, note that we need one there to know the type. """ if left_hand_explicit_type is None: msg = ( "Can't infer type from ORM mapped expression " "assigned to attribute '{}'; please specify a " "Python type or " "Mapped[<python type>] on the left hand side." ) util.fail(api, msg.format(node.name), node) return api.named_type("__sa_Mapped", [AnyType(TypeOfAny.special_form)]) else: # use type from the left hand side return left_hand_explicit_type
def _set_declarative_metaclass(api: SemanticAnalyzerPluginInterface, target_cls: ClassDef) -> None: info = target_cls.info sym = api.lookup_fully_qualified_or_none( "sqlalchemy.orm.decl_api.DeclarativeMeta") assert sym is not None and isinstance(sym.node, TypeInfo) info.declared_metaclass = info.metaclass_type = Instance(sym.node, [])
def _re_apply_declarative_assignments( cls: ClassDef, api: SemanticAnalyzerPluginInterface, cls_metadata: util.DeclClassApplied, ): """For multiple class passes, re-apply our left-hand side types as mypy seems to reset them in place. """ mapped_attr_lookup = { name: typ for name, typ in cls_metadata.mapped_attr_names } descriptor = api.lookup("__sa_Mapped", cls) for stmt in cls.defs.body: # for a re-apply, all of our statements are AssignmentStmt; # @declared_attr calls will have been converted and this # currently seems to be preserved by mypy (but who knows if this # will change). if (isinstance(stmt, AssignmentStmt) and stmt.lvalues[0].name in mapped_attr_lookup): typ = mapped_attr_lookup[stmt.lvalues[0].name] left_node = stmt.lvalues[0].node inst = Instance(descriptor.node, [typ]) left_node.type = inst
def _infer_type_from_left_and_inferred_right( api: SemanticAnalyzerPluginInterface, node: Var, left_hand_explicit_type: Optional[types.Type], python_type_for_type: Union[Instance, UnionType], ) -> Optional[Union[Instance, UnionType]]: """Validate type when a left hand annotation is present and we also could infer the right hand side:: attrname: SomeType = Column(SomeDBType) """ if not is_subtype(left_hand_explicit_type, python_type_for_type): descriptor = api.lookup("__sa_Mapped", node) effective_type = Instance(descriptor.node, [python_type_for_type]) msg = ("Left hand assignment '{}: {}' not compatible " "with ORM mapped expression of type {}") util.fail( api, msg.format( node.name, format_type(left_hand_explicit_type), format_type(effective_type), ), node, ) return left_hand_explicit_type
def _info_for_cls(cls: ClassDef, api: SemanticAnalyzerPluginInterface) -> TypeInfo: if cls.info is CLASSDEF_NO_INFO: sym = api.lookup_qualified(cls.name, cls) assert sym and isinstance(sym.node, TypeInfo) return sym.node return cls.info
def for_argument(cls, semanal: SemanticAnalyzerPluginInterface, argument: Argument) -> 'ResolverArgumentInfo': type_annotation = semanal.anal_type( argument.type_annotation) if argument.type_annotation else None return cls( name=argument.variable.name, type=type_annotation or AnyType(TypeOfAny.unannotated), context=argument, )
def add_metadata_var(api: SemanticAnalyzerPluginInterface, info: TypeInfo) -> None: """Add .metadata attribute to a declarative base.""" sym = api.lookup_fully_qualified_or_none('sqlalchemy.sql.schema.MetaData') if sym: assert isinstance(sym.node, TypeInfo) typ = Instance(sym.node, []) # type: Type else: typ = AnyType(TypeOfAny.special_form) add_var_to_class('metadata', typ, info)
def _get_type_for_expr(expr: Expression, api: SemanticAnalyzerPluginInterface): if isinstance(expr, NameExpr): return api.named_type(expr.name) if isinstance(expr, IndexExpr): type_ = _get_type_for_expr(expr.base, api) type_.args = (_get_type_for_expr(expr.index, api),) return type_ raise ValueError(f"Unsupported expression f{type(expr)}")
def create_ortho_diff_class(base1: TypeInfo, base2: TypeInfo, api: SemanticAnalyzerPluginInterface, call_ctx: Context) -> Tuple[str, SymbolTableNode]: # https://github.com/dropbox/sqlalchemy-stubs/blob/55470ceab8149db983411d5c094c9fe16343c58b/sqlmypy.py#L173-L216 cls_name = get_ortho_diff_name(base1.defn, base2.defn) class_def = ClassDef(cls_name, Block([])) class_def.fullname = api.qualified_name(cls_name) info = TypeInfo(SymbolTable(), class_def, api.cur_mod_id) class_def.info = info obj = api.builtin_type('builtins.object') info.bases = [cast(Instance, fill_typevars(b)) for b in (base1, base2)] try: calculate_mro(info) except MroError: api.fail('Unable to calculate MRO for dynamic class', call_ctx) info.bases = [obj] info.fallback_to_any = True return cls_name, SymbolTableNode(GDEF, info)
def _type_id_for_unbound_type(type_: UnboundType, cls: ClassDef, api: SemanticAnalyzerPluginInterface) -> int: type_id = None sym = api.lookup(type_.name, type_) if sym is not None: if isinstance(sym.node, TypeAlias): type_id = _type_id_for_named_node(sym.node.target.type) elif isinstance(sym.node, TypeInfo): type_id = _type_id_for_named_node(sym.node) return type_id
def apply_implementer( iface_arg: Expression, class_info: TypeInfo, api: SemanticAnalyzerPluginInterface, ) -> None: if not isinstance(iface_arg, RefExpr): api.fail("Argument to implementer should be a ref expression", iface_arg) return iface_name = iface_arg.fullname if iface_name is None: # unknown interface, probably from stubless package return iface_type = iface_arg.node if iface_type is None: return if not isinstance(iface_type, TypeInfo): # Possibly an interface from unimported package, ignore return if not self._is_interface(iface_type): api.fail( f"zope.interface.implementer accepts interface, " f"not {iface_name}.", iface_arg, ) api.fail( f"Make sure you have stubs for all packages that " f"provide interfaces for {iface_name} class hierarchy.", iface_arg, ) return self._apply_interface(class_info, iface_type)
def type_id_for_unbound_type( type_: UnboundType, cls: ClassDef, api: SemanticAnalyzerPluginInterface) -> Optional[int]: sym = api.lookup_qualified(type_.name, type_) if sym is not None: if isinstance(sym.node, TypeAlias): target_type = get_proper_type(sym.node.target) if isinstance(target_type, Instance): return type_id_for_named_node(target_type.type) elif isinstance(sym.node, TypeInfo): return type_id_for_named_node(sym.node) return None
def add_method_to_class( api: SemanticAnalyzerPluginInterface, cls: ClassDef, name: str, args: List[Argument], return_type: Type, self_type: Optional[Type] = None, tvar_def: Optional[TypeVarDef] = None, ) -> None: """Adds a new method to a class definition. """ info = cls.info # First remove any previously generated methods with the same name # to avoid clashes and problems in the semantic analyzer. if name in info.names: sym = info.names[name] if sym.plugin_generated and isinstance(sym.node, FuncDef): cls.defs.body.remove(sym.node) self_type = self_type or fill_typevars(info) function_type = api.named_type('__builtins__.function') args = [Argument(Var('self'), self_type, None, ARG_POS)] + args arg_types, arg_names, arg_kinds = [], [], [] for arg in args: assert arg.type_annotation, 'All arguments must be fully typed.' arg_types.append(arg.type_annotation) arg_names.append(arg.variable.name) arg_kinds.append(arg.kind) signature = CallableType(arg_types, arg_kinds, arg_names, return_type, function_type) if tvar_def: signature.variables = [tvar_def] func = FuncDef(name, args, Block([PassStmt()])) func.info = info func.type = set_callable_name(signature, func) func._fullname = info.fullname + '.' + name func.line = info.line # NOTE: we would like the plugin generated node to dominate, but we still # need to keep any existing definitions so they get semantically analyzed. if name in info.names: # Get a nice unique name instead. r_name = get_unique_redefinition_name(name, info.names) info.names[r_name] = info.names[name] info.names[name] = SymbolTableNode(MDEF, func, plugin_generated=True) info.defn.defs.body.append(func)
def _adjust_interface_function( self, api: SemanticAnalyzerPluginInterface, class_info: TypeInfo, func_def: FuncDef, ) -> Statement: if func_def.arg_names and func_def.arg_names[0] == "self": # reveal the common mistake of leaving "self" arguments in the # interface api.fail("Interface methods should not have 'self' argument", func_def) else: selftype = Instance(class_info, [], line=class_info.line, column=class_info.column) selfarg = Argument(Var("self", None), selftype, None, ARG_POS) if isinstance(func_def.type, CallableType): func_def.type.arg_names.insert(0, "self") func_def.type.arg_kinds.insert(0, ARG_POS) func_def.type.arg_types.insert(0, selftype) func_def.arg_names.insert(0, "self") func_def.arg_kinds.insert(0, ARG_POS) func_def.arguments.insert(0, selfarg) func_def.is_abstract = True if func_def.name() in ("__getattr__", "__getattribute__"): # These special methods cannot be decorated. Likely a mypy bug. return func_def func_def.is_decorated = True var = Var(func_def.name(), func_def.type) var.is_initialized_in_class = True var.info = func_def.info var.set_line(func_def.line) decor = Decorator(func_def, [], var) return decor
def _apply_placeholder_attr_to_class( api: SemanticAnalyzerPluginInterface, cls: ClassDef, qualified_name: str, attrname: str, ): sym = api.lookup_fully_qualified_or_none(qualified_name) if sym: assert isinstance(sym.node, TypeInfo) type_ = Instance(sym.node, []) else: type_ = AnyType(TypeOfAny.special_form) var = Var(attrname) var.info = cls.info var.type = type_ cls.info.names[attrname] = SymbolTableNode(MDEF, var)
def _get_func_def_ret_type(semanal: SemanticAnalyzerPluginInterface, funcdef: FuncDef) -> Type: """ Given a `FuncDef`, return its return-type (or `Any`) """ ret_type = None type_ = funcdef.type if isinstance(type_, CallableType): ret_type = type_.ret_type if isinstance(ret_type, UnboundType): ret_type = semanal.anal_type(ret_type) return ret_type or AnyType(TypeOfAny.unannotated)
def _unbound_to_instance( api: SemanticAnalyzerPluginInterface, typ: Type ) -> Type: """Take the UnboundType that we seem to get as the ret_type from a FuncDef and convert it into an Instance/TypeInfo kind of structure that seems to work as the left-hand type of an AssignmentStatement. """ if not isinstance(typ, UnboundType): return typ # TODO: figure out a more robust way to check this. The node is some # kind of _SpecialForm, there's a typing.Optional that's _SpecialForm, # but I cant figure out how to get them to match up if typ.name == "Optional": # convert from "Optional?" to the more familiar # UnionType[..., NoneType()] return _unbound_to_instance( api, UnionType( [_unbound_to_instance(api, typ_arg) for typ_arg in typ.args] + [NoneType()] ), ) node = api.lookup_qualified(typ.name, typ) if ( node is not None and isinstance(node, SymbolTableNode) and isinstance(node.node, TypeInfo) ): bound_type = node.node return Instance( bound_type, [ _unbound_to_instance(api, arg) if isinstance(arg, UnboundType) else arg for arg in typ.args ], ) else: return typ
def apply_interface( iface_arg: Expression, class_info: TypeInfo, api: SemanticAnalyzerPluginInterface, context: Context, ) -> None: if not isinstance(iface_arg, RefExpr): api.fail("Argument to implementer should be a ref expression", iface_arg) return iface_name = iface_arg.fullname if iface_name is None: # unknown interface, probably from stubless package return iface_type = iface_arg.node if iface_type is None: return if not isinstance(iface_type, TypeInfo): # Possibly an interface from unimported package, ignore return if not self._is_interface(iface_type): api.fail( f"zope.interface.implementer accepts interface, " f"not {iface_name}.", iface_arg, ) api.fail( f"Make sure you have stubs for all packages that " f"provide interfaces for {iface_name} class hierarchy.", iface_arg, ) return # print("CLASS INFO", class_info) md = self._get_metadata(class_info) if "implements" not in md: md["implements"] = [] # impl_list = cast(List[str], md['implements']) md["implements"].append(iface_type.fullname) self.log(f"Found implementation of " f"{iface_type.fullname}: {class_info.fullname}") # Make sure implementation is treated as a subtype of an interface. Pretend # there is a decorator for the class that will create a "type promotion", # but ensure this only gets applied a single time per interface. promote = Instance(iface_type, []) if not any(ti._promote == promote for ti in class_info.mro): faketi = TypeInfo(SymbolTable(), iface_type.defn, iface_type.module_name) faketi._promote = promote class_info.mro.append(faketi)
def _make_declarative_meta(api: SemanticAnalyzerPluginInterface, target_cls: ClassDef): declarative_meta_name: NameExpr = NameExpr("__sa_DeclarativeMeta") declarative_meta_name.kind = GDEF declarative_meta_name.fullname = "sqlalchemy.orm.decl_api.DeclarativeMeta" # installed by _add_globals sym = api.lookup("__sa_DeclarativeMeta", target_cls) declarative_meta_typeinfo = sym.node declarative_meta_name.node = declarative_meta_typeinfo target_cls.metaclass = declarative_meta_name declarative_meta_instance = Instance(declarative_meta_typeinfo, []) info = target_cls.info info.declared_metaclass = info.metaclass_type = declarative_meta_instance
def resolve_nameexpr( expr: Expression, api: SemanticAnalyzerPluginInterface) -> Optional[SymbolTableNode]: if not isinstance(expr, NameExpr): api.fail('Cannot resolve this, please use a simple name', expr, code=MISC) return None try: return api.lookup_qualified(expr.name, ctx=expr) except KeyError: if api.final_iteration: api.fail('Cannot resolve this, please use a simple name', expr, code=MISC) return None api.defer() return None