drf 和 django_对Django和DRF进行类型检查

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是其中两个。 是。 因为现在可以输入了! 让我介绍djangodrf TypedDjango组织和存根。

This is going to be a concise tutorial and getting started guide.

这将是一个简洁的教程和入门指南。

荣誉 (Kudos)

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领导这个项目以及所有实现这一目标的贡献者表示由衷的感谢。 你们真棒!

TLDR (TLDR)

In this article, I am showing how types work with django and drf. You can have a look at the result here.

在本文中,我将展示类型如何与djangodrf 。 您可以在这里查看结果

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在所有已配置的项目中启动新项目。 它看起来与示例项目完全一样。

入门 (Getting started)

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-stubsdjangorestframework-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?

我们在这里做什么?

  1. 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,设置等)猜测类型。

  2. 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!

现在,我们已安装并配置了所有内容。 让我们输入检查内容!

类型检查视图 (Typechecking views)

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.

有用! 好的,但这很简单。 让我们将示例复杂一点,并创建一个自定义模型,以展示如何键入模型和查询集。

类型检查模型和查询集 (Typechecking models and queryset)

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 QuerySets 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] 。 我现在可以使用实型。

类型检查API (Typechecking APIs)

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!

不,这行不通! 修正您的代码!

结论 (Conclusion)

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.

键入框架接口是一件很棒的事情。 当与诸如returnsmappers之类的工具结合使用时,它将允许编写封装在类型化框架接口中的类型安全和声明性业务逻辑。 并减少这两者之间的层中的错误数量。

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-stubsdjangorestframework-stubs是新项目。 仍然有很多错误,计划中的功能,缺少类型说明。 我们欢迎社区做出的一切贡献,以使Python开发人员工具真正地变得很棒。

Originally published at my blog.

最初发布在我的博客上

翻译自: https://habr.com/en/post/465007/

drf 和 django

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值