示例#1
0
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())
示例#2
0
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__"
        )
示例#3
0
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),
        )
示例#4
0
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)
示例#5
0
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
示例#6
0
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__"
        )
示例#7
0
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)
示例#8
0
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
示例#9
0
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,
    )