Beispiel #1
0
def _analyze_open_signature(
    arg_types: List[List[Type]],
    args: List[List[Expression]],
    mode_arg_index: int,
    default_return_type: Type,
    api: CheckerPluginInterface,
) -> Type:
    """A helper for analyzing any function that has approximately
    the same signature as the builtin 'open(...)' function.

    Currently, the only thing the caller can customize is the index
    of the 'mode' argument. If the mode argument is omitted or is a
    string literal, we refine the return type to either 'TextIO' or
    'BinaryIO' as appropriate.
    """
    mode = None
    if not arg_types or len(arg_types[mode_arg_index]) != 1:
        mode = 'r'
    else:
        mode_expr = args[mode_arg_index][0]
        if isinstance(mode_expr, StrExpr):
            mode = mode_expr.value
    if mode is not None:
        assert isinstance(default_return_type, Instance)  # type: ignore
        if 'b' in mode:
            return api.named_generic_type('typing.BinaryIO', [])
        else:
            return api.named_generic_type('typing.TextIO', [])
    return default_return_type
Beispiel #2
0
def get_related_manager_type_from_metadata(model_info: TypeInfo, related_manager_name: str,
                                           api: CheckerPluginInterface) -> Optional[Instance]:
    related_manager_metadata = get_related_managers_metadata(model_info)
    if not related_manager_metadata:
        return None

    if related_manager_name not in related_manager_metadata:
        return None

    manager_class_name = related_manager_metadata[related_manager_name]['manager']
    of = related_manager_metadata[related_manager_name]['of']
    of_types = []
    for of_type_name in of:
        if of_type_name == 'any':
            of_types.append(AnyType(TypeOfAny.implementation_artifact))
        else:
            try:
                of_type = api.named_generic_type(of_type_name, [])
            except AssertionError:
                # Internal error: attempted lookup of unknown name
                of_type = AnyType(TypeOfAny.implementation_artifact)

            of_types.append(of_type)

    return api.named_generic_type(manager_class_name, of_types)
Beispiel #3
0
def error_unexpected_behavior(detail: str, api: CheckerPluginInterface,
                              context: Context) -> None:  # pragma: no cover
    # Can't think of a good way to test this, but I confirmed it renders as desired by adding to a non-error path
    link = 'https://github.com/samuelcolvin/pydantic/issues/new/choose'
    full_message = f'The pydantic mypy plugin ran into unexpected behavior: {detail}\n'
    full_message += f'Please consider reporting this bug at {link} so we can try to fix it!'
    api.fail(full_message, context, code=ERROR_UNEXPECTED)
Beispiel #4
0
def check_intoin_outtoout(
    *,
    l1_arg: Expression,
    l1_type: Instance,
    l2_arg: Expression,
    l2_type: Instance,
    api: CheckerPluginInterface,
):
    if l1_type.args[0] != l2_type.args[0]:
        api.fail(
            f"Layer input ({l1_type.args[0]}) not compatible with next layer input ({l2_type.args[0]})",
            l1_arg,
            code=error_layer_input,
        )
        api.fail(
            f"Layer input ({l2_type.args[0]}) not compatible with previous layer input ({l1_type.args[0]})",
            l2_arg,
            code=error_layer_input,
        )
    if l1_type.args[1] != l2_type.args[1]:
        api.fail(
            f"Layer output ({l1_type.args[1]}) not compatible with next layer output ({l2_type.args[1]})",
            l1_arg,
            code=error_layer_output,
        )
        api.fail(
            f"Layer output ({l2_type.args[1]}) not compatible with previous layer output ({l1_type.args[1]})",
            l2_arg,
            code=error_layer_output,
        )
Beispiel #5
0
def _str_to_int(api: CheckerPluginInterface, typ: Type) -> Type:
    if isinstance(typ, Instance):
        if typ.type.fullname() == 'builtins.str':
            return api.named_generic_type('builtins.int', [])
        elif typ.args:
            return typ.copy_modified(args=[_str_to_int(api, t) for t in typ.args])

    return typ
Beispiel #6
0
def _float_to_int(api: CheckerPluginInterface, typ: Type) -> Type:
    if isinstance(typ, Instance):
        if typ.type.fullname() == 'builtins.float':
            return api.named_generic_type('builtins.int', [])
        elif typ.args:
            return typ.copy_modified(
                args=[_float_to_int(api, t) for t in typ.args])
    return typ
