404 Not Found

404 Not Found


nginx

نظام حقن التبعيات — التصميم الأساسي لـ FastAPI

حقن التبعيات مثل واجهات قطع الليغو—كل قطعة تهتم فقط بما يجب أن تقدمه ولا تحتاج لمعرفة من يستخدمها. اتصالات قاعدة البيانات والمستخدم الحالي وفحوصات الصلاحيات يتم حلها تلقائيًا عبر طبقات متداخلة.

1. ما ستتعلمه


2. قصة Alice الحقيقية

(1) المشكلة: كل نقطة نهاية تسترجع البيانات من قاعدة البيانات ومعلومات المستخدم بشكل متكرر

يحتوي PriceTracker الخاص بـ Alice على 20 نقطة نهاية، كل منها يتطلب فتح اتصال قاعدة بيانات والتحقق من رمز JWT واسترجاع المستخدم الحالي وفحص الصلاحيات. تنسخ وتلصق نفس 15 سطرًا من كود التهيئة في كل دالة نقطة نهاية، لذلك إذا تغيرت طريقة اتصال قاعدة البيانات، يجب عليها إجراء 20 تعديلًا منفصلًا.

(2) حلول حقن التبعيات

يستخرج حقن التبعيات في FastAPI المنطق المتكرر إلى دوال تبعية قابلة لإعادة الاستخدام. تحتاج نقاط النهاية فقط إلى تعريف db = Depends(get_db)، ويقوم FastAPI بالحل والحقن تلقائيًا، بما في ذلك الحل التكراري للتبعيات الفرعية.

PYTHON
from fastapi import Depends

async def get_db():
    db = Database()
    yield db
    db.close()

async def get_current_user(token: str, db=Depends(get_db)):
    return verify_token(token, db)

@app.get("/products")
async def list_products(user=Depends(get_current_user), db=Depends(get_db)):
    # user و db يتم حقنهما تلقائيًا
    ...

(3) العائد

اتصالات قاعدة البيانات ومصادقة المستخدم وفحوصات الصلاحيات—التي كانت تتطلب 15 سطرًا من الكود لكل من نقاط النهاية العشرين—تم دمجها في ثلاث دوال تبعية، بحيث يصبح التغيير في مكان واحد ساريًا على المستوى العام. أثناء الاختبار، ببساطة استخدم app.dependency_overrides[get_db] = lambda: mock_db لاستبدال معلومات قاعدة البيانات لجميع نقاط النهاية بسطر واحد من الكود.


3. أساسيات Depends()

(1) تبعيات الدوال

(1) ▶مثال: تبعيات الدوال البسيطة

PYTHON
from fastapi import FastAPI, Depends

app = FastAPI()

def common_parameters(
    q: str | None = None,
    skip: int = 0,
    limit: int = 100,
):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/products")
async def list_products(commons: dict = Depends(common_parameters)):
    return commons

@app.get("/prices")
async def list_prices(commons: dict = Depends(common_parameters)):
    return commons

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) ▶مثال: تبعيات الأصناف

PYTHON
from fastapi import FastAPI, Depends, Query

class CommonQueryParams:
    def __init__(
        self,
        q: str | None = Query(None),
        skip: int = Query(0, ge=0),
        limit: int = Query(100, ge=1, le=200),
    ):
        self.q = q
        self.skip = skip
        self.limit = limit

app = FastAPI()

@app.get("/products")
async def list_products(commons: CommonQueryParams = Depends(CommonQueryParams)):
    return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) تبعيات الدوال مقابل تبعيات الأصناف

البُعد تبعية الدالة تبعية الصنف
طريقة التعريف def get_xxx() class Xxx: + __init__
حالات الاستخدام منطق بسيط، استدعاء واحد يتطلب حالة، طرق متعددة
تحليل المعاملات تحليل تلقائي لمعاملات الدالة تحليل تلقائي لمعاملات __init__
قابلية إعادة الاستخدام عالية متوسطة
مستوى التوصية الخيار الأول استخدم في السيناريوهات المعقدة

4. التبعيات الفرعية المتداخلة

(1) شجرة حل التبعيات

100%
graph TD
    A[get_current_user] --> B[get_db]
    A --> C[get_token_from_header]
    C --> D[OAuth2PasswordBearer]
    
    E[require_admin] --> A
    E --> F[check_subscription]
    
    style A fill:#e1f5fe
    style E fill:#fff3e0

يحل FastAPI شجرة التبعيات بشكل تكراري: require_adminget_current_userget_db، مع إنشاء مثيل لكل تبعية مرة واحدة فقط (مخزنة مؤقتًا ضمن نفس الطلب).

