Typechecking Django و DRF

كما تعلمون بالفعل أنا أحب الكتابة ثابت اختياري. الشيء هو أنه في بعض الأحيان ليست اختيارية ، ولكن من المستحيل. لأن لدينا الكثير من المشاريع الكبيرة الغير نمطية في النظام البيئي لبيثون.


كان دجانغو وجانغو ريست فريم اثنين منهم. كانت. لأنه الآن يمكن كتابتها! اسمحوا لي أن أعرض Typed Django منظمة و بذرة ل django و drf .


سيكون هذا البرنامج التعليمي موجزة والبدء في دليل.


مجد


أريد أن أقول "شكراً" كبيرة لمكرنيكوف لقيادته المشروع ولجميع المساهمين الذين جعلوا هذا ممكنًا. أنت كل شيء رائع!


TLDR


في هذه المقالة ، أعرض كيف تعمل الأنواع مع django و drf . يمكنك إلقاء نظرة على النتيجة هنا .


ويمكنك أيضًا استخدام wemake-django-template لبدء مشاريعك الجديدة بكل شيء تم تكوينه بالفعل. سيبدو تمامًا كمثال على المشروع.


الابتداء


في هذا البرنامج التعليمي الصغير ، سأريك العديد من ميزات django-stubs و djangorestframework-stubs في العمل. آمل أن يقنعك ذلك بأن قيام شخص ما بمراجعة الأمور بعدك أمر جيد.


يمكنك دائمًا الرجوع إلى الوثائق الأصلية. وتغطي جميع الخطوات أيضا هناك.


للبدء ، سنحتاج إلى مشروع جديد وبيئة افتراضية نظيفة ، حتى نتمكن من تثبيت تبعياتنا:


 pip install django django-stubs mypy 

ثم سنحتاج إلى تكوين 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 

ثم نقوم بتكوين البرنامج المساعد django-stubs :


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

ماذا نفعل هنا؟


  1. نضيف mypy مخصصًا mypy للمساعدة في تخمين مدقق الكتابة في بعض المواقف المعقدة الخاصة بـ Django (مثل النماذج ، queryset ، الإعدادات ، إلخ)
  2. نضيف أيضًا تهيئة مخصصة لـ django-stubs إلى الإعدادات ، نستخدمها في Django. سوف تحتاج إلى استيراده.

النتيجة النهائية يمكن العثور عليها هنا .


لدينا الآن كل ما تم تثبيته وتكوينه. دعونا اكتب التحقق من الأشياء!


طرق فحص الكتابة


لنبدأ بكتابة طرق العرض نظرًا لأنه أسهل شيء بالنسبة لهذا المكون الإضافي.


إليكم وجهة نظرنا البسيطة المبنية على الوظيفة:


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

لنركض ونرى ما هي الأنواع التي يعرفها. لاحظ أننا قد نحتاج إلى تعديل 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' 

دعنا نحاول كسر شيء:


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

كلا ، هناك خطأ مطبعي وسوف 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" 

إنه يعمل! حسنًا ، لكن هذا جميل تمامًا. دعنا نعقد مثالنا قليلاً وننشئ نموذجًا مخصصًا لإظهار كيف يمكننا كتابة النماذج و querysets.


نماذج typechecking و queryset


جانغو ORM هي ميزة القاتل. انها مرنة جدا وديناميكية. وهذا يعني أيضًا أنه من الصعب الكتابة. دعونا نرى بعض الميزات التي تغطيها بالفعل django-stubs .


تعريف نموذجنا:


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

وتغطي كل مجال من مجالات هذا النموذج بواسطة 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*' 

كل شيء يبدو جيدا! يوفر django-stubs mypy مخصصًا mypy لتحويل حقول النماذج إلى أنواع مثيل صحيحة. لهذا السبب يتم كشف جميع الأنواع بشكل صحيح.


الميزة الثانية الكبيرة في البرنامج المساعد 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, ) 

وإليك كيف يمكن التحقق من ذلك:


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

يمكننا حتى .values() باستخدام .values() و .values_list() . هذا البرنامج المساعد ذكي!


لقد QuerySet صعوبات في شرح طرق إعادة QuerySet لعدة سنوات. تعمل هذه الميزة على حل مشكلة كبيرة بالنسبة لي: لم يعد Iterable[BlogPost] أو List[User] . يمكنني الآن استخدام أنواع حقيقية.


Typechecking واجهات برمجة التطبيقات


لكن كتابة طرق العرض والنماذج والنماذج والأوامر وعناوين url والمشرف ليست هي كل ما لدينا. يحتوي TypedDjango أيضًا على نسخ djangorestframework . دعونا تثبيت وتكوينه:


 pip install djangorestframework djangorestframework-stubs 

ويمكننا البدء في إنشاء مسلسلات:


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

المشاهدات:


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

وأجهزة التوجيه:


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

لا يبدو الأمر كما لو أن شيئًا ما قد تغير ، لكن كل ما بداخله كان مكتوبًا: الإعدادات ، والمتسلسلات ، ووجهات النظر ، وأجهزة التوجيه. سيتيح لك ذلك إضافة تدرجات بشكل متزايد إلى حيث تشتد الحاجة إليها.


دعنا نحاول تغيير 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]]") 

لا ، لن ينجح! إصلاح التعليمات البرمجية الخاصة بك!


استنتاج


كتابة واجهات الإطار أمر رائع. عندما يتم دمجها مع أدوات مثل returns mappers ، فإنها ستسمح لكتابة منطق الأعمال الآمن للنوع والملفوف في واجهات إطار عمل مكتوبة. ولتقليل عدد الأخطاء في الطبقة بين هذين.


تتيح لك الكتابة الثابتة التدريجية الاختيارية أيضًا البدء بسرعة وإضافة أنواع فقط عندما يتم تثبيت واجهة برمجة التطبيقات (API) الخاصة بك أو الانتقال مع التطوير القائم على الأنواع من البداية.


ومع ذلك ، فإن django-stubs و djangorestframework-stubs من المشاريع الجديدة. لا يزال هناك الكثير من الأخطاء ، والميزات المخطط لها ، في عداد المفقودين المواصفات النوع. نرحب بكل مساهمة من المجتمع لجعل أدوات المطور في Python رائعة حقًا.


نشرت أصلا في مدونتي .

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


All Articles