Beispiel #7
0
def make_typeddict(api: CheckerPluginInterface,
                   fields: "OrderedDict[str, MypyType]",
                   required_keys: Set[str]) -> TypedDictType:
    object_type = api.named_generic_type("mypy_extensions._TypedDict", [])
    typed_dict_type = TypedDictType(fields,
                                    required_keys=required_keys,
                                    fallback=object_type)
    return typed_dict_type
Beispiel #8
0
def _build_vals_dict(typ: Instance,
                     api: CheckerPluginInterface) -> "OrderedDict[str, Type]":
    return OrderedDict({
        name:
        (api.named_generic_type("odoo.models._RecordId", [stn.type])
         if isinstance(stn.type, Instance)
         and stn.type.type.fullname.startswith("odoo.models.") else stn.type)
        for name, stn in typ.type.names.items() if stn.type
    })
Beispiel #9
0
def open_return_type(api: CheckerPluginInterface,
                     args: List[List[Expression]]) -> Optional[Type]:
    def return_type(word: Literal["Text", "Buffered", "Raw"]) -> Type:
        return api.named_generic_type(
            "typing.Awaitable",
            [api.named_generic_type("trio._Async{}IOBase".format(word), [])],
        )

    if len(args) < 2 or len(args[1]) == 0:
        # If mode is unspecified, the default is text
        return return_type("Text")

    if len(args[1]) == 1:
        # Mode was specified
        mode_arg = args[1][0]
        if isinstance(mode_arg, StrExpr):
            # Mode is a string constant
            if "b" not in mode_arg.value:
                # If there's no "b" in it, it's a text mode
                return return_type("Text")
            # Otherwise it's binary -- determine whether buffered or not
            if len(args) >= 3 and len(args[2]) == 1:
                # Buffering was specified
                buffering_arg = args[2][0]
                if isinstance(buffering_arg, IntExpr):
                    # Buffering is a constant -- zero means
                    # unbuffered, otherwise buffered
                    if buffering_arg.value == 0:
                        return return_type("Raw")
                    return return_type("Buffered")
                # Not a constant, so we're not sure which it is.
                options = [
                    api.named_generic_type("trio._AsyncRawIOBase", []),
                    api.named_generic_type("trio._AsyncBufferedIOBase", []),
                ]  # type: List[Type]
                return api.named_generic_type(
                    "typing.Awaitable",
                    [UnionType.make_simplified_union(options)])
            else:
                # Buffering is default if not specified
                return return_type("Buffered")

    # Mode wasn't a constant or we couldn't make sense of it
    return None
Beispiel #10
0
def check_chained(
    *,
    l1_arg: Expression,
    l1_type: Instance,
    l2_arg: Expression,
    l2_type: Instance,
    api: CheckerPluginInterface,
):
    if not is_subtype(l1_type.args[1], l2_type.args[0]):
        api.fail(
            f"Layer outputs type ({l1_type.args[1]}) but the next layer expects ({l2_type.args[0]}) as an input",
            l1_arg,
            code=error_layer_output,
        )
        api.fail(
            f"Layer input type ({l2_type.args[0]}) is not compatible with output ({l1_type.args[1]}) from previous layer",
            l2_arg,
            code=error_layer_input,
        )
Beispiel #11
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)
Beispiel #12
0
def resolve_model_pk_lookup(api: CheckerPluginInterface,
                            model_type_info: TypeInfo) -> LookupNode:
    # Primary keys are special-cased
    primary_key_type = helpers.extract_primary_key_type_for_get(
        model_type_info)
    if primary_key_type:
        return FieldNode(primary_key_type)
    else:
        # No PK, use the get type for AutoField as PK type.
        autofield_info = api.lookup_typeinfo(
            'django.db.models.fields.AutoField')
        pk_type = helpers.get_private_descriptor_type(autofield_info,
                                                      '_pyi_private_get_type',
                                                      is_nullable=False)
        return FieldNode(pk_type)
Beispiel #13
0
def make_simple_type(
    fieldtype: str,
    arg_names: List[List[Optional[str]]],
    args: List[List[Expression]],
    api: CheckerPluginInterface,
) -> Optional[Type]:
    typename = SIMPLE_FIELD_TO_TYPE.get(fieldtype)
    if not typename:
        return None
    stdtype = api.named_generic_type(typename, [])
    for nameset, argset in zip(arg_names, args):
        for name, arg in zip(nameset, argset):
            if name == "required" and is_false_literal(arg):
                nonetype = NoneTyp()
                optionaltype = UnionType([stdtype, nonetype])
                return optionaltype
    return stdtype