(1) ▶مثال: تبعيات فرعية متداخلة—DB → المستخدم

PYTHON
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

# المستوى 1: جلسة قاعدة البيانات
async def get_db():
    db = {"connection": "active"}
    try:
        yield db
    finally:
        db["connection"] = "closed"

# المستوى 2: الحصول على المستخدم الحالي (يعتمد على DB)
async def get_current_user(
    authorization: str = Header(...),
    db: dict = Depends(get_db),
):
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid token")
    token = authorization.replace("Bearer ", "")
    # مبسط: في الإنتاج، تحقق من JWT
    user = {"id": 1, "username": "alice", "role": "admin"}
    return user

# المستوى 3: يتطلب صلاحيات المسؤول (يعتمد على المستخدم الحالي)
async def require_admin(user: dict = Depends(get_current_user)):
    if user["role"] != "admin":
        raise HTTPException(status_code=403, detail="Admin required")
    return user

@app.get("/admin/stats")
async def admin_stats(admin: dict = Depends(require_admin)):
    return {"admin": admin["username"], "total_products": 1000000}

الناتج:

TEXT
# تم تعريف الدالة بنجاح

5. دورة حياة التبعية و yield

(1) مستوى الطلب مقابل مستوى التطبيق

دورة الحياة طريقة التعريف النطاق الاستخدامات النموذجية
مستوى الطلب def dep(): yield x; cleanup يتم إنشاؤه وإتلافه في كل طلب جلسة قاعدة البيانات، اتصال Redis
مستوى التطبيق @app.on_event("startup") يتم إنشاؤه عند بدء التطبيق تهيئة المحرك، تجمع الاتصالات

(1) ▶مثال: تبعيات yield وتنظيف الموارد

PYTHON
from fastapi import FastAPI, Depends

app = FastAPI()

# تبعية بنطاق الطلب مع التنظيف
async def get_db_session():
    # الإعداد: إنشاء الجلسة
    session = {"id": "session-123", "active": True}
    print(f"DB session opened: {session['id']}")
    try:
        yield session  # يتم حقن هذا في نقطة النهاية
    finally:
        # التنظيف: إغلاق الجلسة (يعمل بعد الاستجابة)
        session["active"] = False
        print(f"DB session closed: {session['id']}")

@app.get("/products")
async def list_products(db: dict = Depends(get_db_session)):
    print(f"Using DB session: {db['id']}")
    return {"session_id": db["id"]}

الناتج (سجلات الخادم):

TEXT
DB session opened: session-123
Using DB session: session-123
DB session closed: session-123

6. التبعيات العامة وتبعيات مجموعات التوجيه

(1) تعريفات التبعيات بنطاقات مختلفة

(1) ▶مثال: تبعيات مجموعة التوجيه

PYTHON
from fastapi import FastAPI, Depends, APIRouter, Header, HTTPException

async def verify_api_key(x_api_key: str = Header(...)):
    if x_api_key != "secret-key-123":
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key

app = FastAPI()

# المسارات العامة - لا حاجة لمصادقة
public_router = APIRouter()

@public_router.get("/health")
async def health():
    return {"status": "healthy"}

# المسارات المحمية - مفتاح API مطلوب لجميع المسارات في هذه المجموعة
protected_router = APIRouter(dependencies=[Depends(verify_api_key)])

@protected_router.get("/products")
async def list_products():
    return [{"id": 1, "name": "Widget"}]

@protected_router.post("/products")
async def create_product():
    return {"id": 2, "name": "New Product"}

# تسجيل الموجهات
app.include_router(public_router)
app.include_router(protected_router, prefix="/api/v1")

الناتج:

TEXT
# تم تعريف الدالة بنجاح
نطاق التبعية موقع التعريف نطاق التأثير
مستوى نقطة النهاية @app.get("/", dependencies=[...]) نقطة نهاية واحدة
مستوى مجموعة التوجيه APIRouter(dependencies=[...]) جميع نقاط النهاية في مجموعة التوجيه
المستوى العام FastAPI(dependencies=[...]) جميع نقاط نهاية التطبيق

7. تغطية التبعيات والاختبار

(1) dependency_overrides

أثناء الاختبار، من الضروري استبدال تبعيات الإنتاج (مثل اتصالات قاعدة البيانات وواجهات API الخارجية)؛ يسمح لك app.dependency_overrides باستبدال تطبيقات التبعية دون تعديل الكود المصدري.

(1) ▶مثال: استبدال تبعيات قاعدة البيانات في الاختبارات

