예제 #1
0
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
    # Imported here because django isn't properly loaded yet when module is loaded
    from django.contrib.auth.base_user import AbstractBaseUser
    from django.contrib.auth.models import AnonymousUser

    abstract_base_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AbstractBaseUser)
    anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)

    # This shouldn't be able to happen, as we managed to import the models above.
    assert abstract_base_user_info is not None
    assert anonymous_user_info is not None

    if ctx.default_attr_type != UnionType([Instance(abstract_base_user_info, []), Instance(anonymous_user_info, [])]):
        # Type has been changed from the default in django-stubs.
        # I.e. HttpRequest has been subclassed and user-type overridden, so let's leave it as is.
        return ctx.default_attr_type

    auth_user_model = django_context.settings.AUTH_USER_MODEL
    user_cls = django_context.apps_registry.get_model(auth_user_model)
    user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)

    if user_info is None:
        return ctx.default_attr_type

    return UnionType([Instance(user_info, []), Instance(anonymous_user_info, [])])
예제 #2
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.get_field_related_model_cls(current_field)
    if related_model_cls is None:
        return AnyType(TypeOfAny.from_error)

    default_related_field_type = set_descriptor_types_for_field(ctx)

    # self reference with abstract=True on the model where ForeignKey is defined
    current_model_cls = current_field.model
    if (current_model_cls._meta.abstract
            and current_model_cls == related_model_cls):
        # for all derived non-abstract classes, set variable with this name to
        # __get__/__set__ of ForeignKey of derived model
        for model_cls in django_context.all_registered_model_classes:
            if issubclass(model_cls, current_model_cls) and not model_cls._meta.abstract:
                derived_model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
                if derived_model_info is not None:
                    fk_ref_type = Instance(derived_model_info, [])
                    derived_fk_type = reparametrize_related_field_type(default_related_field_type,
                                                                       set_type=fk_ref_type, get_type=fk_ref_type)
                    helpers.add_new_sym_for_info(derived_model_info,
                                                 name=current_field.name,
                                                 sym_type=derived_fk_type)

    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

    # replace Any with referred_to_type
    return reparametrize_related_field_type(default_related_field_type,
                                            set_type=related_model_to_set_type,
                                            get_type=related_model_type)
예제 #3
0
    def resolve_lookup_expected_type(self, ctx: MethodContext,
                                     model_cls: Type[Model],
                                     lookup: str) -> MypyType:
        query = Query(model_cls)
        try:
            lookup_parts, field_parts, is_expression = query.solve_lookup_type(
                lookup)
            if is_expression:
                return AnyType(TypeOfAny.explicit)
        except FieldError as exc:
            ctx.api.fail(exc.args[0], ctx.context)
            return AnyType(TypeOfAny.from_error)

        field = self._resolve_field_from_parts(field_parts, model_cls)

        lookup_cls = None
        if lookup_parts:
            lookup = lookup_parts[-1]
            lookup_cls = field.get_lookup(lookup)
            if lookup_cls is None:
                # unknown lookup
                return AnyType(TypeOfAny.explicit)

        if lookup_cls is None or isinstance(lookup_cls, Exact):
            return self.get_field_lookup_exact_type(
                helpers.get_typechecker_api(ctx), field)

        assert lookup_cls is not None

        lookup_info = helpers.lookup_class_typeinfo(
            helpers.get_typechecker_api(ctx), lookup_cls)
        if lookup_info is None:
            return AnyType(TypeOfAny.explicit)

        for lookup_base in helpers.iter_bases(lookup_info):
            if lookup_base.args and isinstance(lookup_base.args[0], Instance):
                lookup_type: MypyType = lookup_base.args[0]
                # if it's Field, consider lookup_type a __get__ of current field
                if (isinstance(lookup_type, Instance)
                        and lookup_type.type.fullname()
                        == fullnames.FIELD_FULLNAME):
                    field_info = helpers.lookup_class_typeinfo(
                        helpers.get_typechecker_api(ctx), field.__class__)
                    if field_info is None:
                        return AnyType(TypeOfAny.explicit)
                    lookup_type = helpers.get_private_descriptor_type(
                        field_info,
                        '_pyi_private_get_type',
                        is_nullable=field.null)
                return lookup_type

        return AnyType(TypeOfAny.explicit)