Beispiel #14
0
    def _report_implementation_problems(
        self,
        impl_type: Instance,
        iface_type: Instance,
        api: CheckerPluginInterface,
        context: Context,
    ) -> None:
        # This mimicks mypy's MessageBuilder.report_protocol_problems with
        # simplifications for zope interfaces.

        # Blatantly assume we are dealing with this particular implementation
        # of CheckerPluginInterface, because it has functionality we want to
        # reuse.
        assert isinstance(api, TypeChecker)

        impl_info = impl_type.type
        iface_info = iface_type.type
        iface_members = self._index_members(iface_info)
        impl_members = self._index_members(impl_info)

        # Report missing members
        missing: Dict[str, List[str]] = defaultdict(list)
        for member, member_iface in iface_members.items():
            if find_member(member, impl_type, impl_type) is None:
                iface_name = member_iface.fullname
                if member_iface == iface_info:
                    # Since interface is directly implemented by this class, we
                    # can use shorter name.
                    iface_name = member_iface.name
                missing[iface_name].append(member)

        if missing:
            for iface_name, members in missing.items():
                missing_fmt = ", ".join(members)
                api.fail(
                    f"'{impl_info.name}' is missing following "
                    f"'{iface_name}' interface members: {missing_fmt}.",
                    context,
                )

        # Report member type conflicts
        for member, member_iface in iface_members.items():
            iface_mtype = find_member(member, iface_type, iface_type)
            assert iface_mtype is not None
            impl_mtype = find_member(member, impl_type, impl_type)
            if impl_mtype is None:
                continue

            # Find out context for error reporting. If the member is not
            # defined inside the class that declares interface implementation,
            # show the error near the class definition. Otherwise, show it near
            # the member definition.
            ctx: Context = impl_info
            impl_member_def_info = impl_members.get(member)
            impl_name = impl_info.name
            if impl_member_def_info is not None:
                if impl_member_def_info == impl_info:
                    ctx = impl_mtype
                else:
                    impl_name = impl_member_def_info.fullname

            iface_name = iface_info.name
            if member_iface != iface_info:
                iface_name = member_iface.fullname

            if isinstance(impl_mtype, PartialType):
                # We don't know how to deal with partial type here. Partial
                # types will be resolved later when the implementation class is
                # fully type-checked. We are doing our job before that, so all
                # we can do is to skip checking of such members.
                continue

            if isinstance(iface_mtype, FunctionLike) and isinstance(
                    impl_mtype, FunctionLike):
                api.check_override(
                    override=impl_mtype,
                    original=iface_mtype,
                    name=impl_name,
                    name_in_super=member,
                    supertype=iface_name,
                    original_class_or_static=False,
                    override_class_or_static=False,
                    node=ctx,
                )

            else:
                # We could check the field type for compatibility with the code
                # below, however this yields too many false-positives in
                # real-life projects. The problem is that we can only check
                # types defined on a class, instead of instance types, so all
                # "descriptor" properties will be falsly reported as type
                # mismatches. Instead we opt-out from property type checking
                # until we figure out the workaround.

                # api.check_subtype(
                #     impl_mtype,
                #     iface_mtype,
                #     context=ctx,
                #     subtype_label=f'"{impl_name}" has type',
                #     supertype_label=f'interface "{iface_name}" defines "{member}" as',
                # )
                pass