PYTHON
from fastapi.testclient import TestClient
from fastapi import FastAPI, Depends

app = FastAPI()

# التبعية الحقيقية
async def get_db():
    return {"type": "postgresql", "host": "prod-db"}

# تبعية وهمية للاختبار
def get_mock_db():
    return {"type": "sqlite", "host": "memory"}

@app.get("/db-info")
async def db_info(db: dict = Depends(get_db)):
    return db

# في الاختبارات:
def test_db_info():
    app.dependency_overrides[get_db] = get_mock_db
    client = TestClient(app)
    response = client.get("/db-info")
    assert response.json() == {"type": "sqlite", "host": "memory"}
    # تنظيف
    app.dependency_overrides.clear()

الناتج:

TEXT
# تم تعريف الدالة بنجاح

❓أسئلة شائعة

س هل يتم استدعاء تبعيات Depends عدة مرات؟
ج ضمن نفس الطلب، يتم تنفيذ كل تبعية مرة واحدة فقط، ويتم تخزين النتيجة مؤقتًا لإعادة استخدامها. للطلبات المختلفة، يتم إعادة تنفيذ التبعية.
س متى يتم تنفيذ دالة التنظيف لتبعية yield؟
ج يتم تنفيذها بعد إرسال الاستجابة إلى العميل. إذا ألقت نقطة النهاية استثناءً، لا تزال دالة التنظيف تُنفَّذ (مشابه لـ try/finally).
س كم يمكن أن يكون عمق تداخل التبعيات؟
ج لا يوجد حد صارم، لكن التداخل أكثر من ثلاث مستويات يجعل التصحيح أكثر صعوبة. سلسلة DI ثلاثية المستويات في PriceTracker (get_db → get_current_user → require_admin) هي الحد الأعلى الموصى به.
س هل يمكن استخدام التبعيات المتزامنة وغير المتزامنة معًا؟
ج نعم. يتعامل FastAPI تلقائيًا مع التبعيات المتزامنة وغير المتزامنة. تعمل التبعيات المتزامنة في تجمع الخيوط، بينما تعمل التبعيات غير المتزامنة في حلقة الأحداث.
س هل يؤثر dependency_overrides على اختبارات أخرى؟
ج نعم، لأنه يعدل كائن app. تأكد من استدعاء app.dependency_overrides.clear() أو استخدام fixtures لإدارته بعد انتهاء الاختبار.
س كيف يتم حقن معاملات init لتبعيات الأصناف؟
ج تمامًا مثل تبعيات الدوال، يتم تحليل معاملات init

📖ملخص

  • يستخرج Depends() المنطق المتكرر إلى دوال أو أصناف مساعدة قابلة لإعادة الاستخدام؛ تحتاج نقاط النهاية فقط إلى تعريفها ولا تحتاج للقلق بشأن التنفيذ
  • حل تكراري تلقائي للتبعيات الفرعية؛ تخزين مؤقت ضمن نفس الطلب لتجنب العمليات المكررة
  • ينفذ yield إدارة دورة حياة التبعيات على مستوى الطلب لضمان إقران إنشاء الموارد وتنظيفها
  • تبعيات مجموعات التوجيه (APIRouter(dependencies=[...])) تسمح لمجموعة من نقاط النهاية بمشاركة منطق المصادقة والتحقق
  • app.dependency_overrides هو أداة اختبار قوية تستبدل التبعيات الفعلية لجميع نقاط النهاية بسطر واحد من الكود

📝تمارين

  1. مشكلة أساسية (الصعوبة ⭐): أنشئ دالة مساعدة get_db تُرجع قاموسًا يحاكي اتصال قاعدة بيانات، وحقنها واستخدمها في كلتا نقطتي النهاية. تلميح: db: dict = Depends(get_db)
  2. مشكلة متقدمة (الصعوبة ⭐⭐): نفّذ مستويين من التبعيات المتداخلة: get_dbget_current_user (قراءة رأس Authorization من الطلب)، وحقن معلومات المستخدم الحالي في نقطة النهاية /me. تلميح: user: dict = Depends(get_current_user)
  3. تحدي (الصعوبة ⭐⭐⭐): نفّذ سلسلة DI من ثلاث طبقات لـ PriceTracker: get_db (yield + تنظيف) → get_current_user (التحقق من الرمز) → require_subscription("pro") (فحص مستوى الاشتراك)، واستبدل تبعية DB بـ dependency_overrides أثناء الاختبار. تلميح: تبعية yield + app.dependency_overrides[get_db] = mock_fn

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%