def dynamic_init_hook(ctx: ClassDefContext) -> None: if "__init__" in ctx.cls.info.names: return # transform from mypy.plugins.common import add_method_to_class from mypy.types import NoneType from mypy.nodes import ( AssignmentStmt, NameExpr, PlaceholderNode, Var, Argument, ARG_NAMED, ) # from: mypy.plugins.dataclasses args = [] cls = ctx.cls for stmt in cls.defs.body: # Any assignment that doesn't use the new type declaration # syntax can be ignored out of hand. if not (isinstance(stmt, AssignmentStmt) and stmt.new_syntax): continue # a: int, b: str = 1, 'foo' is not supported syntax so we # don't have to worry about it. lhs = stmt.lvalues[0] if not isinstance(lhs, NameExpr): continue sym = cls.info.names.get(lhs.name) if sym is None: # This name is likely blocked by a star import. We don't need to defer because # defer() is already called by mark_incomplete(). continue node = sym.node if isinstance(node, PlaceholderNode): # This node is not ready yet. return None assert isinstance(node, Var) # x: ClassVar[int] is ignored by dataclasses. if node.is_classvar: continue args.append(Argument(node, node.type, None, ARG_NAMED)) add_method_to_class(ctx.api, cls, "__init__", args=args, return_type=NoneType())
def add_additional_orm_attributes( cls: ClassDef, api: SemanticAnalyzerPluginInterface, attributes: List[util.SQLAlchemyAttribute], ) -> None: """Apply __init__, __table__ and other attributes to the mapped class.""" info = util.info_for_cls(cls, api) if info is None: return is_base = util.get_is_base(info) if "__init__" not in info.names and not is_base: mapped_attr_names = {attr.name: attr.type for attr in attributes} for base in info.mro[1:-1]: if "sqlalchemy" not in info.metadata: continue base_cls_attributes = util.get_mapped_attributes(base, api) if base_cls_attributes is None: continue for attr in base_cls_attributes: mapped_attr_names.setdefault(attr.name, attr.type) arguments = [] for name, typ in mapped_attr_names.items(): if typ is None: typ = AnyType(TypeOfAny.special_form) arguments.append( Argument( variable=Var(name, typ), type_annotation=typ, initializer=TempNode(typ), kind=ARG_NAMED_OPT, ) ) add_method_to_class(api, cls, "__init__", arguments, NoneTyp()) if "__table__" not in info.names and util.get_has_table(info): _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.sql.schema.Table", "__table__" ) if not is_base: _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.orm.mapper.Mapper", "__mapper__" )
def attr_utils_serialise_serde(cls_def_ctx: ClassDefContext): """ Handles :func:`attr_utils.serialise.serde`. :param cls_def_ctx: """ info = cls_def_ctx.cls.info # https://gitter.im/python/typing?at=5e078653eac8d1511e737d8c str_type = cls_def_ctx.api.named_type(f"{_builtins}.str") bool_type = cls_def_ctx.api.named_type(f"{_builtins}.bool") implicit_any = AnyType(TypeOfAny.special_form) mapping = cls_def_ctx.api.lookup_fully_qualified_or_none("typing.Mapping") mutable_mapping = cls_def_ctx.api.lookup_fully_qualified_or_none( "typing.MutableMapping") mapping_str_any_type = Instance(mapping.node, [str_type, implicit_any]) # type: ignore mutable_mapping_str_any_type = Instance( mutable_mapping.node, [str_type, implicit_any]) # type: ignore # # maybe_mapping_str_any = UnionType.make_union([typ, NoneType()])(mapping_str_any_type) decorated_class_instance = Instance( cls_def_ctx.api.lookup_fully_qualified_or_none( cls_def_ctx.cls.fullname).node, # type: ignore [], ) if "to_dict" not in info.names: add_method_to_class( api=cls_def_ctx.api, cls=cls_def_ctx.cls, name="to_dict", args=[ Argument(Var("convert_values", bool_type), bool_type, None, ARG_OPT) ], return_type=mutable_mapping_str_any_type, ) if "from_dict" not in info.names: add_classmethod_to_class( api=cls_def_ctx.api, cls=cls_def_ctx.cls, name="from_dict", args=[ Argument(Var('d', mapping_str_any_type), mapping_str_any_type, None, ARG_POS) ], return_type=decorated_class_instance, cls_type=TypeType(decorated_class_instance), )
def make_fake_register_class_instance(api: CheckerPluginInterface, type_args: Sequence[Type] ) -> Instance: defn = ClassDef(REGISTER_RETURN_CLASS, Block([])) defn.fullname = f'functools.{REGISTER_RETURN_CLASS}' info = TypeInfo(SymbolTable(), defn, "functools") obj_type = api.named_generic_type('builtins.object', []).type info.bases = [Instance(obj_type, [])] info.mro = [info, obj_type] defn.info = info func_arg = Argument(Var('name'), AnyType(TypeOfAny.implementation_artifact), None, ARG_POS) add_method_to_class(api, defn, '__call__', [func_arg], NoneType()) return Instance(info, type_args)
def functools_total_ordering_maker_callback( ctx: mypy.plugin.ClassDefContext, auto_attribs_default: bool = False) -> bool: """Add dunder methods to classes decorated with functools.total_ordering.""" if ctx.api.options.python_version < (3, ): # This plugin is not supported in Python 2 mode (it's a no-op). return True comparison_methods = _analyze_class(ctx) if not comparison_methods: ctx.api.fail( 'No ordering operation defined when using "functools.total_ordering": < > <= >=', ctx.reason) return True # prefer __lt__ to __le__ to __gt__ to __ge__ root = max(comparison_methods, key=lambda k: (comparison_methods[k] is None, k)) root_method = comparison_methods[root] if not root_method: # None of the defined comparison methods can be analysed return True other_type = _find_other_type(root_method) bool_type = ctx.api.named_type('builtins.bool') ret_type: Type = bool_type if root_method.type.ret_type != ctx.api.named_type('builtins.bool'): proper_ret_type = get_proper_type(root_method.type.ret_type) if not (isinstance(proper_ret_type, UnboundType) and proper_ret_type.name.split('.')[-1] == 'bool'): ret_type = AnyType(TypeOfAny.implementation_artifact) for additional_op in _ORDERING_METHODS: # Either the method is not implemented # or has an unknown signature that we can now extrapolate. if not comparison_methods.get(additional_op): args = [ Argument(Var('other', other_type), other_type, None, ARG_POS) ] add_method_to_class(ctx.api, ctx.cls, additional_op, args, ret_type) return True
def _add_additional_orm_attributes( cls: ClassDef, api: SemanticAnalyzerPluginInterface, cls_metadata: util.DeclClassApplied, ) -> None: """Apply __init__, __table__ and other attributes to the mapped class.""" info = util._info_for_cls(cls, api) if "__init__" not in info.names and cls_metadata.is_mapped: mapped_attr_names = {n: t for n, t in cls_metadata.mapped_attr_names} for mapped_base in cls_metadata.mapped_mro: base_cls_metadata = util.DeclClassApplied.deserialize( mapped_base.type.metadata["_sa_decl_class_applied"], api ) for n, t in base_cls_metadata.mapped_attr_names: mapped_attr_names.setdefault(n, t) arguments = [] for name, typ in mapped_attr_names.items(): if typ is None: typ = AnyType(TypeOfAny.special_form) arguments.append( Argument( variable=Var(name, typ), type_annotation=typ, initializer=TempNode(typ), kind=ARG_NAMED_OPT, ) ) add_method_to_class(api, cls, "__init__", arguments, NoneTyp()) if "__table__" not in info.names and cls_metadata.has_table: _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.sql.schema.Table", "__table__" ) if cls_metadata.is_mapped: _apply_placeholder_attr_to_class( api, cls, "sqlalchemy.orm.mapper.Mapper", "__mapper__" )
def copy_method_to_another_class( ctx: ClassDefContext, self_type: Instance, new_method_name: str, method_node: FuncDef, return_type: Optional[MypyType] = None, original_module_name: Optional[str] = None, ) -> None: semanal_api = get_semanal_api(ctx) if method_node.type is None: if not semanal_api.final_iteration: semanal_api.defer() return arguments, return_type = build_unannotated_method_args(method_node) add_method_to_class(semanal_api, ctx.cls, new_method_name, args=arguments, return_type=return_type, self_type=self_type) return method_type = method_node.type if not isinstance(method_type, CallableType): if not semanal_api.final_iteration: semanal_api.defer() return if return_type is None: return_type = bind_or_analyze_type(method_type.ret_type, semanal_api, original_module_name) if return_type is None: return # We build the arguments from the method signature (`CallableType`), because if we were to # use the arguments from the method node (`FuncDef.arguments`) we're not compatible with # a method loaded from cache. As mypy doesn't serialize `FuncDef.arguments` when caching arguments = [] # Note that the first argument is excluded, as that's `self` for pos, (arg_type, arg_kind, arg_name) in enumerate( zip(method_type.arg_types[1:], method_type.arg_kinds[1:], method_type.arg_names[1:]), start=1, ): bound_arg_type = bind_or_analyze_type(arg_type, semanal_api, original_module_name) if bound_arg_type is None: return if arg_name is None and hasattr(method_node, "arguments"): arg_name = method_node.arguments[pos].variable.name arguments.append( Argument( # Positional only arguments can have name as `None`, if we can't find a name, we just invent one.. variable=Var( name=arg_name if arg_name is not None else str(pos), type=arg_type), type_annotation=bound_arg_type, initializer=None, kind=arg_kind, pos_only=arg_name is None, )) add_method_to_class(semanal_api, ctx.cls, new_method_name, args=arguments, return_type=return_type, self_type=self_type)
def transform_type_info(context: mypy.plugin.ClassDefContext, proto_type_node: TypeInfo, attribute_type_node: TypeInfo, init: bool) -> TypeInfo: transformed_info: TypeInfo = context.cls.info # Get the list of proto_type class members that are not dunders or private names = [ name for name in proto_type_node.names if not name.startswith('_') ] transformedNames = [] for name in names: proto_node = proto_type_node.names[name] # SymbolTableNode if isinstance(proto_node.node, FuncDef): # Ignore methods continue transformedNames.append(name) copied_node = proto_node.copy() # SymbolTableNode copied_node.node = copy.copy(proto_node.node) # Var copied_node.node._fullname = "{}.{}".format(transformed_info.fullname, copied_node.node.name) copied_node.plugin_generated = True try: nestedTypeInfo = typeInfoByProtoTypeName.get( proto_node.node.type.type.fullname, None) except AttributeError: nestedTypeInfo = None if isinstance(proto_node.node.type, mypy.types.AnyType): # AnyType is not a transformable class anyway. pass else: print("Warning: Failed to check fullname of {}: {}".format( name, proto_node.node.type)) if nestedTypeInfo is not None: # This member's type has been transformed. copied_node.node.type = \ mypy.types.Instance(nestedTypeInfo, []) else: if attribute_type_node.is_generic(): typeArgs = [proto_node.node.type] else: typeArgs = [] copied_node.node.type = \ mypy.types.Instance(attribute_type_node, typeArgs) transformed_info.names[name] = copied_node protoTypeInstance = mypy.types.Instance(proto_type_node, []) if init: argument = Argument( Var(proto_type_node.name.lower(), protoTypeInstance), protoTypeInstance, None, ARG_POS) add_method_to_class(context.api, context.cls, "__init__", [ argument, ], mypy.types.NoneType()) add_method_to_class(context.api, context.cls, "GetProtoType", [], protoTypeInstance) # Now that the class is built, update the info for name in transformedNames: transformed_info.names[name].node.info = transformed_info return transformed_info
def update_dataclass_json_type(ctx: ClassDefContext) -> None: str_type = builtin_type(ctx.api, "str") int_type = builtin_type(ctx.api, "int") sep_type = UnionType.make_union( [builtin_type(ctx.api, 'tuple', [str_type, str_type]), NoneType()]) indent_type = UnionType.make_union([int_type, NoneType()]) args = [ Argument(Var('separators', sep_type), sep_type, None, ARG_NAMED_OPT), Argument(Var('indent', indent_type), indent_type, None, ARG_NAMED_OPT), ] add_method_to_class(ctx.api, ctx.cls, 'to_json', args=args, return_type=str_type) # It would be lovely to actually have this return a typed dict ;) json_dict_type = builtin_type( ctx.api, 'dict', [str_type, AnyType(TypeOfAny.explicit)]) add_method_to_class(ctx.api, ctx.cls, 'to_dict', args=[], return_type=json_dict_type) instance_type = fill_typevars(ctx.cls.info) bool_type = builtin_type(ctx.api, 'bool') args = [ Argument(Var('o', json_dict_type), json_dict_type, None, ARG_POS), Argument(Var('infer_missing', bool_type), bool_type, None, ARG_NAMED_OPT), ] add_classmethod_to_class( ctx.api, ctx.cls, 'from_dict', args=args, return_type=instance_type, ) bytes_type = builtin_type(ctx.api, 'bytes') json_data_type = UnionType.make_union([str_type, bytes_type]) args = [ Argument(Var('json_data', json_data_type), json_data_type, None, ARG_POS), Argument(Var('infer_missing', bool_type), bool_type, None, ARG_NAMED_OPT), ] add_classmethod_to_class( ctx.api, ctx.cls, 'from_json', args=args, return_type=instance_type, )