예제 #1
0
def reparametrize_related_field_type(related_field_type: Instance, set_type,
                                     get_type) -> Instance:
    args = [
        helpers.convert_any_to_type(related_field_type.args[0], set_type),
        helpers.convert_any_to_type(related_field_type.args[1], get_type),
    ]
    return helpers.reparametrize_instance(related_field_type, new_args=args)
예제 #2
0
    def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method: str) -> Dict[str, MypyType]:
        contenttypes_in_apps = self.apps_registry.is_installed("django.contrib.contenttypes")
        if contenttypes_in_apps:
            from django.contrib.contenttypes.fields import GenericForeignKey

        expected_types = {}
        # add pk if not abstract=True
        if not model_cls._meta.abstract:
            primary_key_field = self.get_primary_key_field(model_cls)
            field_set_type = self.get_field_set_type(api, primary_key_field, method=method)
            expected_types["pk"] = field_set_type

        for field in model_cls._meta.get_fields():
            if isinstance(field, Field):
                field_name = field.attname
                field_set_type = self.get_field_set_type(api, field, method=method)
                expected_types[field_name] = field_set_type

                if isinstance(field, ForeignKey):
                    field_name = field.name
                    foreign_key_info = helpers.lookup_class_typeinfo(api, field.__class__)
                    if foreign_key_info is None:
                        # maybe there's no type annotation for the field
                        expected_types[field_name] = AnyType(TypeOfAny.unannotated)
                        continue

                    related_model = self.get_field_related_model_cls(field)
                    if related_model is None:
                        expected_types[field_name] = AnyType(TypeOfAny.from_error)
                        continue

                    if related_model._meta.proxy_for_model is not None:
                        related_model = related_model._meta.proxy_for_model

                    related_model_info = helpers.lookup_class_typeinfo(api, related_model)
                    if related_model_info is None:
                        expected_types[field_name] = AnyType(TypeOfAny.unannotated)
                        continue

                    is_nullable = self.get_field_nullability(field, method)
                    foreign_key_set_type = helpers.get_private_descriptor_type(
                        foreign_key_info, "_pyi_private_set_type", is_nullable=is_nullable
                    )
                    model_set_type = helpers.convert_any_to_type(foreign_key_set_type, Instance(related_model_info, []))

                    expected_types[field_name] = model_set_type

            elif contenttypes_in_apps and isinstance(field, GenericForeignKey):
                # it's generic, so cannot set specific model
                field_name = field.name
                gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
                if gfk_info is None:
                    gfk_set_type = AnyType(TypeOfAny.unannotated)
                else:
                    gfk_set_type = helpers.get_private_descriptor_type(
                        gfk_info, "_pyi_private_set_type", is_nullable=True
                    )
                expected_types[field_name] = gfk_set_type

        return expected_types
예제 #3
0
    def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *,
                           method: str) -> Dict[str, MypyType]:
        from django.contrib.contenttypes.fields import GenericForeignKey

        expected_types = {}
        # add pk
        primary_key_field = self.get_primary_key_field(model_cls)
        field_set_type = self.fields_context.get_field_set_type(
            api, primary_key_field, method=method)
        expected_types['pk'] = field_set_type

        for field in model_cls._meta.get_fields():
            if isinstance(field, Field):
                field_name = field.attname
                field_set_type = self.fields_context.get_field_set_type(
                    api, field, method=method)
                expected_types[field_name] = field_set_type

                if isinstance(field, ForeignKey):
                    field_name = field.name
                    foreign_key_info = helpers.lookup_class_typeinfo(
                        api, field.__class__)
                    if foreign_key_info is None:
                        # maybe there's no type annotation for the field
                        expected_types[field_name] = AnyType(
                            TypeOfAny.unannotated)
                        continue

                    related_model = field.related_model
                    if related_model._meta.proxy_for_model:
                        related_model = field.related_model._meta.proxy_for_model

                    related_model_info = helpers.lookup_class_typeinfo(
                        api, related_model)
                    if related_model_info is None:
                        expected_types[field_name] = AnyType(
                            TypeOfAny.unannotated)
                        continue

                    is_nullable = self.fields_context.get_field_nullability(
                        field, method)
                    foreign_key_set_type = helpers.get_private_descriptor_type(
                        foreign_key_info,
                        '_pyi_private_set_type',
                        is_nullable=is_nullable)
                    model_set_type = helpers.convert_any_to_type(
                        foreign_key_set_type, Instance(related_model_info, []))

                    expected_types[field_name] = model_set_type

            elif isinstance(field, GenericForeignKey):
                # it's generic, so cannot set specific model
                field_name = field.name
                gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
                gfk_set_type = helpers.get_private_descriptor_type(
                    gfk_info, '_pyi_private_set_type', is_nullable=True)
                expected_types[field_name] = gfk_set_type

        return expected_types