예제 #4
0
def _get_current_field_from_assignment(
        ctx: FunctionContext,
        django_context: DjangoContext) -> Optional[Field]:
    outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
    if (outer_model_info is None or not helpers.is_model_subclass_info(
            outer_model_info, django_context)):
        return None

    field_name = None
    for stmt in outer_model_info.defn.defs.body:
        if isinstance(stmt, AssignmentStmt):
            if stmt.rvalue == ctx.context:
                if not isinstance(stmt.lvalues[0], NameExpr):
                    return None
                field_name = stmt.lvalues[0].name
                break
    if field_name is None:
        return None

    model_cls = django_context.get_model_class_by_fullname(
        outer_model_info.fullname)
    if model_cls is None:
        return None

    current_field = model_cls._meta.get_field(field_name)
    return current_field
예제 #5
0
def get_field_type_from_lookup(
    ctx: MethodContext,
    django_context: DjangoContext,
    model_cls: Type[Model],
    *,
    method: str,
    lookup: str,
    silent_on_error: bool = False,
) -> Optional[MypyType]:
    try:
        lookup_field = django_context.resolve_lookup_into_field(
            model_cls, lookup)
    except FieldError as exc:
        if not silent_on_error:
            ctx.api.fail(exc.args[0], ctx.context)
        return None
    except LookupsAreUnsupported:
        return AnyType(TypeOfAny.explicit)

    if (isinstance(lookup_field, RelatedField)
            and lookup_field.column == lookup) or isinstance(
                lookup_field, ForeignObjectRel):
        related_model_cls = django_context.get_field_related_model_cls(
            lookup_field)
        if related_model_cls is None:
            return AnyType(TypeOfAny.from_error)
        lookup_field = django_context.get_primary_key_field(related_model_cls)

    field_get_type = django_context.get_field_get_type(
        helpers.get_typechecker_api(ctx), lookup_field, method=method)
    return field_get_type
예제 #6
0
def get_type_of_settings_attribute(ctx: AttributeContext,
                                   django_context: DjangoContext) -> MypyType:
    assert isinstance(ctx.context, MemberExpr)
    setting_name = ctx.context.name
    if not hasattr(django_context.settings, setting_name):
        ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}",
                     ctx.context)
        return ctx.default_attr_type

    typechecker_api = helpers.get_typechecker_api(ctx)

    # first look for the setting in the project settings file, then global settings
    settings_module = typechecker_api.modules.get(
        django_context.django_settings_module)
    global_settings_module = typechecker_api.modules.get(
        'django.conf.global_settings')
    for module in [settings_module, global_settings_module]:
        if module is not None:
            sym = module.names.get(setting_name)
            if sym is not None and sym.type is not None:
                return sym.type

    # if by any reason it isn't present there, get type from django settings
    value = getattr(django_context.settings, setting_name)
    value_fullname = helpers.get_class_fullname(value.__class__)

    value_info = helpers.lookup_fully_qualified_typeinfo(
        typechecker_api, value_fullname)
    if value_info is None:
        return ctx.default_attr_type

    return Instance(value_info, [])
예제 #7
0
def _get_field_instance(ctx: MethodContext, field_fullname: str) -> MypyType:
    field_info = helpers.lookup_fully_qualified_typeinfo(
        helpers.get_typechecker_api(ctx), field_fullname)
    if field_info is None:
        return AnyType(TypeOfAny.unannotated)
    return Instance(field_info,
                    [AnyType(TypeOfAny.explicit),
                     AnyType(TypeOfAny.explicit)])
