تعمق في نظام حقن التبعية القوي في FastAPI. تعلم التقنيات المتقدمة والاعتمادات المخصصة والنطاقات واستراتيجيات الاختبار لتطوير واجهة برمجة تطبيقات قوية.
نظام الاعتمادية في FastAPI: حقن التبعية المتقدم
يعتبر نظام حقن التبعية (DI) في FastAPI حجر الزاوية في تصميمه، حيث يعزز النمطية وقابلية الاختبار وإعادة الاستخدام. في حين أن الاستخدام الأساسي مباشر، فإن إتقان تقنيات DI المتقدمة يفتح قوة ومرونة كبيرتين. تتعمق هذه المقالة في حقن التبعية المتقدم في FastAPI، وتغطي التبعيات المخصصة والنطاقات واستراتيجيات الاختبار وأفضل الممارسات.
فهم الأساسيات
قبل الخوض في الموضوعات المتقدمة، دعنا نلخص بسرعة أساسيات حقن التبعية في FastAPI:
- التبعيات كوظائف: يتم تعريف التبعيات كوظائف بايثون عادية.
- الحقن التلقائي: يقوم FastAPI تلقائيًا بحقن هذه التبعيات في عمليات المسار بناءً على تلميحات النوع.
- تلميحات النوع كعقود: تحدد تلميحات النوع أنواع الإدخال المتوقعة للتبعيات ووظائف عملية المسار.
- التبعيات الهرمية: يمكن أن تعتمد التبعيات على تبعيات أخرى، مما يؤدي إلى إنشاء شجرة تبعية.
إليك مثال بسيط:
from fastapi import FastAPI, Depends
app = FastAPI()
def get_db():
db = {"items": []}
try:
yield db
finally:
# Close the connection if needed
pass
@app.get("/items/")
async def read_items(db: dict = Depends(get_db)):
return db["items"]
في هذا المثال، get_db هي تبعية توفر اتصالاً بقاعدة البيانات. يستدعي FastAPI تلقائيًا get_db ويحقن النتيجة في وظيفة read_items.
تقنيات التبعية المتقدمة
1. استخدام الفئات كتابعيات
في حين أن الوظائف شائعة الاستخدام، يمكن أن تكون الفئات أيضًا بمثابة تبعيات، مما يسمح بإدارة وحالات وطرق أكثر تعقيدًا. يكون هذا مفيدًا بشكل خاص عند التعامل مع اتصالات قاعدة البيانات أو خدمات المصادقة أو الموارد الأخرى التي تتطلب التهيئة والتنظيف.
from fastapi import FastAPI, Depends
app = FastAPI()
class Database:
def __init__(self):
self.connection = self.create_connection()
def create_connection(self):
# Simulate a database connection
print("Creating database connection...")
return {"items": []}
def close(self):
# Simulate closing a database connection
print("Closing database connection...")
def get_db():
db = Database()
try:
yield db.connection
finally:
db.close()
@app.get("/items/")
async def read_items(db: dict = Depends(get_db)):
return db["items"]
في هذا المثال، تغلف فئة Database منطق اتصال قاعدة البيانات. تنشئ تبعية get_db مثيلاً لفئة Database وتعطي الاتصال. تضمن كتلة finally إغلاق الاتصال بشكل صحيح بعد معالجة الطلب.
2. تجاوز التبعيات
يسمح لك FastAPI بتجاوز التبعيات، وهو أمر بالغ الأهمية للاختبار والتطوير. يمكنك استبدال تبعية حقيقية بمحاكاة أو كعب معطل لعزل التعليمات البرمجية الخاصة بك وضمان نتائج متسقة.
from fastapi import FastAPI, Depends
app = FastAPI()
# Original dependency
def get_settings():
# Simulate loading settings from a file or environment
return {"api_key": "real_api_key"}
@app.get("/items/")
async def read_items(settings: dict = Depends(get_settings)):
return {"api_key": settings["api_key"]}
# Override for testing
def get_settings_override():
return {"api_key": "test_api_key"}
app.dependency_overrides[get_settings] = get_settings_override
# To revert back to the original:
# del app.dependency_overrides[get_settings]
في هذا المثال، يتم تجاوز تبعية get_settings بـ get_settings_override. يتيح لك ذلك استخدام مفتاح API مختلف لأغراض الاختبار.
3. استخدام `contextvars` للبيانات ذات النطاق المحدد للطلب
contextvars هي وحدة بايثون توفر متغيرات محلية للسياق. هذا مفيد لتخزين البيانات الخاصة بالطلب، مثل معلومات مصادقة المستخدم أو معرفات الطلب أو بيانات التتبع. يتيح لك استخدام contextvars مع حقن التبعية في FastAPI الوصول إلى هذه البيانات في جميع أنحاء تطبيقك.
import contextvars
from fastapi import FastAPI, Depends, Request
app = FastAPI()
# Create a context variable for the request ID
request_id_var = contextvars.ContextVar("request_id")
# Middleware to set the request ID
@app.middleware("http")
async def add_request_id(request: Request, call_next):
request_id = str(uuid.uuid4())
request_id_var.set(request_id)
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response
# Dependency to access the request ID
def get_request_id():
return request_id_var.get()
@app.get("/items/")
async def read_items(request_id: str = Depends(get_request_id)):
return {"request_id": request_id}
في هذا المثال، تحدد البرامج الوسيطة معرف طلب فريد لكل طلب وارد. تسترد تبعية get_request_id معرف الطلب من سياق contextvars. يتيح لك ذلك تتبع الطلبات عبر تطبيقك.
4. التبعيات غير المتزامنة
يدعم FastAPI بسلاسة التبعيات غير المتزامنة. هذا ضروري لعمليات الإدخال/الإخراج غير المحظورة، مثل استعلامات قاعدة البيانات أو استدعاءات API الخارجية. ما عليك سوى تحديد وظيفة التبعية الخاصة بك كوظيفة async def.
from fastapi import FastAPI, Depends
import asyncio
app = FastAPI()
async def get_data():
# Simulate an asynchronous operation
await asyncio.sleep(1)
return {"message": "Hello from async dependency!"}
@app.get("/items/")
async def read_items(data: dict = Depends(get_data)):
return data
في هذا المثال، تعد تبعية get_data وظيفة غير متزامنة تحاكي التأخير. ينتظر FastAPI تلقائيًا نتيجة التبعية غير المتزامنة قبل حقنها في وظيفة read_items.
5. استخدام المولدات لإدارة الموارد (اتصالات قاعدة البيانات، مقابض الملفات)
يوفر استخدام المولدات (مع yield) إدارة تلقائية للموارد، مما يضمن إغلاق/إصدار الموارد بشكل صحيح عبر كتلة finally حتى في حالة حدوث أخطاء.
from fastapi import FastAPI, Depends
app = FastAPI()
def get_file_handle():
try:
file_handle = open("my_file.txt", "r")
yield file_handle
finally:
file_handle.close()
@app.get("/file_content/")
async def read_file_content(file_handle = Depends(get_file_handle)):
content = file_handle.read()
return {"content": content}
نطاقات التبعية ودورات الحياة
يعد فهم نطاقات التبعية أمرًا بالغ الأهمية لإدارة دورة حياة التبعيات وضمان تخصيص الموارد وإصدارها بشكل صحيح. لا يقدم FastAPI بشكل مباشر تعليقات توضيحية صريحة للنطاق مثل بعض أطر عمل DI الأخرى (مثل @RequestScope الخاص بـ Spring، @ApplicationScope)، ولكن الجمع بين كيفية تعريف التبعيات وكيفية إدارة الحالة يحقق نتائج مماثلة.
نطاق الطلب
هذا هو النطاق الأكثر شيوعًا. يتلقى كل طلب مثيلًا جديدًا من التبعية. يتحقق هذا عادةً عن طريق إنشاء كائن جديد داخل وظيفة التبعية وإصداره، كما هو موضح في مثال قاعدة البيانات سابقًا. يساعد استخدام contextvars أيضًا في تحقيق نطاق الطلب.
نطاق التطبيق (Singleton)
يتم إنشاء مثيل واحد للتبعية ومشاركته عبر جميع الطلبات طوال دورة حياة التطبيق. غالبًا ما يتم ذلك باستخدام متغيرات عامة أو سمات على مستوى الفئة.
from fastapi import FastAPI, Depends
app = FastAPI()
# Singleton instance
GLOBAL_SETTING = {"api_key": "global_api_key"}
def get_global_setting():
return GLOBAL_SETTING
@app.get("/items/")
async def read_items(setting: dict = Depends(get_global_setting)):
return setting
كن حذرًا عند استخدام التبعيات ذات النطاق التطبيقي مع حالة قابلة للتغيير، حيث يمكن أن تؤثر التغييرات التي يتم إجراؤها بواسطة طلب واحد على طلبات أخرى. قد تكون هناك حاجة إلى آليات المزامنة (الأقفال، إلخ) إذا كان تطبيقك يحتوي على طلبات متزامنة.
نطاق الجلسة (بيانات خاصة بالمستخدم)
اربط التبعيات بجلسات المستخدم. يتطلب هذا آلية إدارة الجلسة (على سبيل المثال، استخدام ملفات تعريف الارتباط أو JWTs) ويتضمن عادةً تخزين التبعيات في بيانات الجلسة.
from fastapi import FastAPI, Depends, Cookie
from typing import Optional
import uuid
app = FastAPI()
# In a real app, store sessions in a database or cache
sessions = {}
async def get_user_id(session_id: Optional[str] = Cookie(None)) -> str:
if session_id is None or session_id not in sessions:
session_id = str(uuid.uuid4())
sessions[session_id] = {"user_id": str(uuid.uuid4())} # Assign a random user ID
return sessions[session_id]["user_id"]
@app.get("/profile/")
async def read_profile(user_id: str = Depends(get_user_id)):
return {"user_id": user_id}
اختبار التبعيات
تتمثل إحدى الفوائد الأساسية لحقن التبعية في تحسين قابلية الاختبار. من خلال فصل المكونات، يمكنك بسهولة استبدال التبعيات بعمليات محاكاة أو جذوع أثناء الاختبار.
1. تجاوز التبعيات في الاختبارات
كما هو موضح سابقًا، تعد آلية dependency_overrides في FastAPI مثالية للاختبار. قم بإنشاء تبعيات وهمية ترجع نتائج يمكن التنبؤ بها واستخدمها لعزل التعليمات البرمجية الخاصة بك قيد الاختبار.
from fastapi.testclient import TestClient
from fastapi import FastAPI, Depends
app = FastAPI()
# Original dependency
def get_external_data():
# Simulate fetching data from an external API
return {"data": "Real external data"}
@app.get("/data/")
async def read_data(data: dict = Depends(get_external_data)):
return data
# Test
from unittest.mock import MagicMock
def get_external_data_mock():
return {"data": "Mocked external data"}
def test_read_data():
app.dependency_overrides[get_external_data] = get_external_data_mock
client = TestClient(app)
response = client.get("/data/")
assert response.status_code == 200
assert response.json() == {"data": "Mocked external data"}
# Clean up overrides
app.dependency_overrides.clear()
2. استخدام مكتبات المحاكاة
توفر مكتبات مثل unittest.mock أدوات قوية لإنشاء كائنات وهمية والتحكم في سلوكها. يمكنك استخدام عمليات المحاكاة لمحاكاة التبعيات المعقدة والتحقق من أن التعليمات البرمجية الخاصة بك تتفاعل معها بشكل صحيح.
import unittest
from unittest.mock import MagicMock
# (Define the FastAPI app and get_external_data as above)
class TestReadData(unittest.TestCase):
def test_read_data_with_mock(self):
# Create a mock for the get_external_data dependency
mock_get_external_data = MagicMock(return_value={"data": "Mocked data from unittest"})
# Override the dependency with the mock
app.dependency_overrides[get_external_data] = mock_get_external_data
client = TestClient(app)
response = client.get("/data/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"data": "Mocked data from unittest"})
# Assert that the mock was called
mock_get_external_data.assert_called_once()
# Clean up overrides
app.dependency_overrides.clear()
3. حقن التبعية للاختبارات الوحدة (خارج سياق FastAPI)
حتى عند اختبار وظائف الوحدة *خارج* معالجات نقطة نهاية API، لا تزال مبادئ حقن التبعية سارية. بدلاً من الاعتماد على Depends الخاص بـ FastAPI، قم بحقن التبعيات يدويًا في الوظيفة قيد الاختبار.
# Example function to test
def process_data(data_source):
data = data_source.fetch_data()
# ... process the data ...
return processed_data
class MockDataSource:
def fetch_data(self):
return {"example": "data"}
# Unit test
def test_process_data():
mock_data_source = MockDataSource()
result = process_data(mock_data_source)
# Assertions on the result
اعتبارات الأمان مع حقن التبعية
يقدم حقن التبعية، على الرغم من كونه مفيدًا، مخاوف أمنية محتملة إذا لم يتم تنفيذه بعناية.
1. التباس التبعية
تأكد من أنك تسحب التبعيات من مصادر موثوقة. تحقق من سلامة الحزمة واستخدم مديري الحزم مع إمكانات فحص الثغرات الأمنية. هذا مبدأ أمان عام لسلسلة توريد البرامج، ولكنه يتفاقم بسبب DI حيث قد تقوم بحقن مكونات من مصادر متنوعة.
2. حقن التبعيات الضارة
كن على دراية بالتبعيات التي تقبل مدخلات خارجية دون التحقق من الصحة المناسب. يمكن للمهاجم حقن تعليمات برمجية أو بيانات ضارة من خلال تبعية مخترقة. قم بتطهير جميع مدخلات المستخدم وتنفيذ آليات تحقق قوية.
3. تسرب المعلومات من خلال التبعيات
تأكد من أن التبعيات لا تكشف عن غير قصد عن معلومات حساسة. راجع التعليمات البرمجية وتكوين تبعياتك لتحديد الثغرات الأمنية المحتملة لتسرب المعلومات.
4. الأسرار المشفرة
تجنب ترميز الأسرار (مفاتيح API وكلمات مرور قاعدة البيانات وما إلى ذلك) مباشرة في رمز التبعية الخاص بك. استخدم متغيرات البيئة أو أدوات إدارة التكوين الآمنة لتخزين وإدارة الأسرار.
import os
from fastapi import FastAPI, Depends
app = FastAPI()
def get_api_key():
api_key = os.environ.get("API_KEY")
if not api_key:
raise ValueError("API_KEY environment variable not set.")
return api_key
@app.get("/secure_endpoint/")
async def secure_endpoint(api_key: str = Depends(get_api_key)):
# Use api_key for authentication/authorization
return {"message": "Access granted"}
تحسين الأداء مع حقن التبعية
يمكن أن يؤثر حقن التبعية على الأداء إذا لم يتم استخدامه بحكمة. فيما يلي بعض استراتيجيات التحسين:
1. تقليل تكلفة إنشاء التبعية
تجنب إنشاء تبعيات باهظة الثمن في كل طلب إن أمكن. إذا كانت التبعية عديمة الحالة أو يمكن مشاركتها عبر الطلبات، ففكر في استخدام نطاق singleton أو تخزين مثيل التبعية مؤقتًا.
2. التهيئة الكسولة
قم بتهيئة التبعيات فقط عند الحاجة إليها. يمكن أن يقلل ذلك من وقت بدء التشغيل واستهلاك الذاكرة، خاصة بالنسبة للتطبيقات التي تحتوي على العديد من التبعيات.
3. تخزين نتائج التبعية مؤقتًا
قم بتخزين نتائج حسابات التبعية المكلفة مؤقتًا إذا كان من المحتمل إعادة استخدام النتائج. استخدم آليات التخزين المؤقت (مثل Redis، Memcached) لتخزين واسترداد نتائج التبعية.
4. تحسين الرسم البياني للتبعية
حلل الرسم البياني للتبعية الخاص بك لتحديد الاختناقات المحتملة. قم بتبسيط هيكل التبعية وتقليل عدد التبعيات إن أمكن.
5. التبعيات غير المتزامنة لعمليات الإدخال/الإخراج المرتبطة
استخدم التبعيات غير المتزامنة عند إجراء عمليات إدخال/إخراج حظر، مثل استعلامات قاعدة البيانات أو استدعاءات API الخارجية. هذا يمنع حظر سلسلة الرسائل الرئيسية ويحسن الاستجابة الشاملة للتطبيق.
أفضل الممارسات لحقن التبعية في FastAPI
- حافظ على التبعيات بسيطة: اهدف إلى الحصول على تبعيات صغيرة ومركزة تؤدي مهمة واحدة. هذا يحسن القراءة وقابلية الاختبار وقابلية الصيانة.
- استخدم تلميحات النوع: استفد من تلميحات النوع لتحديد أنواع الإدخال والإخراج المتوقعة للتبعيات بوضوح. هذا يحسن وضوح التعليمات البرمجية ويسمح لـ FastAPI بإجراء فحص النوع الثابت.
- وثق التبعيات: وثق الغرض والاستخدام لكل تبعية. يساعد هذا المطورين الآخرين على فهم كيفية استخدام التعليمات البرمجية الخاصة بك وصيانتها.
- اختبر التبعيات بدقة: اكتب اختبارات وحدة للتبعيات الخاصة بك للتأكد من أنها تتصرف كما هو متوقع. يساعد هذا في منع الأخطاء وتحسين الموثوقية العامة لتطبيقك.
- استخدم اصطلاحات تسمية متسقة: استخدم اصطلاحات تسمية متسقة للتبعيات الخاصة بك لتحسين إمكانية قراءة التعليمات البرمجية.
- تجنب التبعيات الدائرية: يمكن أن تؤدي التبعيات الدائرية إلى تعليمات برمجية معقدة ويصعب تصحيحها. أعد تصميم التعليمات البرمجية الخاصة بك للتخلص من التبعيات الدائرية.
- ضع في اعتبارك حاويات حقن التبعية (اختياري): في حين أن حقن التبعية المضمن في FastAPI كافٍ لمعظم الحالات، ففكر في استخدام حاوية حقن تبعية مخصصة (على سبيل المثال،
inject،autowire) للتطبيقات الأكثر تعقيدًا.
الخلاصة
يعد نظام حقن التبعية في FastAPI أداة قوية تعزز النمطية وقابلية الاختبار وإعادة الاستخدام. من خلال إتقان التقنيات المتقدمة، مثل استخدام الفئات كتابعيات وتجاوز التبعيات واستخدام contextvars، يمكنك إنشاء واجهات برمجة تطبيقات قوية وقابلة للتطوير. يعد فهم نطاقات التبعية ودورات الحياة أمرًا بالغ الأهمية لإدارة الموارد بفعالية. قم دائمًا بإعطاء الأولوية لاختبار تبعياتك بدقة لضمان موثوقية وأمان تطبيقاتك. من خلال اتباع أفضل الممارسات والنظر في الآثار الأمنية والمترتبة على الأداء المحتملة، يمكنك الاستفادة من الإمكانات الكاملة لنظام حقن التبعية في FastAPI.