Mengecek Django dan DRF

Seperti yang telah Anda ketahui, saya suka mengetik statis opsional. Masalahnya adalah bahwa kadang-kadang itu bukan opsional, tetapi tidak mungkin. Karena kami memiliki banyak proyek besar yang belum diketik dalam ekosistem Python.


Mereka berdua adalah Django dan Django-Rest-Framework. Apakah. Karena sekarang mereka bisa mengetik! Izinkan saya memperkenalkan organisasi drf Django dan bertopik untuk django dan drf .


Ini akan menjadi tutorial ringkas dan panduan memulai.


Kudos


Saya ingin mengucapkan "terima kasih" yang besar kepada @mkurnikov karena memimpin proyek ini dan kepada semua kontributor yang memungkinkan ini terjadi. Anda semua luar biasa!


TLDR


Pada artikel ini, saya menunjukkan bagaimana tipe bekerja dengan django dan drf . Anda dapat melihat hasilnya di sini .


Dan Anda juga dapat menggunakan wemake-django-template untuk memulai proyek baru Anda dengan semua yang sudah dikonfigurasi. Ini akan terlihat persis seperti contoh proyek.


Memulai


Dalam tutorial kecil ini, saya akan menunjukkan kepada Anda beberapa fitur django-stubs dan djangorestframework-stubs in action. Saya harap ini akan meyakinkan Anda bahwa memiliki seseorang untuk menggandakan periksa setelah Anda adalah hal yang baik.


Anda selalu dapat merujuk ke dokumentasi asli. Semua langkah juga tercakup di sana.


Untuk memulai kita akan memerlukan proyek baru dan lingkungan virtual yang bersih , sehingga kita dapat menginstal dependensi kita:


 pip install django django-stubs mypy 

Maka kita perlu mengkonfigurasi mypy dengan benar. Itu dapat dibagi menjadi dua langkah. Pertama, kita mengkonfigurasi mypy itu sendiri:


 # 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 

Kemudian kita mengkonfigurasi plugin django-stubs :


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

Apa yang kita lakukan di sini?


  1. Kami menambahkan plugin mypy khusus untuk membantu tipe menebak tipe checker di beberapa situasi spesifik Django yang rumit (seperti model, queryset, pengaturan, dll)
  2. Kami juga menambahkan konfigurasi khusus untuk django-stubs untuk mengarahkannya ke pengaturan, kami gunakan untuk Django. Perlu mengimpornya.

Hasil akhirnya dapat ditemukan di sini .


Kami sekarang memiliki semua yang diinstal dan dikonfigurasi. Mari kita ketik periksa!


Mengecek tampilan


Mari kita mulai dengan mengetikkan pandangan karena ini adalah hal termudah untuk dilakukan dengan plugin ini.


Berikut tampilan sederhana berdasarkan fungsi kami:


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

Mari kita jalankan dan lihat jenis apa yang disadarinya. Perhatikan bahwa kami mungkin perlu memodifikasi PYTHONPATH , sehingga mypy dapat mengimpor proyek kami:


 ยป 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' 

Mari kita coba hancurkan sesuatu:


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

Tidak, ada kesalahan ketik dan mypy akan menangkapnya:


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

Itu berhasil! Ok, tapi itu cukup mudah. Mari kita sedikit mempersulit contoh kita dan membuat model khusus untuk menunjukkan bagaimana kita bisa mengetik model dan queryset.


Mengetik model dan queryset


ORANG Django adalah fitur pembunuh. Ini sangat fleksibel dan dinamis. Ini juga berarti sulit untuk mengetik. Mari kita lihat beberapa fitur yang sudah dilindungi oleh django-stubs .


Definisi model kami:


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

Dan setiap bidang model ini ditutupi oleh django-stubs . Mari kita lihat jenis apa yang diungkapkan:


 ยป 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*' 

Semuanya terlihat bagus! django-stubs menyediakan plugin mypy khusus untuk mengubah bidang model menjadi tipe instance yang benar. Itu sebabnya semua tipe diungkapkan dengan benar.


Fitur besar kedua dari plugin django-stubs adalah kita dapat mengetik 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, ) 

Dan inilah cara memeriksa:


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

Kami bahkan dapat membuat anotasi kueri dengan .values() dan .values_list() . Plugin ini cerdas!


Saya telah berjuang dengan metode anotasi yang mengembalikan QuerySet selama beberapa tahun. Fitur ini memecahkan masalah besar bagi saya: tidak ada lagi Iterable[BlogPost] atau List[User] . Sekarang saya bisa menggunakan tipe nyata.


Memeriksa jenis API


Tapi, mengetikkan pandangan, model, formulir, perintah, url, dan admin tidak semua yang kita miliki. TypedDjango juga memiliki typing untuk djangorestframework . Mari kita instal dan konfigurasikan:


 pip install djangorestframework djangorestframework-stubs 

Dan kita bisa mulai membuat 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'] 

Dilihat:


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

Dan router:


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

Bahkan tidak terlihat seperti sesuatu telah berubah, tetapi semua yang ada di dalamnya diketik: pengaturan, serializers, viewet, dan router. Ini akan memungkinkan Anda untuk menambahkan mengetik secara bertahap di mana Anda paling membutuhkannya.


Mari kita coba ubah queryset = BlogPost.objects.all()
to queryset = [1, 2, 3] dalam tampilan kami:


 ยป 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]]") 

Tidak, itu tidak akan berhasil! Perbaiki kode Anda!


Kesimpulan


Mengetik antarmuka kerangka kerja adalah hal yang luar biasa untuk dimiliki. Ketika dikombinasikan dengan alat-alat seperti returns dan mappers itu akan memungkinkan penulisan logika bisnis tipe-aman dan deklaratif yang dibungkus menjadi antarmuka kerangka kerja yang diketik. Dan untuk mengurangi jumlah kesalahan di lapisan antara keduanya.


Pengetikan statis bertahap opsional juga memungkinkan Anda untuk memulai dengan cepat dan menambahkan jenis hanya ketika API Anda distabilkan atau mengikuti pengembangan yang didorong oleh jenis sejak awal.


Namun, django-stubs dan djangorestframework-stubs adalah proyek baru. Masih ada banyak bug, fitur yang direncanakan, spesifikasi tipe yang hilang. Kami menyambut setiap kontribusi dari komunitas untuk membuat perkakas pengembang dengan Python benar-benar luar biasa.


Awalnya diterbitkan di blog saya .

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


All Articles