drf 和 django
As you have already know I love optional static typing. The thing is that sometimes it is not optional, but impossible. Because we have plenty of big untyped projects in Python's ecosystem.
正如你已经 知道我爱可选的静态类型。 问题是,有时它不是可选的,但却是不可能的。 因为我们在Python的生态系统中有很多大型的无类型项目。
Django and Django-Rest-Framework were two of them. Were. Because now they can be typed! Let me introduce TypedDjango organisation and stubs for django
and drf
.
Django和Django-Rest-Framework是其中两个。 是。 因为现在可以输入了! 让我介绍django
和drf
TypedDjango组织和存根。
This is going to be a concise tutorial and getting started guide.
这将是一个简洁的教程和入门指南。
I want to say a big "thank you" to @mkurnikov for leading the project and to all contributors who made this possible. You are all awesome!
我要对@mkurnikov领导这个项目以及所有实现这一目标的贡献者表示由衷的感谢。 你们真棒!
In this article, I am showing how types work with django
and drf
. You can have a look at the result here.
在本文中,我将展示类型如何与django
和drf
。 您可以在这里查看结果 。
And you can also use wemake-django-template
to start your new projects with everything already configured. It will look exactly like the example project.
您还可以使用wemake-django-template
在所有已配置的项目中启动新项目。 它看起来与示例项目完全一样。
In this little tutorial, I will show you several features of django-stubs
and djangorestframework-stubs
in action. I hope this will convince you that having someone to doublecheck things after you is a good thing.
在这个小教程中,我将向您展示django-stubs
和djangorestframework-stubs
一些功能。 我希望这能使您相信,有人在您之后仔细检查事情是一件好事。
You can always refer to the original documentation. All the steps are also covered there.
您始终可以参考原始文档。 所有步骤也都包含在此处。
To start we will need a new project and a clean virtual environment, so we can install our dependencies:
首先,我们需要一个新项目和一个干净的虚拟环境 ,因此我们可以安装依赖项:
pip install django django-stubs mypy
Then we will need to configure mypy
correctly. It can be split into two steps. First, we configure the mypy
itself:
然后,我们将需要正确配置mypy
。 它可以分为两个步骤。 首先,我们配置mypy
本身:
# setup.cfg
[mypy]
# The mypy configurations: https://mypy.readthedocs.io/en/latest/config_file.html
python_version = 3.7
check_untyped_defs = True
disallow_any_generics = True
disallow_untyped_calls = True
disallow_untyped_decorators = True
ignore_errors = False
ignore_missing_imports = True
implicit_reexport = False
strict_optional = True
strict_equality = True
no_implicit_optional = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
warn_no_return = True
Then we configure django-stubs
plugin:
然后我们配置django-stubs
插件:
# setup.cfg
[mypy]
# Appending to `mypy` section:
plugins =
mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = server.settings
What do we do here?
我们在这里做什么?
We add a custom mypy
plugin to help the type checker guess types in some complicated Django-specific situations (like models, queryset, settings, etc)
我们添加了一个自定义的mypy
插件,以帮助类型检查器在某些特定于Django的复杂情况下(例如模型,queryset,设置等)猜测类型。
We also add custom configuration for django-stubs
to point it to the settings, we use for Django. It will need to import it.
我们还为django-stubs
添加了自定义配置,以将其指向我们用于Django的设置。 它将需要导入它。
The final result can be found here.
最终结果可以在这里找到。
We now have everything installed and configured. Let's type check things!
现在,我们已安装并配置了所有内容。 让我们输入检查内容!
Let's start with typing views as it is the easiest thing to do with this plugin.
让我们从键入视图开始,因为使用此插件最简单。
Here's our simple function-based view:
这是我们基于函数的简单视图:
# server/apps/main/views.py
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
def index(request: HttpRequest) -> HttpResponse:
reveal_type(request.is_ajax)
reveal_type(request.user)
return render(request, 'main/index.html')
Let's run and see what types it is aware of. Note that we might need to modify PYTHONPATH
, so mypy
would be able to import our project:
让我们运行,看看它知道什么类型。 请注意,我们可能需要修改PYTHONPATH
,以便mypy
能够导入我们的项目:
» PYTHONPATH="$PYTHONPATH:$PWD" mypy server
server/apps/main/views.py:14: note: Revealed type is 'def () -> builtins.bool'
server/apps/main/views.py:15: note: Revealed type is 'django.contrib.auth.models.User'
Let's try to break something:
让我们尝试破坏一些东西:
# server/apps/main/views.py
def index(request: HttpRequest) -> HttpResponse:
return render(request.META, 'main/index.html')
Nope, there's a typo and mypy
will catch it:
不,有错别字, mypy
会抓住它:
» PYTHONPATH="$PYTHONPATH:$PWD" mypy server
server/apps/main/views.py:18: error: Argument 1 to "render" has incompatible type "Dict[str, Any]"; expected "HttpRequest"
It works! Ok, but that is pretty straight-forward. Let's complicate our example a little bit and create a custom model to show how can we type models and querysets.
有用! 好的,但这很简单。 让我们将示例复杂一点,并创建一个自定义模型,以展示如何键入模型和查询集。
Django's ORM is a killer-feature. It is very flexible and dynamic. It also means that it is hard to type. Let's see some features that are already covered by django-stubs
.
Django的ORM是杀手级功能。 它非常灵活和动态。 这也意味着很难键入。 让我们看看django-stubs
已经涵盖的一些功能。
Our model definition:
我们的模型定义:
# server/apps/main/models.py
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class BlogPost(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
reveal_type(self.id) # example reveal of all fields in a model
reveal_type(self.author)
reveal_type(self.text)
reveal_type(self.is_published)
reveal_type(self.created_at)
return '<BlogPost {0}>'.format(self.id)
And every field of this model is covered by django-stubs
. Let's see what types are revealed:
django-stubs
覆盖了该模型的每个领域。 让我们看看揭示了哪些类型:
» PYTHONPATH="$PYTHONPATH:$PWD" mypy server
server/apps/main/models.py:21: note: Revealed type is 'builtins.int*'
server/apps/main/models.py:22: note: Revealed type is 'django.contrib.auth.models.User*'
server/apps/main/models.py:23: note: Revealed type is 'builtins.str*'
server/apps/main/models.py:24: note: Revealed type is 'builtins.bool*'
server/apps/main/models.py:25: note: Revealed type is 'datetime.datetime*'
Everything looks good! django-stubs
provides a custom mypy
plugin to convert model fields into correct instance types. That's why all types are correctly revealed.
一切看起来都不错! django-stubs
提供了一个自定义的mypy
插件,可将模型字段转换为正确的实例类型。 这就是所有类型都能正确显示的原因。
The second big feature of django-stubs
plugin is that we can type QuerySet
:
django-stubs
插件的第二大功能是我们可以输入QuerySet
:
# server/apps/main/logic/repo.py
from django.db.models.query import QuerySet
from server.apps.main.models import BlogPost
def published_posts() -> 'QuerySet[BlogPost]': # works fine!
return BlogPost.objects.filter(
is_published=True,
)
And here's how it can be checked:
以及如何检查它:
reveal_type(published_posts().first())
# => Union[server.apps.main.models.BlogPost*, None]
We can even annotate querysets with .values()
and .values_list()
calls. This plugin is smart!
我们甚至可以使用.values()
和.values_list()
调用注释.values()
.values_list()
。 这个插件很聪明!
I have struggled with annotating methods returning QuerySet
s for several years. This feature solves a big problem for me: no more Iterable[BlogPost]
or List[User]
. I can now use real types.
几年来,我一直在使用注释方法来返回QuerySet
。 此功能为我解决了一个大问题:不再有Iterable[BlogPost]
或List[User]
。 我现在可以使用实型。
But, typing views, models, forms, commands, urls, and admin is not all we have. TypedDjango also has typings for djangorestframework
. Let's install and configure it:
但是,键入视图,模型,表单,命令,URL和admin并不是我们的全部。 TypedDjango也有djangorestframework
。 让我们安装和配置它:
pip install djangorestframework djangorestframework-stubs
And we can start to create serializers:
我们可以开始创建序列化器:
# server/apps/main/serializers.py
from rest_framework import serializers
from server.apps.main.models import BlogPost, User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email']
class BlogPostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer()
class Meta:
model = BlogPost
fields = ['author', 'text', 'is_published', 'created_at']
Views:
观看次数:
# server/apps/main/views.py
from rest_framework import viewsets
from server.apps.main.serializers import BlogPostSerializer
from server.apps.main.models import BlogPost
class BlogPostViewset(viewsets.ModelViewSet):
serializer_class = BlogPostSerializer
queryset = BlogPost.objects.all()
And routers:
和路由器:
# server/apps/main/urls.py
from django.urls import path, include
from rest_framework import routers
from server.apps.main.views import BlogPostViewset, index
router = routers.DefaultRouter()
router.register(r'posts', BlogPostViewset)
urlpatterns = [
path('', include(router.urls)),
# ...
]
It does not even look like something has changed, but everything inside is typed: settings, serializers, viewsets, and routers. It will allow you to incrementally add typings where you need them the most.
它看起来甚至没有什么改变,但是里面的所有东西都被键入:设置,序列化器,视图集和路由器。 它将允许您在最需要它们的地方逐步添加类型。
Let's try to change queryset = BlogPost.objects.all()
to queryset = [1, 2, 3]
in our views:
让我们尝试在queryset = BlogPost.objects.all()
更改为queryset = [1, 2, 3]
:
» PYTHONPATH="$PYTHONPATH:$PWD" mypy server
server/apps/main/views.py:25: error: Incompatible types in assignment (expression has type "List[int]", base class "GenericAPIView" defined the type as "Optional[QuerySet[Any]]")
No, it won't work! Fix your code!
不,这行不通! 修正您的代码!
Typing the framework interfaces is an awesome thing to have. When combined with tools like returns
and mappers
it will allow writing type-safe and declarative business logic wrapped into typed framework interfaces. And to decrease the number of errors in the layer between these two.
键入框架接口是一件很棒的事情。 当与诸如returns
和mappers
之类的工具结合使用时,它将允许编写封装在类型化框架接口中的类型安全和声明性业务逻辑。 并减少这两者之间的层中的错误数量。
Optional gradual static typing also allows you to start fast and add types only when your API is stabilized or go with types-driven development from the very start.
可选的渐进式静态类型还使您能够快速启动并仅在API稳定后才添加类型,或者从一开始就进行类型驱动的开发。
However, django-stubs
and djangorestframework-stubs
are new projects. There are still a lot of bugs, planned features, missing type specs. We welcome every contribution from the community to make the developer tooling in Python truly awesome.
但是, django-stubs
和djangorestframework-stubs
是新项目。 仍然有很多错误,计划中的功能,缺少类型说明。 我们欢迎社区做出的一切贡献,以使Python开发人员工具真正地变得很棒。
Originally published at my blog.
最初发布在我的博客上 。
drf 和 django