Beispiel #15
0
def resolve_model_lookup(api: CheckerPluginInterface,
                         model_type_info: TypeInfo, lookup: str) -> LookupNode:
    """Resolve a lookup on the given model."""
    if lookup == 'pk':
        # Primary keys are special-cased
        primary_key_type = helpers.extract_primary_key_type_for_get(
            model_type_info)
        if primary_key_type:
            return FieldNode(primary_key_type)
        else:
            # No PK, use the get type for AutoField as PK type.
            autofield_info = api.lookup_typeinfo(
                'django.db.models.fields.AutoField')
            pk_type = helpers.get_private_descriptor_type(
                autofield_info, '_pyi_private_get_type', is_nullable=False)
            return FieldNode(pk_type)

    field_name = get_actual_field_name_for_lookup_field(
        lookup, model_type_info)

    field_node = model_type_info.get(field_name)
    if not field_node:
        raise LookupException(
            f'When resolving lookup "{lookup}", field "{field_name}" was not found in model {model_type_info.name()}'
        )

    if field_name.endswith('_id'):
        field_name_without_id = field_name.rstrip('_id')
        foreign_key_field = model_type_info.get(field_name_without_id)
        if foreign_key_field is not None and helpers.is_foreign_key(
                foreign_key_field.type):
            # Hack: If field ends with '_id' and there is a model field without the '_id' suffix, then use that field.
            field_node = foreign_key_field
            field_name = field_name_without_id

    field_node_type = field_node.type
    if field_node_type is None or not isinstance(field_node_type, Instance):
        raise LookupException(
            f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}'
        )

    if helpers.is_foreign_key(field_node_type):
        field_type = helpers.extract_field_getter_type(field_node_type)
        is_nullable = helpers.is_optional(field_type)
        if is_nullable:
            field_type = helpers.make_required(field_type)

        if isinstance(field_type, Instance):
            return RelatedModelNode(typ=field_type, is_nullable=is_nullable)
        else:
            raise LookupException(
                f"Not an instance for field {field_type} lookup {lookup}")

    field_type = helpers.extract_field_getter_type(field_node_type)

    if field_type:
        return FieldNode(typ=field_type)
    else:
        # Not a Field
        if field_name == 'id':
            # If no 'id' field was fouond, use an int
            return FieldNode(api.named_generic_type('builtins.int', []))

        related_manager_arg = None
        if field_node_type.type.has_base(
                helpers.RELATED_MANAGER_CLASS_FULLNAME):
            related_manager_arg = field_node_type.args[0]

        if related_manager_arg is not None:
            # Reverse relation
            return RelatedModelNode(typ=related_manager_arg, is_nullable=True)
        raise LookupException(
            f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}'
        )
Beispiel #16
0
def error_from_orm(model_name: str, api: CheckerPluginInterface,
                   context: Context) -> None:
    api.fail(f'"{model_name}" does not have orm_mode=True',
             context,
             code=ERROR_ORM)
Beispiel #17
0
def resolve_model_lookup(api: CheckerPluginInterface,
                         model_type_info: TypeInfo, lookup: str) -> LookupNode:
    """Resolve a lookup on the given model."""
    if lookup == 'pk':
        return resolve_model_pk_lookup(api, model_type_info)

    field_name = get_actual_field_name_for_lookup_field(
        lookup, model_type_info)

    field_node = model_type_info.get(field_name)
    if not field_node:
        raise LookupException(
            f'When resolving lookup "{lookup}", field "{field_name}" was not found in model {model_type_info.name()}'
        )

    if field_name.endswith('_id'):
        field_name_without_id = field_name.rstrip('_id')
        foreign_key_field = model_type_info.get(field_name_without_id)
        if foreign_key_field is not None and helpers.is_foreign_key_like(
                foreign_key_field.type):
            # Hack: If field ends with '_id' and there is a model field without the '_id' suffix, then use that field.
            field_node = foreign_key_field
            field_name = field_name_without_id

    field_node_type = field_node.type
    if field_node_type is None or not isinstance(field_node_type, Instance):
        raise LookupException(
            f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}'
        )

    if field_node_type.type.fullname() == 'builtins.object':
        # could be related manager
        related_manager_type = helpers.get_related_manager_type_from_metadata(
            model_type_info, field_name, api)
        if related_manager_type:
            model_arg = related_manager_type.args[0]
            if not isinstance(model_arg, Instance):
                raise LookupException(
                    f'When resolving lookup "{lookup}", could not determine type '
                    f'for {model_type_info.name()}.{field_name}')

            return RelatedModelNode(typ=model_arg, is_nullable=False)

    if helpers.is_foreign_key_like(field_node_type):
        field_type = helpers.extract_field_getter_type(field_node_type)
        is_nullable = helpers.is_optional(field_type)
        if is_nullable:
            # type is always non-optional
            field_type = helpers.make_required(field_type)

        if isinstance(field_type, Instance):
            return RelatedModelNode(typ=field_type, is_nullable=is_nullable)
        else:
            raise LookupException(
                f"Not an instance for field {field_type} lookup {lookup}")

    field_type = helpers.extract_field_getter_type(field_node_type)
    if field_type:
        return FieldNode(typ=field_type)

    # Not a Field
    if field_name == 'id':
        # If no 'id' field was found, use an int
        return FieldNode(api.named_generic_type('builtins.int', []))

    raise LookupException(
        f'When resolving lookup {lookup!r}, could not determine type for {model_type_info.name()}.{field_name}'
    )