跳转到主要内容

Django的类型存根

项目描述

django-types PyPI

Django的类型存根。

注意:该项目是从https://github.com/typeddjango/django-stubs分叉的,目的是移除对mypy插件的依赖,这样mypy不会因为Django配置而崩溃,并且非mypy类型检查器,如pyright,将与Django更好地协同工作。

安装

pip install django-types

当您尝试使用QuerySet[MyModel]Manager[MyModel]或其他基于Django的通用类型时,您将得到TypeError: 'type' object is not subscriptable错误。

这种情况发生是因为这些Django类不支持运行时中的__class_getitem__魔法方法。

  1. 您可以使用django_stubs_ext辅助工具,它会修补我们在django中使用的所有类型。

    安装它

    pip install django-stubs-ext  # as a production dependency
    

    然后将它放置在您的顶级设置中

    import django_stubs_ext
    
    django_stubs_ext.monkeypatch()
    

    您可以使用django_stubs_ext.monkeypatch(extra_classes=[YourDesiredType])添加额外的类型来修补

  2. 您也可以使用字符串:'QuerySet[MyModel]''Manager[MyModel]',这样它就可以作为类型检查的类型,并在运行时作为常规的str

用法

ORM模型中的外键ids和相关名称作为属性

当定义一个具有外键的Django ORM模型时,如下所示

class User(models.Model):
    team = models.ForeignKey(
        "Team",
        null=True,
        on_delete=models.SET_NULL,
    )
    role = models.ForeignKey(
        "Role",
        null=True,
        on_delete=models.SET_NULL,
        related_name="users",
    )

将创建两个属性,即预期的teamteam_id。另外,在Team上创建了一个相关管理器user_set,用于反向访问。

为了正确地为外键本身以及创建的ids添加类型,您可以这样做

from typing import TYPE_CHECKING

from someapp.models import Team
if TYPE_CHECKING:
    # In this example Role cannot be imported due to circular import issues,
    # but doing so inside TYPE_CHECKING will make sure that the typing below
    # knows what "Role" means
    from anotherapp.models import Role


class User(models.Model):
    team_id: Optional[int]
    team = models.ForeignKey(
        Team,
        null=True,
        on_delete=models.SET_NULL,
    )
    role_id: int
    role = models.ForeignKey["Role"](
        "Role",
        null=False,
        on_delete=models.SET_NULL,
        related_name="users",
    )


reveal_type(User().team)
# note: Revealed type is 'Optional[Team]'
reveal_type(User().role)
# note: Revealed type is 'Role'

这将确保可以访问team_idrole_id。此外,teamrole将被类型化为它们相应的对象。

要能够访问相关管理器TeamRole,您可以这样做

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    # This doesn't really exists on django so it always need to be imported this way
    from django.db.models.manager import RelatedManager
    from user.models import User


class Team(models.Model):
    if TYPE_CHECKING:
        user_set = RelatedManager["User"]()


class Role(models.Model):
    if TYPE_CHECKING:
        users = RelatedManager["User"]()

reveal_type(Team().user_set)
# note: Revealed type is 'RelatedManager[User]'
reveal_type(Role().users)
# note: Revealed type is 'RelatedManager[User]'

另一种选择是使用注解

from __future__ import annotations  # or just be in python 3.11

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from django.db.models import Manager
    from user.models import User


class Team(models.Model):
    user_set: Manager[User]


class Role(models.Model):
    users: Manager[User]

reveal_type(Team().user_set)
# note: Revealed type is 'Manager[User]'
reveal_type(Role().users)
# note: Revealed type is 'Manager[User]'

id字段

默认情况下,如果不存在,Django会为您创建一个AutoField

为了让类型检查器知道关于id字段的信息,您需要显式地声明该字段。

# before
class Post(models.Model):
    ...

# after
class Post(models.Model):
    id = models.AutoField(primary_key=True)
    # OR
    id: int

HttpRequestuser属性

HttpRequestuser属性的类型为Union[AbstractBaseUser, AnonymousUser],但对于您的大多数视图,您可能只想有一个认证用户或一个AnonymousUser

因此,我们可以为每种情况定义一个子类

class AuthedHttpRequest(HttpRequest):
    user: User  # type: ignore [assignment]

然后您可以在视图中使用它

@auth.login_required
def activity(request: AuthedHttpRequest, team_id: str) -> HttpResponse:
    ...

您还可以通过使login_required装饰器更严格来确保其装饰的函数的第一个参数是AuthedHttpRequest

from typing import Any, Union, TypeVar, cast
from django.http import HttpRequest, HttpResponse
from typing_extensions import Protocol
from functools import wraps

class RequestHandler1(Protocol):
    def __call__(self, request: AuthedHttpRequest) -> HttpResponse:
        ...


class RequestHandler2(Protocol):
    def __call__(self, request: AuthedHttpRequest, __arg1: Any) -> HttpResponse:
        ...


RequestHandler = Union[RequestHandler1, RequestHandler2]


# Verbose bound arg due to limitations of Python typing.
# see: https://github.com/python/mypy/issues/5876
_F = TypeVar("_F", bound=RequestHandler)


def login_required(view_func: _F) -> _F:
    @wraps(view_func)
    def wrapped_view(
        request: AuthedHttpRequest, *args: object, **kwargs: object
    ) -> HttpResponse:
        if request.user.is_authenticated:
            return view_func(request, *args, **kwargs)  # type: ignore [call-arg]
        raise AuthenticationRequired

    return cast(_F, wrapped_view)

然后下面的将会产生类型错误

@auth.login_required
def activity(request: HttpRequest, team_id: str) -> HttpResponse:
    ...

相关

项目详情


下载文件

下载您平台上的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

django_types-0.19.1.tar.gz (175.8 kB 查看哈希)

上传时间

构建分布

django_types-0.19.1-py3-none-any.whl (370.9 kB 查看哈希值)

上传时间 Python 3

支持