الوحدة 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 ولا يتحقّق منه، وهو فقد صامت لكامل قيمة الإطار. القاعدة: كلّ معامل ومخرج له نوع صريح.