دورة FastAPI: تعلّم بناء واجهات API بسرعة باستخدام بايثون

دقائق القراءة: 7

ما هي FastAPI ولماذا تحظى بهذه الشعبية؟

تُعد FastAPI إطار عمل حديثاً وسريعاً لبناء واجهات API باستخدام لغة Python. وقد صُمّم ليمنح المطورين تجربة تطوير مرنة، مع أداء مرتفع وسهولة واضحة في كتابة الشيفرة وتوثيقها واختبارها.

تكمن قوة FastAPI في أنها تجمع بين عدة مزايا مهمة:

  • سهولة البدء حتى للمطور الذي يملك أساسيات جيدة في Python.
  • توثيق تلقائي جاهز عبر واجهات مثل Swagger UI.
  • دعم ممتاز لتحديد أنواع البيانات والتحقق منها.
  • أداء عالٍ مناسب للتطبيقات الحديثة والخدمات الخلفية.

إذا كنت ترغب في تطوير واجهات برمجية بسرعة ومنهجية واضحة، فإن FastAPI خيار عملي وقوي.

واجهة توضيحية لدورة FastAPI لتعلّم بناء واجهات برمجة التطبيقات باستخدام بايثون

ماذا ستتعلم في هذه الدورة التقنية؟

يركّز هذا الدرس التمهيدي على أساسيات العمل مع FastAPI، ويغطي المحاور التالية:

  • تثبيت FastAPI وتشغيل المشروع لأول مرة.
  • إنشاء أول واجهة API.
  • التعامل مع Path Parameters.
  • استخدام Query Parameters.
  • دمج مسارات الطلب مع معاملات الاستعلام.
  • إرسال البيانات عبر Request Body.
  • استخدام العمليات الأساسية: GET وPOST وPUT وDELETE.

المتطلبات الأساسية قبل البدء

قبل البدء في تعلّم FastAPI، يُفضَّل أن يكون لديك:

  • معرفة أساسية بلغة Python.
  • إصدار Python 3.6 أو أحدث مثبت على جهازك.
  • محرر شيفرة مثل Visual Studio Code.
  • معرفة بسيطة بكيفية استخدام الطرفية أو سطر الأوامر.

تثبيت FastAPI وتشغيل الخادم

عملية التثبيت بسيطة جداً، لأن FastAPI تُثبت عبر مدير الحزم pip. كذلك تحتاج إلى أداة uvicorn لتشغيل الخادم المحلي.

pip install fastapi uvicorn

يمكنك أيضاً استخدام أوامر بديلة بحسب نظام التشغيل أو إعدادات بيئتك، مثل:

python -m pip install fastapi
pip install uvicorn

تكمن أهمية uvicorn في أنه يشغّل تطبيق ASGI، وهو ما تحتاجه FastAPI لعرض الواجهة البرمجية عبر المتصفح أو أدوات الاختبار.

إنشاء أول تطبيق باستخدام FastAPI

بعد تثبيت الحزم، أنشئ ملفاً باسم myapi.py ثم أضف الشيفرة التالية:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def index():
    return {"name": "first data"}

في هذا المثال:

  • قمنا باستيراد الكائن FastAPI.
  • أنشأنا نسخة من التطبيق داخل المتغير app.
  • أضفنا مساراً رئيسياً باستخدام @app.get("/").
  • أعدنا استجابة بصيغة تشبه JSON.

تشغيل التطبيق محلياً

لتشغيل المشروع، استخدم الأمر التالي:

uvicorn myapi:app --reload

هذا الأمر يعني:

  • myapi: اسم الملف دون الامتداد .py.
  • app: اسم المتغير الذي يحتوي على تطبيق FastAPI.
  • --reload: لإعادة تشغيل الخادم تلقائياً عند تعديل الشيفرة.

بعد التشغيل، افتح المتصفح على العنوان المحلي الذي يظهر في الطرفية، وغالباً سيكون http://127.0.0.1:8000.

التوثيق التلقائي في FastAPI

من أبرز ميزات FastAPI أنها تنشئ توثيقاً تفاعلياً تلقائياً. يكفي أن تفتح الرابط:

http://127.0.0.1:8000/docs

ستظهر لك واجهة Swagger UI التي تسمح لك بما يلي:

  • استعراض جميع المسارات المتاحة.
  • معرفة نوع كل طلب مثل GET أو POST.
  • اختبار الطلبات مباشرة من المتصفح.
  • عرض الاستجابات وأكواد الحالة بسهولة.

