Django的Mypy存根
项目描述
此包包含类型存根和自定义mypy插件,以提供更精确的Django框架静态类型和类型推断。Django使用一些Python“魔法”,这使得某些代码模式的精确类型变得有问题。这就是为什么我们需要这个项目。最终目标是能够为大多数常见模式获取精确的类型。
安装
pip install 'django-stubs[compatible-mypy]'
要使mypy意识到该插件,您需要在
[mypy]
plugins =
mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = "myproject.settings"
您的mypy.ini
或setup.cfg
文件中添加。
也支持pyproject.toml配置
[tool.mypy]
plugins = ["mypy_django_plugin.main"]
[tool.django-stubs]
django_settings_module = "myproject.settings"
这里发生了两件事
- 我们需要显式列出要由
mypy
加载的插件 - 您可以选择指定如上所示的
django_settings_module
,或者让django_stubs
使用您的环境中的DJANGO_SETTINGS_MODULE
变量。
这个完全工作的类型模板可以作为示例。
版本兼容性
我们依赖于不同的django
和mypy
版本
django-stubs | Mypy版本 | Django版本 | Django部分支持 | Python版本 |
---|---|---|---|---|
5.1.0 | 1.11.x | 5.1 | 4.2 | 3.8 - 3.12 |
5.0.4 | 1.11.x | 5.0 | 4.2 | 3.8 - 3.12 |
5.0.3 | 1.11.x | 5.0 | 4.2 | 3.8 - 3.12 |
5.0.2 | 1.10.x | 5.0 | 4.2 | 3.8 - 3.12 |
5.0.1 | 1.10.x | 5.0 | 4.2 | 3.8 - 3.12 |
5.0.0 | 1.10.x | 5.0 | 4.2, 4.1 | 3.8 - 3.12 |
4.2.7 | 1.7.x | 4.2 | 4.1, 3.2 | 3.8 - 3.12 |
4.2.6 | 1.6.x | 4.2 | 4.1, 3.2 | 3.8 - 3.12 |
4.2.5 | 1.6.x | 4.2 | 4.1, 3.2 | 3.8 - 3.12 |
4.2.4 | 1.5.x | 4.2 | 4.1, 3.2 | 3.8 - 3.11 |
4.2.3 | 1.4.x | 4.2 | 4.1, 3.2 | 3.8 - 3.11 |
4.2.2 | 1.4.x | 4.2 | 4.1, 3.2 | 3.8 - 3.11 |
4.2.1 | 1.3.x | 4.2 | 4.1, 3.2 | 3.8 - 3.11 |
4.2.0 | 1.2.x | 4.2 | 4.1, 4.0, 3.2 | 3.7 - 3.11 |
1.16.0 | 1.1.x | 4.1 | 4.0, 3.2 | 3.7 - 3.11 |
1.15.0 | 1.0.x | 4.1 | 4.0, 3.2 | 3.7 - 3.11 |
1.14.0 | 0.990+ | 4.1 | 4.0, 3.2 | 3.7 - 3.11 |
功能
Model Meta属性的类型检查
通过从TypedModelMeta
类继承,您可以确保您正在使用正确的类型属性
from django.db import models
from django_stubs_ext.db.models import TypedModelMeta
class MyModel(models.Model):
example = models.CharField(max_length=100)
class Meta(TypedModelMeta):
ordering = ["example"]
constraints = [
models.UniqueConstraint(fields=["example"], name="unique_example"),
]
其他类型基类
django_stubs_ext.db.router.TypedDatabaseRouter
可以用作实现自定义数据库路由时的基类。
设置
django-stubs有几个设置,您可以在
pyproject.toml
中列出,在表[tool.django-stubs]
下mypy.ini
在表[mypy.plugins.django-stubs]
下
支持的设置有
-
django_settings_module
,一个字符串,默认为os.getenv(DJANGO_SETTINGS_MODULE)
。指定您的设置模块的导入路径,与Django的
DJANGO_SETTINGS_MODULE
环境变量相同。 -
strict_settings
,一个布尔值,默认为true
。
常见问题解答
这是一个官方的Django项目吗?
不是,目前我们独立于Django。有一个提议将我们的项目合并到Django本身。您可以通过点赞PR来表示您的支持。
在生产中使用这个安全吗?
是的,它是安全的!此项目根本不会影响您的运行时。它只影响mypy
类型检查过程。
但是,如果没有mypy
,使用此项目就没有意义。
当我安装此插件运行mypy时,它会崩溃
当前的实现使用Django的运行时来提取有关模型的信息,所以如果您的已安装的应用程序或models.py
损坏,它可能会崩溃。
换句话说,如果您的manage.py runserver
崩溃,mypy也会崩溃。您也可以使用mypy
的--tb
选项来获取有关错误的额外信息。
我无法使用带类型注解的QuerySet或Manager
当您尝试使用QuerySet[MyModel]
、Manager[MyModel]
或其他基于Django的通用类型时,您将得到一个TypeError: 'type' object is not subscriptable
错误。
这是因为这些Django类不支持运行时中的__class_getitem__
魔法方法。
-
您可以使用我们的
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=[您的所需类型])
添加额外的类型进行修补 -
您也可以使用字符串:
'QuerySet[MyModel]'
和'Manager[MyModel]'
,这样它将作为mypy
的类型在运行时作为常规str
。
我如何创建一个保证有已认证用户的 HttpRequest 呢?
Django 内置的 HttpRequest
具有名为 user
的属性,该属性解析为类型
Union[User, AnonymousUser]
其中 User
是由 AUTH_USER_MODEL
设置指定的用户模型。
如果您想要一个您可以注解类型的 HttpRequest
,并且您知道用户已认证,您可以像这样子类化正常的 HttpRequest
类
from django.http import HttpRequest
from my_user_app.models import MyUser
class AuthenticatedHttpRequest(HttpRequest):
user: MyUser
然后使用 AuthenticatedHttpRequest
代替标准的 HttpRequest
,在您知道用户已认证的情况下。例如在视图使用 @login_required
装饰器。
为什么我在自定义管理器上得到不兼容的返回类型错误?
如果您在没有泛型的情况下声明自定义管理器并重写内置方法,您可能会看到有关不兼容的错误消息,例如
from django.db import models
class MyManager(model.Manager):
def create(self, **kwargs) -> "MyModel":
pass
将导致此错误消息
错误:函数 "create" 的返回类型 "MyModel" 与超类型 "BaseManager" 中的 "_T" 不兼容
这是因为 Manager
类是泛型,但没有指定泛型,内置的管理器方法期望返回基管理器的泛型类型,即任何模型。要修复此问题,您应该声明您的管理器并使用您的模型作为类型变量
class MyManager(models.Manager["MyModel"]):
...
我如何注解我调用 QuerySet.annotate 的情况?
Django-stubs 提供了一个特殊类型,django_stubs_ext.WithAnnotations[Model, <Annotations>]
,表示 Model
已注解,意味着模型实例需要额外的属性。
您应该提供这些属性的 TypedDict
,例如 WithAnnotations[MyModel, MyTypedDict]
,以指定哪些注解属性存在。
目前,mypy 插件可以识别传递给 QuerySet.annotate
的特定名称,并将它们包含在类型中,但不会记录这些属性的类型。
对于模型被注解的具体字段的了解尚未用于为 QuerySet
的 values
、values_list
或 filter
方法创建更具体的类型,但是了解模型已被注解的事实已被用于为 values
/values_list
创建更广泛的结果类型,并允许对任何字段进行过滤。
from typing import TypedDict
from django_stubs_ext import WithAnnotations
from django.db import models
from django.db.models.expressions import Value
class MyModel(models.Model):
username = models.CharField(max_length=100)
class MyTypedDict(TypedDict):
foo: str
def func(m: WithAnnotations[MyModel, MyTypedDict]) -> str:
print(m.bar) # Error, since field "bar" is not in MyModel or MyTypedDict.
return m.foo # OK, since we said field "foo" was allowed
func(MyModel.objects.annotate(foo=Value("")).get(id=1)) # OK
func(MyModel.objects.annotate(bar=Value("")).get(id=1)) # Error
为什么我在不兼容的参数类型中提到 _StrPromise
?
Django 的懒翻译函数(如 gettext_lazy
)返回一个 Promise
而不是 str
。这两个类型 不能互换使用。因此,这些函数的返回类型已更改,以反映这一点。
如果您在自己的代码中遇到此错误,您可以将 Promise
强制转换为 str
(导致翻译被评估),或者在使用类型提示时使用 django-stubs-ext
中的 StrPromise
或 StrOrPromise
类型。您选择的解决方案取决于具体情况。有关更多信息,请参阅 Django 文档中的 使用懒翻译对象。
如果这是在 Django 代码中报告的,请报告问题或打开拉取请求以修复类型提示。
如何使用自定义库来处理 Django 设置?
使用类似于 django-split-settings
或 django-configurations
的东西将使 mypy 难以推断您的设置。
使用类似的情况也可能发生
try:
from .local_settings import *
except Exception:
pass
因此,mypy 不喜欢这段代码
from django.conf import settings
settings.CUSTOM_VALUE # E: 'Settings' object has no attribute 'CUSTOM_VALUE'
为了处理这种特殊情况,我们有一个特殊的设置 strict_settings
(默认值为 True
),您可以将它切换到 False
以始终返回 Any
并不抛出任何错误,如果运行时设置模块有给定的值,例如 pyproject.toml
[tool.django-stubs]
strict_settings = false
或者 mypy.ini
[mypy.plugins.django-stubs]
strict_settings = false
然后
# Works:
reveal_type(settings.EXISTS_AT_RUNTIME) # N: Any
# Errors:
reveal_type(settings.MISSING) # E: 'Settings' object has no attribute 'MISSING'
相关项目
awesome-python-typing
- Python 中所有与类型相关内容的精彩列表。djangorestframework-stubs
- Django REST Framework 的存根。pytest-mypy-plugins
- 我们用于测试mypy
存根和插件的pytest
插件。wemake-django-template
- 在几秒钟内创建新的类型化 Django 项目。
获取帮助
我们在这里有 Gitter: https://gitter.im/mypy-django/Lobby 如果您认为您有更通用的类型问题,请参考 https://github.com/python/mypy 和他们的 Gitter。
贡献
该项目是开源的,由社区驱动。因此,我们鼓励大家从小到大的贡献。您可以通过以下方式之一进行贡献
- 贡献代码(例如改进存根、添加插件功能、编写测试等) - 要这样做,请遵循 贡献指南。
- 协助代码审查和问题讨论。
- 识别错误和问题,并报告这些问题
- 在 StackOverflow 上提问和回答问题
您也可以在 gitter 上联系以讨论您的贡献!
项目详情
下载文件
下载您平台上的文件。如果您不确定要选择哪个,请了解有关 安装包 的更多信息。