예제 #8
0
def resolve_manager_method_from_instance(instance: Instance, method_name: str,
                                         ctx: AttributeContext) -> MypyType:
    api = helpers.get_typechecker_api(ctx)
    method_type = get_method_type_from_dynamic_manager(
        api, method_name,
        instance.type) or get_method_type_from_reverse_manager(
            api, method_name, instance.type)

    return method_type if method_type is not None else ctx.default_attr_type
예제 #9
0
def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
    default_return_type = ctx.default_return_type
    assert isinstance(default_return_type, Instance)

    outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
    if (outer_model_info is None
            or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
        return default_return_type

    return helpers.reparametrize_instance(default_return_type, [Instance(outer_model_info, [])])
예제 #10
0
def set_auth_user_model_as_type_for_request_user(
        ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
    auth_user_model = django_context.settings.AUTH_USER_MODEL
    model_cls = django_context.apps_registry.get_model(auth_user_model)
    model_info = helpers.lookup_class_typeinfo(
        helpers.get_typechecker_api(ctx), model_cls)
    if model_info is None:
        return ctx.default_attr_type

    return Instance(model_info, [])
예제 #11
0
def get_user_model_hook(ctx: FunctionContext,
                        django_context: DjangoContext) -> MypyType:
    auth_user_model = django_context.settings.AUTH_USER_MODEL
    model_cls = django_context.apps_registry.get_model(auth_user_model)
    model_cls_fullname = helpers.get_class_fullname(model_cls)

    model_info = helpers.lookup_fully_qualified_typeinfo(
        helpers.get_typechecker_api(ctx), model_cls_fullname)
    if model_info is None:
        return AnyType(TypeOfAny.unannotated)

    return TypeType(Instance(model_info, []))
예제 #12
0
def transform_into_proper_return_type(
        ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
    default_return_type = ctx.default_return_type
    assert isinstance(default_return_type, Instance)

    outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
    if (outer_model_info is None or
            not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME) and
            outer_model_info.fullname() not in helpers.get_all_model_mixins(
                helpers.get_typechecker_api(ctx))):
        # not inside models.Model class
        return ctx.default_return_type
    assert isinstance(outer_model_info, TypeInfo)

    if helpers.has_any_of_bases(default_return_type.type,
                                fullnames.RELATED_FIELDS_CLASSES):
        return fill_descriptor_types_for_related_field(ctx, django_context)

    if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
        return determine_type_of_array_field(ctx, django_context)

    return set_descriptor_types_for_field(ctx)
예제 #13
0
def set_auth_user_model_as_type_for_request_user(
        ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
    auth_user_model = django_context.settings.AUTH_USER_MODEL
    user_cls = django_context.apps_registry.get_model(auth_user_model)
    user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx),
                                              user_cls)

    if user_info is None:
        return ctx.default_attr_type

    # Imported here because django isn't properly loaded yet when module is loaded
    from django.contrib.auth.models import AnonymousUser

    anonymous_user_info = helpers.lookup_class_typeinfo(
        helpers.get_typechecker_api(ctx), AnonymousUser)
    if anonymous_user_info is None:
        # This shouldn't be able to happen, as we managed to import the model above...
        return Instance(user_info, [])

    return UnionType(
        [Instance(user_info, []),
         Instance(anonymous_user_info, [])])
예제 #14
0
def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
                               *, method: str, lookup: str) -> Optional[MypyType]:
    try:
        lookup_field = django_context.lookups_context.resolve_lookup(model_cls, lookup)
    except FieldError as exc:
        ctx.api.fail(exc.args[0], ctx.context)
        return None

    if isinstance(lookup_field, RelatedField) and lookup_field.column == lookup:
        lookup_field = django_context.get_primary_key_field(lookup_field.related_model)

    field_get_type = django_context.fields_context.get_field_get_type(helpers.get_typechecker_api(ctx),
                                                                      lookup_field, method=method)
    return field_get_type