هذه الميزة تختصر وقتاً كبيراً في الاختبار والتوثيق، وهي مفيدة جداً أثناء التطوير والعمل الجماعي.

فهم مفهوم Endpoint في واجهات API

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

  • جلب بيانات مستخدم.
  • إنشاء سجل جديد.
  • تعديل عنصر موجود.
  • حذف مورد من قاعدة البيانات.

على سبيل المثال، في رابط مثل /get-student/1 فإن الجزء /get-student/1 يمثل نقطة وصول مخصصة لاسترجاع بيانات طالب معيّن.

العمليات الأساسية في REST API

عند بناء واجهات برمجية، ستتعامل كثيراً مع العمليات التالية:

  • GET: لجلب البيانات أو عرضها.
  • POST: لإنشاء مورد جديد.
  • PUT: لتحديث مورد موجود.
  • DELETE: لحذف مورد محدد.

هذه العمليات تشكّل الأساس العملي لمعظم تطبيقات API الحديثة.

استخدام Path Parameters لاسترجاع بيانات ديناميكية

لنفترض أن لدينا قاموساً بسيطاً يمثل بيانات الطلاب:

from fastapi import FastAPI, Path

app = FastAPI()

students = {
    1: {
        "name": "John",
        "age": 17,
        "class": "year 12"
    }
}

@app.get("/get-student/{student_id}")
def get_student(student_id: int):
    return students[student_id]

هنا استخدمنا {student_id} داخل الرابط، وهذا يعني أن القيمة ستُمرر من الرابط مباشرة. فإذا طلبت الرابط /get-student/1 فستحصل على بيانات الطالب ذي المعرف 1.

إضافة وصف وقيود على قيمة المعرف

يمكنك تحسين جودة الواجهة بإضافة وصف ومعايير تحقق:

@app.get("/get-student/{student_id}")
def get_student(
    student_id: int = Path(None, description="المعرّف الخاص بالطالب", gt=0, lt=3)
):
    return students[student_id]

في هذا المثال:

  • description: يشرح وظيفة الحقل داخل التوثيق.
  • gt=0: يشترط أن تكون القيمة أكبر من 0.
  • lt=3: يشترط أن تكون القيمة أقل من 3.

هذه التفاصيل تجعل التوثيق أوضح، وتمنع إدخال قيم غير منطقية منذ البداية.

استخدام Query Parameters للبحث والتصفية

تُستخدم Query Parameters عندما تريد تمرير بيانات إضافية بعد علامة الاستفهام داخل الرابط، مثل:

/get-by-name?name=John

مثال عملي:

@app.get("/get-by-name")
def get_student(*, name: str):
    for student_id in students:
        if students[student_id]["name"] == name:
            return students[student_id]
    return {"Data": "Not found"}

في هذا المسار، لا نمرر المعرف داخل الرابط نفسه، بل نستخدم المعامل name للاستعلام عن الطالب بالاسم.

جعل معاملات الاستعلام اختيارية

يمكن جعل الحقل اختيارياً باستخدام Optional:

from typing import Optional

@app.get("/get-by-name")
def get_student(*, name: Optional[str] = None):
    for student_id in students:
        if students[student_id]["name"] == name:
            return students[student_id]
    return {"Data": "Not found"}

هذا الأسلوب أنظف من الناحية البرمجية، كما يوضّح في التوثيق أن القيمة ليست إلزامية.

الفرق بين Path Parameters وQuery Parameters

النوع مكانه في الرابط الاستخدام الشائع
Path Parameter داخل المسار نفسه تحديد مورد بعينه مثل معرف الطالب
Query Parameter بعد علامة ? البحث، التصفية، الفرز، الخيارات الإضافية

عملياً، إذا كنت تريد الوصول إلى عنصر محدد بهوية واضحة، فاستخدم Path Parameter. أما إذا كنت تريد البحث أو التصفية، فالأفضل استخدام Query Parameter.

دمج Path وQuery في مسار واحد

في بعض الحالات قد تحتاج إلى استخدام النوعين معاً داخل نفس الطلب. مثال على ذلك:

@app.get("/get-student/{student_id}")
def get_student(*, student_id: int, name: Optional[str] = None):
    return {"student_id": student_id, "name": name}

هذا الدمج يكون مفيداً عندما تريد تحديد مورد رئيسي باستخدام المعرّف، مع تمرير خيار إضافي مثل الاسم أو نوع العرض أو حالة التصفية.

التعامل مع Request Body وطلب POST