예제 #4
0
def fill_descriptor_types_for_related_field(
        ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
    current_field = _get_current_field_from_assignment(ctx, django_context)
    if current_field is None:
        return AnyType(TypeOfAny.from_error)

    assert isinstance(current_field, RelatedField)

    related_model_cls = django_context.fields_context.get_related_model_cls(
        current_field)

    related_model = related_model_cls
    related_model_to_set = related_model_cls
    if related_model_to_set._meta.proxy_for_model is not None:
        related_model_to_set = related_model_to_set._meta.proxy_for_model

    typechecker_api = helpers.get_typechecker_api(ctx)

    related_model_info = helpers.lookup_class_typeinfo(typechecker_api,
                                                       related_model)
    if related_model_info is None:
        # maybe no type stub
        related_model_type = AnyType(TypeOfAny.unannotated)
    else:
        related_model_type = Instance(related_model_info, [])  # type: ignore

    related_model_to_set_info = helpers.lookup_class_typeinfo(
        typechecker_api, related_model_to_set)
    if related_model_to_set_info is None:
        # maybe no type stub
        related_model_to_set_type = AnyType(TypeOfAny.unannotated)
    else:
        related_model_to_set_type = Instance(related_model_to_set_info,
                                             [])  # type: ignore

    default_related_field_type = set_descriptor_types_for_field(ctx)
    # replace Any with referred_to_type
    args = [
        helpers.convert_any_to_type(default_related_field_type.args[0],
                                    related_model_to_set_type),
        helpers.convert_any_to_type(default_related_field_type.args[1],
                                    related_model_type),
    ]
    return helpers.reparametrize_instance(default_related_field_type,
                                          new_args=args)
예제 #5
0
def determine_type_of_array_field(ctx: FunctionContext,
                                  django_context: DjangoContext) -> MypyType:
    default_return_type = set_descriptor_types_for_field(ctx)

    base_field_arg_type = helpers.get_call_argument_type_by_name(
        ctx, "base_field")
    if not base_field_arg_type or not isinstance(base_field_arg_type,
                                                 Instance):
        return default_return_type

    def drop_combinable(_type: MypyType) -> Optional[MypyType]:
        if isinstance(_type, Instance) and _type.type.has_base(
                fullnames.COMBINABLE_EXPRESSION_FULLNAME):
            return None
        elif isinstance(_type, UnionType):
            items_without_combinable = []
            for item in _type.items:
                reduced = drop_combinable(item)
                if reduced is not None:
                    items_without_combinable.append(reduced)

            if len(items_without_combinable) > 1:
                return UnionType(
                    items_without_combinable,
                    line=_type.line,
                    column=_type.column,
                    is_evaluated=_type.is_evaluated,
                    uses_pep604_syntax=_type.uses_pep604_syntax,
                )
            elif len(items_without_combinable) == 1:
                return items_without_combinable[0]
            else:
                return None

        return _type

    # Both base_field and return type should derive from Field and thus expect 2 arguments
    assert len(base_field_arg_type.args) == len(default_return_type.args) == 2
    args = []
    for new_type, default_arg in zip(base_field_arg_type.args,
                                     default_return_type.args):
        # Drop any base_field Combinable type
        reduced = drop_combinable(new_type)
        if reduced is None:
            ctx.api.fail(
                f"Can't have ArrayField expecting {fullnames.COMBINABLE_EXPRESSION_FULLNAME!r} as data type",
                ctx.context,
            )
        else:
            new_type = reduced

        args.append(helpers.convert_any_to_type(default_arg, new_type))

    return helpers.reparametrize_instance(default_return_type, args)
예제 #6
0
def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
    default_return_type = set_descriptor_types_for_field(ctx)

    base_field_arg_type = helpers.get_call_argument_type_by_name(ctx, 'base_field')
    if not base_field_arg_type or not isinstance(base_field_arg_type, Instance):
        return default_return_type

    base_type = base_field_arg_type.args[1]  # extract __get__ type
    args = []
    for default_arg in default_return_type.args:
        args.append(helpers.convert_any_to_type(default_arg, base_type))

    return helpers.reparametrize_instance(default_return_type, args)
예제 #7
0
    def get_field_set_type(self, api: TypeChecker, field: Field, *, method: str) -> MypyType:
        """ Get a type of __set__ for this specific Django field. """
        target_field = field
        if isinstance(field, ForeignKey):
            target_field = field.target_field

        field_info = helpers.lookup_class_typeinfo(api, target_field.__class__)
        if field_info is None:
            return AnyType(TypeOfAny.from_error)

        field_set_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_set_type',
                                                             is_nullable=self.get_field_nullability(field, method))
        if isinstance(target_field, ArrayField):
            argument_field_type = self.get_field_set_type(api, target_field.base_field, method=method)
            field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
        return field_set_type
