انتقل إلى المحتوى الرئيسي

الوحدة 1 — FastAPI: المسارات والأنواع والتوثيق الآليّ

سنبني في هذه الدورة واجهة برمجية تخدم نموذج تسرّب المشتركين الذي درّبناه في دورة MLOps. لكن قبل أن نُحمّل نموذجًا أو نتحقّق من مدخل، لا بدّ من فهم البنية الأساسيّة لـFastAPI: كيف تُعرَّف المسارات، وكيف تُصرَّح الأنواع، ومن أين يأتي التوثيق التفاعليّ الذي يظهر على /docs بلا سطر إضافيّ.

لماذا FastAPI وليس Flask أو Django REST

Flask خفيف لكنّه لا يفرض أنواعًا ولا يُولّد توثيقًا؛ Django REST قويّ لكنّه ثقيل ومصمَّم حول ORM لا حول التنبّؤات. FastAPI يستند إلى Starlette (طبقة ASGI عالية الأداء) وPydantic (تحقّق مدخلات مبنيّ على الأنواع)، ويستفيد من تلميحات الأنواع في بايثون لبناء ثلاثة أشياء دفعة واحدة: التحقّق التلقائيّ من المدخلات، والتسلسل التلقائيّ للمخرجات، وتوثيق OpenAPI الحيّ. في تجارب مشغّل الاتّصالات، التبديل من Flask إلى FastAPI رفع الإنتاجيّة (طلبات في الثانية) بضعف تقريبًا ووفّر كتابة مئات الأسطر من التحقّق اليدويّ.

أوّل تطبيق يعمل

الملفّ التالي، بعد pip install fastapi uvicorn[standard]، يشتغل خدمة كاملة:

from fastapi import FastAPI

app = FastAPI(
title="Churn Scoring API",
version="0.1.0",
description="واجهة تسجيل تسرّب المشتركين",
)


@app.get("/")
def racine() -> dict:
return {"service": "churn-scoring", "status": "up"}


@app.get("/version")
def version() -> dict:
return {"model_version": "aucun", "api_version": "0.1.0"}

ثمّ نُشغّل الخادم بـUvicorn:

uvicorn app:app --host 0.0.0.0 --port 8000 --reload

الأمر يفتح الاستماع على المنفذ 8000. زيارة http://localhost:8000/ تُعيد JSON. زيارة http://localhost:8000/docs تُعرِض Swagger UI تفاعليّة: كلّ مسار قابل للاختبار من المتصفّح، مع نموذج الطلب ومخطّط الاستجابة. زيارة /redoc تُعرِض توثيقًا بديلًا (ReDoc)، والملفّ الخام على /openapi.json. كلّ هذا مجّانيّ بلا سطر واحد لتوليده.

المسارات ومحدّدات المسار

@app.get("/model/{name}") يُنشئ مسارًا يقبل جزءًا متغيّرًا. FastAPI يمرّر القيمة تلقائيًّا إلى الدالّة إن أعلنّاها كوسيط:

@app.get("/model/{name}")
def details_modele(name: str) -> dict:
return {"model": name, "status": "aucun"}

هنا name مُصرَّح كسلسلة. إن حدّدت name: int، فأيّ مسار غير عدديّ يُعيد 422 تلقائيًّا مع رسالة واضحة تشرح النوع المتوقّع. لا تكتب سطرًا واحدًا للتحقّق؛ FastAPI يقوم بذلك مقابل التصريح بالنوع.

المعاملات الاختياريّة تمرّ عبر معاملات الاستعلام (query parameters) عندما لا تظهر في مسار المحدّد:

@app.get("/predictions/history")
def historique(limit: int = 20, offset: int = 0) -> dict:
return {"limit": limit, "offset": offset, "items": []}

استدعاء GET /predictions/history?limit=50 يمرّر 50 تلقائيًّا. إن أرسل العميل limit=abc فالخدمة تعيد 422 مع تفصيل الحقل والقيمة المتوقّعة.

POST مع جسم JSON

للطلبات التي تحمل بيانات، نستعمل POST مع جسم JSON. الجسم يُعلَن عبر صنف Pydantic (تفصيل في الوحدة القادمة)، لكن هذه صيغة مبسّطة بأنواع أوّليّة:

from pydantic import BaseModel


class Ping(BaseModel):
message: str


@app.post("/ping")
def ping(body: Ping) -> dict:
return {"echo": body.message, "length": len(body.message)}

إرسال {"message": "hello"} يُعيد الصدى. إرسال {} يُعيد 422 لأنّ message إلزاميّ. لا سطر تحقّق مكتوب باليد؛ Pydantic يستنتج القاعدة من الصنف.

تلميحات الأنواع ليست تعليقًا

كثير من الآتين من Flask يظنّون أنّ name: str تعليق للمحرّر فقط. في FastAPI، هو إعلان تنفيذيّ: يُستعمل لبناء مخطّط OpenAPI، للتحقّق من المدخلات، ولتحويل JSON إلى كائن Python، وللعكس. حذف التلميح يعني أنّ FastAPI يعتبر المعامل من نوع Any ولا يتحقّق منه، وهو فقد صامت لكامل قيمة الإطار. القاعدة: كلّ معامل ومخرج له نوع صريح.

أنواع الاستجابة والحالة

لتوثيق أفضل، نُصرّح بنوع الاستجابة عبر response_model، وبرمز الحالة عبر status_code:

from fastapi import status


class RacineOut(BaseModel):
service: str
status: str


@app.get(
"/",
response_model=RacineOut,
status_code=status.HTTP_200_OK,
summary="فحص أنّ الخدمة مشتغلة",
)
def racine() -> RacineOut:
return RacineOut(service="churn-scoring", status="up")

response_model يحصر ما يخرج فعلًا في الحقول المُعلَنة (يحذف أيّ حقل إضافيّ)، ما يمنع تسرّب حقول حسّاسة عن طريق الخطأ. summary يظهر في Swagger UI كعنوان المسار.

Uvicorn وgunicorn

Uvicorn خادم ASGI يعمل ضمن حدث واحد (single-loop asyncio). للاستعمال المحلّيّ يكفي --reload. للإنتاج نُشغّل عادةً عدّة عمّال (workers) لاستغلال الأنوية المتعدّدة، وذلك عبر --workers 4. البديل المهنيّ هو gunicorn كمشرف عمليّات مع uvicorn.workers.UvicornWorker، ونعود إليه في الوحدة 9.

الخلاصة

  • FastAPI يستغلّ تلميحات الأنواع لبناء التحقّق والتسلسل والتوثيق التلقائيّ دفعة واحدة، وهذا ما يُميّزه عن Flask.
  • كلّ مسار يُعلَن بديكوريتر (@app.get, @app.post) مع محدّدات مسار ومعاملات استعلام تُصرَّح بأنواع صريحة.
  • الأجسام الغنيّة تمرّ عبر أصناف Pydantic التي تُغطّي تفاصيلها الوحدة القادمة.
  • Swagger UI على /docs وReDoc على /redoc مجّانيّتان بلا كود، وتُشكّلان توثيقًا حيًّا يستهلكه فريق العملاء والمختبرون.