Digitando Django e DRF

Como você sabe, eu amo a digitação estática opcional. O fato é que, às vezes, não é opcional, mas impossível. Porque temos muitos projetos grandes e não tipados no ecossistema do Python.


Django e Django-Rest-Framework eram dois deles. Foram. Porque agora eles podem ser digitados! Deixe-me apresentar a organização e stubs do drf django para django e drf .


Este será um tutorial conciso e um guia de introdução.


Kudos


Quero dizer um grande "obrigado" a @mkurnikov por liderar o projeto e a todos os colaboradores que tornaram isso possível. Vocês são todos incríveis!


TLDR


Neste artigo, estou mostrando como os tipos funcionam com django e drf . Você pode dar uma olhada no resultado aqui .


E você também pode usar o wemake-django-template para iniciar seus novos projetos com tudo já configurado. Será exatamente igual ao projeto de exemplo.


Introdução


Neste pequeno tutorial, mostrarei vários recursos de django-stubs e djangorestframework-stubs em ação. Espero que isso o convença de que é bom ter alguém para checar as coisas depois de você.


Você sempre pode consultar a documentação original. Todas as etapas também são abordadas lá.


Para começar, precisaremos de um novo projeto e de um ambiente virtual limpo , para que possamos instalar nossas dependências:


 pip install django django-stubs mypy 

Em seguida, precisaremos configurar o mypy corretamente. Pode ser dividido em duas etapas. Primeiro, configuramos o 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 

Então configuramos o plugin django-stubs :


 # setup.cfg [mypy] # Appending to `mypy` section: plugins = mypy_django_plugin.main [mypy.plugins.django-stubs] django_settings_module = server.settings 

O que fazemos aqui?


  1. Adicionamos um plug - in mypy personalizado para ajudar o verificador de tipos a adivinhar tipos em algumas situações complicadas específicas do Django (como modelos, conjunto de consultas, configurações etc.)
  2. Também adicionamos uma configuração personalizada para django-stubs para apontar para as configurações que usamos para o Django. Será necessário importá-lo.

O resultado final pode ser encontrado aqui .


Agora temos tudo instalado e configurado. Vamos digitar verificar as coisas!


Exibições de digitação


Vamos começar digitando visualizações, pois é a coisa mais fácil de fazer com este plugin.


Aqui está nossa visão simples baseada em funções:


 # 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') 

Vamos correr e ver de que tipos ele conhece. Observe que talvez seja necessário modificar o PYTHONPATH , para que o mypy possa importar nosso projeto:


 » 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' 

Vamos tentar quebrar algo:


 # server/apps/main/views.py def index(request: HttpRequest) -> HttpResponse: return render(request.META, 'main/index.html') 

Não, há um erro de digitação e mypy vai pegá-lo:


 » PYTHONPATH="$PYTHONPATH:$PWD" mypy server server/apps/main/views.py:18: error: Argument 1 to "render" has incompatible type "Dict[str, Any]"; expected "HttpRequest" 

Isso funciona! Ok, mas isso é bem direto. Vamos complicar um pouco nosso exemplo e criar um modelo personalizado para mostrar como podemos digitar modelos e conjuntos de consultas.


Modelos de verificação de tipos e conjunto de consultas


O ORM do Django é um recurso matador. É muito flexível e dinâmico. Isso também significa que é difícil digitar. Vamos ver alguns recursos que já são cobertos pelo django-stubs .


Nossa definição de modelo:


 # 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) 

E todos os campos deste modelo são cobertos por django-stubs . Vamos ver quais tipos são revelados:


 » 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*' 

Tudo parece bom! django-stubs fornece um plugin mypy personalizado para converter campos de modelo em tipos de instância corretos. É por isso que todos os tipos são revelados corretamente.


A segunda grande característica do plugin django-stubs é que podemos digitar 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, ) 

E aqui está como isso pode ser verificado:


 reveal_type(published_posts().first()) # => Union[server.apps.main.models.BlogPost*, None] 

Podemos até anotar conjuntos de .values() com .values() e .values_list() . Este plugin é inteligente!


Eu lutei com métodos de anotação retornando QuerySet s por vários anos. Esse recurso resolve um grande problema para mim: não há mais Iterable[BlogPost] ou List[User] . Agora posso usar tipos reais.


APIs de digitação automática


Porém, digitar visualizações, modelos, formulários, comandos, URLs e admin não é tudo o que temos. TypedDjango também possui djangorestframework para djangorestframework . Vamos instalar e configurá-lo:


 pip install djangorestframework djangorestframework-stubs 

E podemos começar a criar serializadores:


 # 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'] 

Visualizações:


 # 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() 

E roteadores:


 # 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)), # ... ] 

Nem parece que algo mudou, mas tudo é digitado: configurações, serializadores, conjuntos de visualizações e roteadores. Isso permitirá que você adicione gradualmente as tipologias onde mais precisar delas.


Vamos tentar alterar queryset = BlogPost.objects.all()
para queryset = [1, 2, 3] em nossas visualizações:


 » 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]]") 

Não, não vai funcionar! Corrija seu código!


Conclusão


Digitar as interfaces de estrutura é uma coisa incrível de se ter. Quando combinado com ferramentas como returns e mappers , permitirá escrever lógica de negócios declarativa e com segurança de tipo, envolvida em interfaces de estrutura digitadas. E para diminuir o número de erros na camada entre esses dois.


A digitação estática gradual opcional também permite iniciar rapidamente e adicionar tipos somente quando sua API estiver estabilizada ou acompanhar o desenvolvimento orientado a tipos desde o início.


No entanto, django-stubs e djangorestframework-stubs são novos projetos. Ainda existem muitos bugs, recursos planejados, especificações de tipo ausentes. Congratulamo-nos com todas as contribuições da comunidade para tornar as ferramentas de desenvolvedor em Python realmente incríveis.


Publicado originalmente no meu blog .

Source: https://habr.com/ru/post/pt465007/


All Articles