예제 #15
0
def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
                             flat: bool, named: bool) -> MypyType:
    field_lookups = resolve_field_lookups(ctx.args[0], django_context)
    if field_lookups is None:
        return AnyType(TypeOfAny.from_error)

    typechecker_api = helpers.get_typechecker_api(ctx)
    if len(field_lookups) == 0:
        if flat:
            primary_key_field = django_context.get_primary_key_field(model_cls)
            lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
                                                     lookup=primary_key_field.attname, method='values_list')
            assert lookup_type is not None
            return lookup_type
        elif named:
            column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
            for field in django_context.get_model_fields(model_cls):
                column_type = django_context.get_field_get_type(typechecker_api, field,
                                                                method='values_list')
                column_types[field.attname] = column_type
            return helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
        else:
            # flat=False, named=False, all fields
            field_lookups = []
            for field in django_context.get_model_fields(model_cls):
                field_lookups.append(field.attname)

    if len(field_lookups) > 1 and flat:
        typechecker_api.fail("'flat' is not valid when 'values_list' is called with more than one field", ctx.context)
        return AnyType(TypeOfAny.from_error)

    column_types = OrderedDict()
    for field_lookup in field_lookups:
        lookup_field_type = get_field_type_from_lookup(ctx, django_context, model_cls,
                                                       lookup=field_lookup, method='values_list')
        if lookup_field_type is None:
            return AnyType(TypeOfAny.from_error)
        column_types[field_lookup] = lookup_field_type

    if flat:
        assert len(column_types) == 1
        row_type = next(iter(column_types.values()))
    elif named:
        row_type = helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
    else:
        row_type = helpers.make_tuple(typechecker_api, list(column_types.values()))

    return row_type
예제 #16
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)
예제 #17
0
def fail_if_manager_type_created_in_model_body(ctx: MethodContext) -> MypyType:
    """
    Method hook that checks if method `<Manager>.from_queryset` is called inside a model class body.

    Doing so won't, for instance, trigger the dynamic class hook(`create_new_manager_class_from_from_queryset_method`)
    for managers.
    """
    api = helpers.get_typechecker_api(ctx)
    outer_model_info = api.scope.active_class()
    if not outer_model_info or not outer_model_info.has_base(
            fullnames.MODEL_CLASS_FULLNAME):
        # Not inside a model class definition
        return ctx.default_return_type

    api.fail("`.from_queryset` called from inside model class body",
             ctx.context,
             code=errorcodes.MANAGER_UNTYPED)
    return ctx.default_return_type
예제 #18
0
def transform_into_proper_return_type(
        ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
    default_return_type = ctx.default_return_type
    assert isinstance(default_return_type, Instance)

    outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
    if outer_model_info is None or not helpers.is_model_subclass_info(
            outer_model_info, django_context):
        return ctx.default_return_type

    assert isinstance(outer_model_info, TypeInfo)

    if helpers.has_any_of_bases(default_return_type.type,
                                fullnames.RELATED_FIELDS_CLASSES):
        return fill_descriptor_types_for_related_field(ctx, django_context)

    if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
        return determine_type_of_array_field(ctx, django_context)

    return set_descriptor_types_for_field_callback(ctx, django_context)
예제 #19
0
def resolve_manager_method(ctx: AttributeContext) -> MypyType:
    """
    A 'get_attribute_hook' that is intended to be invoked whenever the TypeChecker encounters
    an attribute on a class that has 'django.db.models.BaseManager' as a base.
    """
    api = helpers.get_typechecker_api(ctx)
    # Skip (method) type that is currently something other than Any
    if not isinstance(ctx.default_attr_type, AnyType):
        return ctx.default_attr_type

    # (Current state is:) We wouldn't end up here when looking up a method from a custom _manager_.
    # That's why we only attempt to lookup the method for either a dynamically added or reverse manager.
    assert isinstance(ctx.context, MemberExpr)
    method_name = ctx.context.name
    manager_instance = ctx.type
    assert isinstance(manager_instance, Instance)
    method_type = get_method_type_from_dynamic_manager(
        api, method_name,
        manager_instance.type) or get_method_type_from_reverse_manager(
            api, method_name, manager_instance.type)

    return method_type if method_type is not None else ctx.default_attr_type
예제 #20
0
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext],
                           django_context: DjangoContext,
                           model_cls: Type[Model], method: str) -> MypyType:
    typechecker_api = helpers.get_typechecker_api(ctx)
    expected_types = django_context.get_expected_types(typechecker_api,
                                                       model_cls,
                                                       method=method)
    expected_keys = [key for key in expected_types.keys() if key != 'pk']

    for actual_name, actual_type in get_actual_types(ctx, expected_keys):
        if actual_name not in expected_types:
            ctx.api.fail(
                'Unexpected attribute "{}" for model "{}"'.format(
                    actual_name, model_cls.__name__), ctx.context)
            continue
        helpers.check_types_compatible(
            ctx,
            expected_type=expected_types[actual_name],
            actual_type=actual_type,
            error_message='Incompatible type for "{}" of "{}"'.format(
                actual_name, model_cls.__name__))

    return ctx.default_return_type