예제 #8
0
    def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *,
                           method: str) -> Dict[str, MypyType]:
        contenttypes_in_apps = self.apps_registry.is_installed(
            "django.contrib.contenttypes")
        if contenttypes_in_apps:
            from django.contrib.contenttypes.fields import GenericForeignKey

        expected_types = {}
        # add pk if not abstract=True
        if not model_cls._meta.abstract:
            primary_key_field = self.get_primary_key_field(model_cls)
            field_set_type = self.get_field_set_type(api,
                                                     primary_key_field,
                                                     method=method)
            expected_types["pk"] = field_set_type

        def get_field_set_type_from_model_type_info(
                info: Optional[TypeInfo],
                field_name: str) -> Optional[MypyType]:
            if info is None:
                return None
            field_node = info.names.get(field_name)
            if field_node is None or not isinstance(field_node.type, Instance):
                return None
            elif not field_node.type.args:
                # Field declares a set and a get type arg. Fallback to `None` when we can't find any args
                return None

            set_type = field_node.type.args[0]
            return set_type

        model_info = helpers.lookup_class_typeinfo(api, model_cls)
        for field in model_cls._meta.get_fields():
            if isinstance(field, Field):
                field_name = field.attname
                # Try to retrieve set type from a model's TypeInfo object and fallback to retrieving it manually
                # from django-stubs own declaration. This is to align with the setter types declared for
                # assignment.
                field_set_type = get_field_set_type_from_model_type_info(
                    model_info, field_name) or self.get_field_set_type(
                        api, field, method=method)
                expected_types[field_name] = field_set_type

                if isinstance(field, ForeignKey):
                    field_name = field.name
                    foreign_key_info = helpers.lookup_class_typeinfo(
                        api, field.__class__)
                    if foreign_key_info is None:
                        # maybe there's no type annotation for the field
                        expected_types[field_name] = AnyType(
                            TypeOfAny.unannotated)
                        continue

                    related_model = self.get_field_related_model_cls(field)
                    if related_model is None:
                        expected_types[field_name] = AnyType(
                            TypeOfAny.from_error)
                        continue

                    if related_model._meta.proxy_for_model is not None:
                        related_model = related_model._meta.proxy_for_model

                    related_model_info = helpers.lookup_class_typeinfo(
                        api, related_model)
                    if related_model_info is None:
                        expected_types[field_name] = AnyType(
                            TypeOfAny.unannotated)
                        continue

                    is_nullable = self.get_field_nullability(field, method)
                    foreign_key_set_type = helpers.get_private_descriptor_type(
                        foreign_key_info,
                        "_pyi_private_set_type",
                        is_nullable=is_nullable)
                    model_set_type = helpers.convert_any_to_type(
                        foreign_key_set_type, Instance(related_model_info, []))

                    expected_types[field_name] = model_set_type

            elif contenttypes_in_apps and isinstance(field, GenericForeignKey):
                # it's generic, so cannot set specific model
                field_name = field.name
                gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
                if gfk_info is None:
                    gfk_set_type: MypyType = AnyType(TypeOfAny.unannotated)
                else:
                    gfk_set_type = helpers.get_private_descriptor_type(
                        gfk_info, "_pyi_private_set_type", is_nullable=True)
                expected_types[field_name] = gfk_set_type

        return expected_types