اليوم ننشر المادة الثانية من السلسلة المخصصة لاستخدام بيثون في إنستغرام.
آخر مرة تم فيها التحقق من أنواع كود خادم Instagram. الخادم هو متراصة مكتوبة في بيثون. وهو يتألف من عدة ملايين سطر من الشفرة وله عدة آلاف من نقاط النهاية في Django

تتناول هذه المقالة كيفية استخدام Instagram لأنواع المستندات لواجهة برمجة تطبيقات HTTP وفرض العقود عند العمل معهم.
نظرة عامة على الوضع
عندما تفتح عميل Instagram للهاتف المحمول ، فإنه ، عبر HTTP ، يصل إلى JSON-API لخادم Python (Django) الخاص بنا.
فيما يلي بعض المعلومات حول نظامنا والتي ستتيح لك التعرف على مدى تعقيد واجهة برمجة التطبيقات التي نستخدمها لتنظيم عمل عميل الهاتف المحمول. لذلك هنا هو ما لدينا:
- أكثر من 2000 نقاط النهاية على الخادم.
- أكثر من 200 حقل من المستوى الأعلى في كائن بيانات عميل يمثل صورة أو مقطع فيديو أو قصة في تطبيق ما.
- المئات من المبرمجين الذين يكتبون كود الخادم (وحتى أكثر من الذين يتعاملون مع العميل).
- مئات من التعهدات لرمز الخادم مصنوعة يوميا وتعديل API. يعد ذلك ضروريًا لتوفير الدعم لميزات النظام الجديدة.
نستخدم أنواعًا لتوثيق واجهات برمجة التطبيقات HTTP المعقدة والمتطورة باستمرار وفرض العقود عند العمل معهم.
أنواع
لنبدأ من البداية. ظهر وصف بناء جملة التعليقات التوضيحية في شفرة Python في
PEP 484 . لماذا تضيف التعليقات التوضيحية إلى الكود؟
فكر في الوظيفة التي تقوم بتنزيل معلومات حول بطل حرب النجوم:
def get_character(id, calendar): if id == 1000: return Character( id=1000, name="Luke Skywalker", birth_year="19BBY" if calendar == Calendar.BBY else ... ) ...
لفهم هذه الوظيفة ، تحتاج إلى قراءة الكود الخاص بها. بعد القيام بذلك ، يمكنك معرفة ما يلي:
- يستغرق
id
الصحيح للحرف. - يأخذ القيمة من التعداد المقابل (
calendar
). على سبيل المثال ، يشير Calendar.BBY
إلى "Before Battle of Yavin" ، أي "Before Battle of Yavin". - تقوم بإرجاع معلومات حول الشخصية في شكل كيان يحتوي على حقول تمثل معرف هذه الشخصية واسمه وسنة ميلاده.
تحتوي الوظيفة على عقد ضمني ، بمعنى أن يستعيد المبرمج في كل مرة يقرأ فيها رمز الوظيفة. لكن رمز الوظيفة مكتوب مرة واحدة فقط ، وعليك قراءته عدة مرات ، لذلك هذا الأسلوب في التعامل مع هذا الرمز ليس جيدًا بشكل خاص.
علاوة على ذلك ، من الصعب التحقق من أن الآلية التي تستدعي الوظيفة تلتزم بالعقد الضمني الموضح أعلاه. وبالمثل ، من الصعب التحقق من احترام هذا العقد في جسم الوظيفة. في قاعدة التعليمات البرمجية الكبيرة ، يمكن أن تؤدي مثل هذه المواقف إلى حدوث أخطاء.
الآن ضع في اعتبارك نفس الوظيفة التي تعلن عن التعليقات التوضيحية:
def get_character(id: int, calendar: Calendar) -> Character: ...
تتيح لك التعليقات التوضيحية للتعبير صراحةً عن عقد هذه الوظيفة. لفهم ما يجب إدخاله في دالة ما وتعود هذه الوظيفة ، فقط اقرأ توقيعها. يمكن لنظام فحص النوع تحليل الوظيفة بشكل ثابت والتحقق من الامتثال للعقد في الكود. هذا يسمح لك بالتخلص من فئة كاملة من الأخطاء!
أنواع لواجهة برمجة تطبيقات HTTP المختلفة
سنقوم بتطوير HTTP-API يتيح لك الحصول على معلومات حول أبطال حرب النجوم. لوصف العقد الصريح المستخدم عند العمل مع واجهة برمجة التطبيقات هذه ، سنستخدم التعليقات التوضيحية.
يجب أن تقبل واجهة برمجة التطبيقات (API)
id
الحرف (
id
) كمعلمة URL وقيمة تعداد
calendar
كمعلمة طلب. يجب أن تُرجع واجهة برمجة التطبيقات استجابة JSON بمعلومات الحرف.
إليك ما يبدو عليه طلب واجهة برمجة التطبيقات والاستجابة التي يُرجعها:
curl -X GET https://api.starwars.com/characters/1000?calendar=BBY { "id": 1000, "name": "Luke Skywalker", "birth_year": "19BBY" }
لتنفيذ واجهة برمجة التطبيقات هذه في جانغو ، تحتاج أولاً إلى تسجيل مسار URL ووظيفة العرض المسؤولة عن تلقي طلب HTTP المقدم على طول هذا المسار ولإعادة الاستجابة.
urlpatterns = [ url("characters/<id>/", get_character) ]
تقبل الوظيفة ، كمدخل ، معلمات الطلب وعنوان URL (في حالتنا ،
id
). يوزع ويلقي معلمة طلب
calendar
، والتي هي القيمة من التعداد المقابل ، إلى النوع المطلوب. يقوم بتحميل بيانات الأحرف من المتجر وإرجاع قاموس متسلسل في JSON وملفوف في استجابة HTTP.
def get_character(request: IGWSGIRequest, id: str) -> JsonResponse: calendar = Calendar(request.GET.get("calendar", "BBY")) character = Store.get_character(id, calendar) return JsonResponse(asdict(character))
على الرغم من أن الوظيفة مزودة بتعليقات توضيحية للنوع ، إلا أنها لا تصف بشكل صريح العقد الصعب لواجهة برمجة تطبيقات HTTP. من توقيع هذه الوظيفة ، لا يمكننا العثور على أسماء أو أنواع معلمات الطلب ، أو حقول الاستجابة وأنواعها.
هل من الممكن أن يكون توقيع تمثيل الوظيفة هو بالضبط نفس المعلومات التي يحملها توقيع الوظيفة التي سبق النظر فيها مع تعليقات توضيحية على الكتابة؟
def get_character(id: int, calendar: Calendar) -> Character: ...
يمكن أن تكون معلمات الوظيفة معلمات استعلام (URL أو استعلام أو معلمات نص الاستعلام). قد يمثل نوع القيمة التي يتم إرجاعها بواسطة الدالة محتويات الاستجابة. من خلال هذا النهج ، سيكون لدينا تحت تصرفنا عقدًا صريحًا ومفهومًا لواجهة برمجة تطبيقات HTTP ، يمكن ضمان الالتزام بها من خلال نظام فحص النوع.
تطبيق
كيفية تنفيذ هذه الفكرة؟
نحن نستخدم أداة تحويل لتحويل وظيفة التمثيل المكتوب بقوة إلى وظيفة تمثيل Django. هذه الخطوة لا تتطلب تغييرات من حيث العمل مع إطار جانغو. يمكننا استخدام نفس الوسيطة ، ونفس المسارات والمكونات الأخرى التي اعتدنا عليها.
@api_view def get_character(id: int, calendar: Calendar) -> Character: ...
النظر في تفاصيل
api_view
الديكور
api_view
:
def api_view(view): @functools.wraps(view) def django_view(request, *args, **kwargs): params = { param_name: param.annotation(extract(request, param)) for param_name, param in inspect.signature(view).parameters.items() } data = view(**params) return JsonResponse(asdict(data)) return django_view
هذا جزء صعب من التعليمات البرمجية لفهمه. دعنا نحلل معالمه.
نحن ، كقيمة إدخال ، نأخذ وظيفة تمثيل مكتوبة بقوة ونلفها في وظيفة تمثيل Django عادية ، نعود إليها:
def api_view(view): @functools.wraps(view) def django_view(request, *args, **kwargs): ... return django_view
الآن نلقي نظرة على تنفيذ وظيفة عرض جانغو. نحتاج أولاً إلى إنشاء وسيطات لوظيفة عرض تقديمي مطبوعة بشدة. نحن نستخدم الاستبطان ووحدة
التفتيش للحصول على توقيع هذه الوظيفة والتكرار على المعلمات:
for param_name, param in inspect.signature(view).parameters.items()
لكل معلمة ، نسميها دالة
extract
، والتي تستخرج قيمة المعلمة من الطلب.
ثم نلقي المعلمة بالنوع المتوقع المحدد في التوقيع (على سبيل المثال ، نلقي
calendar
السلسلة على قيمة عنصر من عناصر تعداد
Calendar
).
param.annotation(extract(request, param))
ندعو وظيفة عرض مكتوبة بقوة مع الحجج التي أنشأناها:
data = view(**params)
ترجع الدالة قيمة مكتوبة بشدة لفئة
Character
. نحن نأخذ هذه القيمة ونحولها إلى قاموس ونلفها في استجابة HTTP بتنسيق JSON:
return JsonResponse(asdict(data))
! ممتاز لدينا الآن وظيفة عرض Django التي تلتف وظيفة المشاهدة المكتوبة بقوة. أخيرًا ، ألقِ نظرة على وظيفة
extract
:
def extract(request: HttpRequest, param: Parameter) -> Any: if request.resolver_match.route.contains(f"<{param}>"): return request.resolver_match.kwargs.get(param.name) else: return request.GET.get(param.name)
يمكن أن تكون كل معلمة معلمة URL أو معلمة طلب. مسار عنوان URL للطلب (المسار الذي سجلناه في البداية) متاح في كائن المسار في نظام محدد مواقع المعلومات لموقع Django. نحن نبحث عن اسم المعلمة في المسار. إذا كان هناك اسم ، فلدينا معلمة URL. هذا يعني أنه يمكننا بطريقة ما استخراجها من الطلب. وإلا ، فهذه معلمة استعلام ويمكننا أيضًا استخراجها ، ولكن بطريقة أخرى.
هذا كل شيء. هذا تطبيق مبسط ، لكنه يوضح الفكرة الأساسية لكتابة واجهة برمجة التطبيقات.
أنواع البيانات
يمكن تمثيل النوع المستخدم لتمثيل محتويات استجابة HTTP (على سبيل المثال ،
Character
) إما بواسطة فئة بيانات أو قاموس مكتوب.
فئة البيانات هي تنسيق وصف فئة مضغوط يمثل البيانات.
from dataclasses import dataclass @dataclass(frozen=True) class Character: id: int name: str birth_year: str luke = Character( id=1000, name="Luke Skywalker", birth_year="19BBY" )
يستخدم Instagram عادةً فئات البيانات لنمذجة كائنات استجابة HTTP. فيما يلي ميزاتها الرئيسية:
- أنها تولد تلقائيا إنشاءات القالب وأساليب المساعدة المختلفة.
- يمكن فهمها لأنظمة فحص الكتابة ، مما يعني أن القيم يمكن أن تخضع لفحص النوع.
- انهم الحفاظ على الحصانة بفضل
frozen=True
بناء frozen=True
. - وهي متوفرة في مكتبة Python القياسية 3.7 ، أو كخلفية خلفية في فهرس بيثون.
لسوء الحظ ، يحتوي Instagram على قاعدة تعليمات برمجية قديمة تستخدم قواميس كبيرة غير نمطية ، يتم تمريرها بين الوظائف والوحدات النمطية. لن يكون من السهل ترجمة كل هذا الرمز من القواميس إلى فئات البيانات. نتيجة لذلك ، نحن باستخدام فئات البيانات للرمز الجديد ، وفي الشفرة القديمة نستخدم
القواميس المكتوبة .
يتيح لنا استخدام القواميس المكتوبة إضافة تعليقات توضيحية إلى كائنات قاموس العميل ، وبدون تغيير سلوك نظام العمل ، استخدم إمكانات التحقق من الكتابة.
from mypy_extensions import TypedDict class Character(TypedDict): id: int name: str birth_year: str luke: Character = {"id": 1000} luke["name"] = "Luke Skywalker" luke["birth_year"] = 19
خطأ في التعامل
من المتوقع أن تقوم دالة العرض بإرجاع معلومات الحرف في شكل كيان
Character
. ماذا يجب أن نفعل إذا احتجنا إلى إرجاع خطأ إلى العميل؟
يمكنك طرح استثناء سيتم اكتشافه بواسطة الإطار وتحويله إلى استجابة HTTP مع معلومات الخطأ.
@api_view("GET") def get_character(id: str, calendar: Calendar) -> Character: try: return Store.get_character(id) except CharacterNotFound: raise Http404Exception()
يوضح هذا المثال أيضًا طريقة HTTP في الديكور ، والذي يحدد طرق HTTP المسموح بها لواجهة برمجة التطبيقات هذه.
الأدوات
تتم كتابة واجهة برمجة تطبيقات HTTP بشدة باستخدام طريقة HTTP وأنواع الطلبات وأنواع الاستجابة. يمكننا استكشاف واجهة برمجة التطبيقات هذه وتحديد ما إذا كان يجب قبول طلب GET بسلسلة
id
في مسار URL ومع قيمة
calendar
المرتبطة بالتعداد المقابل في سلسلة الاستعلام. يمكننا أيضًا معرفة أنه استجابةً لهذا الطلب ، يجب إعطاء استجابة JSON بمعلومات حول طبيعة
Character
.
ما الذي يمكن عمله بكل هذه المعلومات؟
OpenAPI هو تنسيق وصف API يستند إلى إنشاء مجموعة غنية من الأدوات المساعدة. هذا هو النظام البيئي كله. إذا كتبنا بعض التعليمات البرمجية لإجراء الاستبطان لنقطة النهاية وإنشاء مواصفات OpenAPI استنادًا إلى البيانات المستلمة ، فإن هذا يعني أنه سيكون لدينا قدرات هذه الأدوات.
paths: /characters/{id}: get: parameters: - in: path name: id schema: type: integer required: true - in: query name: calendar schema: type: string enum: ["BBY"] responses: '200': content: application/json: schema: type: object ...
يمكننا إنشاء وثائق HTTP API لواجهة
get_character
API ، والتي تتضمن الأسماء والأنواع ومعلومات الطلب والاستجابة. هذا هو المستوى المناسب من التجريد لمطوري العملاء الذين يحتاجون إلى تلبية الطلبات إلى نقطة النهاية المناسبة. لا يحتاجون إلى قراءة رمز تنفيذ Python لنقطة النهاية هذه.
وثائق APIعلى هذا الأساس ، يمكنك إنشاء أدوات إضافية. على سبيل المثال ، وسيلة لتنفيذ طلب من المستعرض. يسمح هذا للمطورين بالوصول إلى HTTP APIs التي تهمهم دون الحاجة إلى كتابة التعليمات البرمجية. يمكننا حتى إنشاء رمز عميل آمن من النوع لضمان عمل الأنواع بشكل صحيح على كل من العميل والخادم. نتيجة لهذا ، قد يكون لدينا تحت تصرفنا واجهة برمجة تطبيقات مكتوبة بدقة على الخادم ، يتم إجراء المكالمات باستخدام رمز عميل مكتوب بدقة.
بالإضافة إلى ذلك ، يمكننا إنشاء نظام التحقق من التوافق مع الإصدارات السابقة. ماذا يحدث إذا أصدرنا إصدارًا جديدًا من رمز الخادم الذي يمكننا من خلاله الوصول إلى واجهة برمجة التطبيقات (API) المعنية ، فنحن بحاجة إلى استخدام
id
name
birth_year
، ثم نفهم أننا لا نعرف أعياد الميلاد لجميع الشخصيات؟ في هذه الحالة ،
birth_year
المعلمة
birth_year
إلى أن تكون اختيارية ، ولكن الإصدارات القديمة من العملاء التي تتوقع وجود معلمة مماثلة قد تتوقف عن العمل. على الرغم من اختلاف واجهات برمجة التطبيقات الخاصة بنا في الكتابة الصريحة ، إلا أن الأنواع المتوافقة يمكن أن تتغير (على سبيل المثال ، ستتغير واجهة برمجة التطبيقات إذا كان استخدام سنة ميلاد الشخصية إلزاميًا ثم أصبح اختياريًا). يمكننا تتبع تغييرات واجهة برمجة التطبيقات (API) وتحذير مطوري واجهة برمجة التطبيقات (API) عن طريق إعطائهم مطالبات في الوقت المناسب ، ومن خلال إجراء بعض التغييرات ، يمكنهم تعطيل أداء العملاء.
النتائج
هناك مجموعة كاملة من بروتوكولات التطبيقات التي يمكن لأجهزة الكمبيوتر استخدامها للتواصل مع بعضها البعض.
يتمثل جانب واحد من هذا الطيف بأطر RPC مثل Thrift و gRPC. وهي تختلف من حيث أنها عادة ما تحدد أنواعًا صارمة للطلبات والاستجابات وتوليد رمز العميل والخادم لتنظيم تشغيل الطلبات. يمكنهم الاستغناء عن HTTP وحتى بدون JSON.
من ناحية أخرى ، هناك أطر ويب غير منظمة مكتوبة في بيثون والتي ليس لديها عقود واضحة للطلبات والردود. يوفر منهجنا فرصًا نموذجية لأُطر عمل منظمة بشكل أكثر وضوحًا ، ولكن في الوقت نفسه يسمح لك بمواصلة استخدام حزمة HTTP + JSON ويساهم في حقيقة أنه يجب عليك إجراء حد أدنى من التغييرات على رمز التطبيق.
من المهم أن نلاحظ أن هذه الفكرة ليست جديدة. هناك العديد من الأطر المكتوبة بلغات مطبوعة بشدة توفر للمطورين الميزات التي وصفناها. إذا تحدثنا عن بيثون ، فهذا هو ، على سبيل المثال ، إطار عمل
APIStar .
لقد كلفنا بنجاح باستخدام أنواع واجهة برمجة تطبيقات HTTP. تمكنا من تطبيق النهج الموضح على كتابة واجهات برمجة التطبيقات في قاعدة الشفرة الخاصة بنا بأكملها نظرًا لحقيقة أنه مناسب تمامًا لوظائف العرض التقديمي الحالية. قيمة ما فعلناه واضحة لجميع المبرمجين لدينا. أي أننا نتحدث عن حقيقة أن الوثائق التي تم إنشاؤها تلقائيًا أصبحت وسيلة فعالة للاتصال بين من يقومون بتطوير الخادم وأولئك الذين يكتبون عميل Instagram.
أعزائي القراء! كيف يمكنك التعامل مع تصميم واجهات برمجة التطبيقات HTTP في مشاريع بيثون الخاصة بك؟