예제 #21
0
def get_values_list_row_type(
    ctx: MethodContext,
    django_context: DjangoContext,
    model_cls: Type[Model],
    *,
    is_annotated: bool,
    flat: bool,
    named: bool,
) -> MypyType:
    field_lookups = resolve_field_lookups(ctx.args[0], django_context)
    if field_lookups is None:
        return AnyType(TypeOfAny.from_error)

    typechecker_api = helpers.get_typechecker_api(ctx)
    if len(field_lookups) == 0:
        if flat:
            primary_key_field = django_context.get_primary_key_field(model_cls)
            lookup_type = get_field_type_from_lookup(
                ctx,
                django_context,
                model_cls,
                lookup=primary_key_field.attname,
                method="values_list")
            assert lookup_type is not None
            return lookup_type
        elif named:
            column_types: "OrderedDict[str, MypyType]" = OrderedDict()
            for field in django_context.get_model_fields(model_cls):
                column_type = django_context.get_field_get_type(
                    typechecker_api, field, method="values_list")
                column_types[field.attname] = column_type
            if is_annotated:
                # Return a NamedTuple with a fallback so that it's possible to access any field
                return helpers.make_oneoff_named_tuple(
                    typechecker_api,
                    "Row",
                    column_types,
                    extra_bases=[
                        typechecker_api.named_generic_type(
                            ANY_ATTR_ALLOWED_CLASS_FULLNAME, [])
                    ],
                )
            else:
                return helpers.make_oneoff_named_tuple(typechecker_api, "Row",
                                                       column_types)
        else:
            # flat=False, named=False, all fields
            if is_annotated:
                return typechecker_api.named_generic_type(
                    "builtins.tuple", [AnyType(TypeOfAny.special_form)])
            field_lookups = []
            for field in django_context.get_model_fields(model_cls):
                field_lookups.append(field.attname)

    if len(field_lookups) > 1 and flat:
        typechecker_api.fail(
            "'flat' is not valid when 'values_list' is called with more than one field",
            ctx.context)
        return AnyType(TypeOfAny.from_error)

    column_types = OrderedDict()
    for field_lookup in field_lookups:
        lookup_field_type = get_field_type_from_lookup(
            ctx,
            django_context,
            model_cls,
            lookup=field_lookup,
            method="values_list",
            silent_on_error=is_annotated)
        if lookup_field_type is None:
            if is_annotated:
                lookup_field_type = AnyType(TypeOfAny.from_omitted_generics)
            else:
                return AnyType(TypeOfAny.from_error)
        column_types[field_lookup] = lookup_field_type

    if flat:
        assert len(column_types) == 1
        row_type = next(iter(column_types.values()))
    elif named:
        row_type = helpers.make_oneoff_named_tuple(typechecker_api, "Row",
                                                   column_types)
    else:
        row_type = helpers.make_tuple(typechecker_api,
                                      list(column_types.values()))

    return row_type
예제 #22
0
def set_auth_user_model_boolean_fields(
        ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
    boolinfo = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx),
                                             bool)
    assert boolinfo is not None
    return Instance(boolinfo, [])