عند إنشاء سجل جديد، لا يكفي عادةً تمرير المعرف فقط، بل تحتاج إلى إرسال بيانات الكائن نفسه داخل جسم الطلب Request Body. وهنا تظهر أهمية نماذج Pydantic.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

students = {}

class Student(BaseModel):
    name: str
    age: int
    year: str

@app.post("/create-student/{student_id}")
def create_student(student_id: int, student: Student):
    if student_id in students:
        return {"Error": "Student exists"}

    students[student_id] = student.dict()
    return students[student_id]

في هذا المثال:

  • أنشأنا نموذجاً باسم Student.
  • حدّدنا الحقول المطلوبة وأنواعها.
  • استخدمنا POST لإنشاء طالب جديد.
  • تحققنا أولاً من عدم وجود الطالب مسبقاً.

ميزة هذا الأسلوب أن FastAPI تتحقق تلقائياً من صحة البيانات قبل تنفيذ المنطق البرمجي.

تحديث البيانات باستخدام PUT

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

from typing import Optional
from pydantic import BaseModel

class UpdateStudent(BaseModel):
    name: Optional[str] = None
    age: Optional[int] = None
    year: Optional[str] = None

ثم نستخدمه داخل مسار التحديث:

@app.put("/update-student/{student_id}")
def update_student(student_id: int, student: UpdateStudent):
    if student_id not in students:
        return {"Error": "Student does not exist"}

    if student.name is not None:
        students[student_id]["name"] = student.name

    if student.age is not None:
        students[student_id]["age"] = student.age

    if student.year is not None:
        students[student_id]["year"] = student.year

    return students[student_id]

لماذا هذا الأسلوب مهم؟

لو استخدمت نموذجاً يفرض جميع الحقول، فسيصبح المستخدم مضطراً لإرسال كل البيانات في كل مرة، حتى لو أراد تعديل قيمة واحدة فقط. أما النموذج الاختياري فيسمح بتحديث جزئي من دون الكتابة فوق القيم الأصلية بـ None.

وهذا يعد سلوكاً أفضل من ناحية تجربة المطور، كما يقلل الأخطاء المنطقية داخل التطبيق.

حذف البيانات باستخدام DELETE

لحذف سجل معين، يمكنك إنشاء مسار بسيط يعتمد على معرّف العنصر:

@app.delete("/delete-student/{student_id}")
def delete_student(student_id: int):
    if student_id not in students:
        return {"Error": "Student does not exist"}

    del students[student_id]
    return {"Message": "Student deleted successfully"}

هذا المسار ينفّذ خطوتين أساسيتين:

  1. التحقق من وجود الطالب.
  2. حذف السجل ثم إرجاع رسالة نجاح.

ورغم بساطة المثال، فإنه يعكس منطق الحذف المستخدم في معظم تطبيقات CRUD العملية.

أفضل الممارسات عند تعلّم FastAPI

  • استخدم نماذج Pydantic دائماً لتنظيم البيانات والتحقق منها.
  • افصل بين نماذج الإنشاء ونماذج التحديث.
  • أضف أوصافاً للمعاملات باستخدام Path() ووسائل التحقق الأخرى.
  • اختبر جميع المسارات عبر صفحة /docs أثناء التطوير.
  • لا تعتمد على القواميس داخل الذاكرة في المشاريع الحقيقية، بل استخدم قاعدة بيانات مناسبة.

متى تكون FastAPI خياراً مناسباً؟

تكون FastAPI مناسبة جداً في الحالات التالية:

  • بناء خدمات خلفية سريعة لواجهات الويب أو تطبيقات الجوال.
  • إنشاء REST APIs واضحة وقابلة للتوسع.
  • تطوير أدوات داخلية للشركات والفرق التقنية.
  • إنشاء نماذج أولية بسرعة قبل الانتقال إلى بيئات الإنتاج.
  • المشاريع التي تتطلب توثيقاً تلقائياً واختبارات سريعة.

الخلاصة التقنية

تقدّم FastAPI مزيجاً ممتازاً من السرعة وسهولة التطوير وجودة التوثيق، وهو ما يجعلها إطاراً عملياً للغاية لبناء واجهات API حديثة باستخدام Python. من الناحية التقنية، أهم ما يميزها ليس فقط الأداء، بل قدرتها على فرض بنية نظيفة للبيانات عبر Pydantic، مع تقليل الأخطاء وتحسين تجربة المطور. إذا كنت تبدأ في عالم الواجهات البرمجية أو تبحث عن إطار خفيف واحترافي، فإن تعلّم FastAPI استثمار ذكي ومفيد على المدى الطويل.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *