أطلق العنان لقوة FastAPI لتحميلات ملفات النماذج المتعددة الأجزاء بكفاءة. يغطي هذا الدليل الشامل أفضل الممارسات ومعالجة الأخطاء والتقنيات المتقدمة للمطورين العالميين.
إتقان تحميل الملفات في FastAPI: الغوص العميق في معالجة بيانات النماذج المتعددة الأجزاء
في تطبيقات الويب الحديثة، تعد القدرة على التعامل مع تحميلات الملفات مطلبًا أساسيًا. سواء كان المستخدمون يرسلون صورًا شخصية، أو مستندات للمعالجة، أو وسائط للمشاركة، فإن آليات تحميل الملفات القوية والفعالة ضرورية. يتفوق FastAPI، وهو إطار عمل ويب بايثون عالي الأداء، في هذا المجال، حيث يوفر طرقًا مبسطة لإدارة بيانات النماذج المتعددة الأجزاء، وهي المعيار لإرسال الملفات عبر HTTP. سيأخذك هذا الدليل الشامل عبر تعقيدات تحميل الملفات في FastAPI، من التنفيذ الأساسي إلى الاعتبارات المتقدمة، مما يضمن لك بناء واجهات برمجة تطبيقات قوية وقابلة للتطوير لجمهور عالمي بثقة.
فهم بيانات النماذج المتعددة الأجزاء
قبل الخوض في تنفيذ FastAPI، من الضروري فهم ماهية بيانات النماذج المتعددة الأجزاء. عندما يرسل متصفح الويب نموذجًا يحتوي على ملفات، فإنه يستخدم عادةً السمة enctype="multipart/form-data". يكسر نوع الترميز هذا إرسال النموذج إلى أجزاء متعددة، لكل منها نوع المحتوى الخاص به ومعلومات التوزيع. هذا يسمح بنقل أنواع مختلفة من البيانات ضمن طلب HTTP واحد، بما في ذلك حقول النص، والحقول غير النصية، والملفات الثنائية.
يتكون كل جزء في طلب متعدد الأجزاء من:
- رأس Content-Disposition: يحدد اسم حقل النموذج (
name) ، وبالنسبة للملفات، اسم الملف الأصلي (filename). - رأس Content-Type: يشير إلى نوع MIME للجزء (على سبيل المثال ،
text/plain،image/jpeg). - الجسم (Body): البيانات الفعلية لذلك الجزء.
نهج FastAPI لتحميل الملفات
يستفيد FastAPI من مكتبة بايثون القياسية ويتكامل بسلاسة مع Pydantic للتحقق من صحة البيانات. بالنسبة لتحميلات الملفات، فإنه يستخدم نوع UploadFile من الوحدة fastapi. توفر هذه الفئة واجهة مريحة وآمنة للوصول إلى بيانات الملف الذي تم تحميله.
تنفيذ تحميل ملف بسيط
لنبدأ بمثال بسيط لكيفية إنشاء نقطة نهاية في FastAPI تقبل تحميل ملف واحد. سنستخدم الدالة File من fastapi لتحديد معلمة الملف.
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: UploadFile):
return {"filename": file.filename, "content_type": file.content_type}
في هذا المثال:
- نقوم باستيراد
FastAPIوFileوUploadFile. - تم تعريف نقطة النهاية
/files/كطلبPOST. - تم وصف معلمة
fileبـUploadFile، مما يشير إلى أنها تتوقع تحميل ملف. - داخل دالة نقطة النهاية ، يمكننا الوصول إلى خصائص الملف الذي تم تحميله مثل
filenameوcontent_type.
عندما يرسل العميل طلب POST إلى /files/ مع إرفاق ملف (عادةً عبر نموذج مع enctype="multipart/form-data") ، سيقوم FastAPI تلقائيًا بمعالجة التحليل وتوفير كائن UploadFile. يمكنك بعد ذلك التفاعل مع هذا الكائن.
حفظ الملفات التي تم تحميلها
غالبًا، ستحتاج إلى حفظ الملف الذي تم تحميله على القرص أو معالجة محتوياته. يوفر كائن UploadFile طرقًا لهذا:
read(): يقرأ محتوى الملف بالكامل في الذاكرة كبايتات. استخدم هذا للملفات الصغيرة.write(content: bytes): يكتب البايتات إلى الملف.seek(offset: int): يغير موضع الملف الحالي.close(): يغلق الملف.
من المهم التعامل مع عمليات الملفات بشكل غير متزامن، خاصة عند التعامل مع ملفات كبيرة أو مهام مرتبطة بالدخل/الخرج. يدعم UploadFile في FastAPI العمليات غير المتزامنة.
from fastapi import FastAPI, File, UploadFile
import shutil
app = FastAPI()
@app.post("/files/save/")
async def save_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"file '{file.filename}' saved at '{file_location}'"}
في هذا المثال المحسّن:
- نستخدم
File(...)للإشارة إلى أن هذه المعلمة مطلوبة. - نحدد مسارًا محليًا سيتم حفظ الملف فيه. تأكد من وجود الدليل
uploads. - نفتح ملف الوجهة في وضع الكتابة الثنائية (`"wb+"`).
- نقوم بقراءة محتوى الملف الذي تم تحميله بشكل غير متزامن باستخدام
await file.read()ثم كتابته إلى الملف المحلي.
ملاحظة: قد تكون قراءة الملف بالكامل في الذاكرة باستخدام await file.read() مشكلة للملفات الكبيرة جدًا. في مثل هذه السيناريوهات، فكر في بث محتوى الملف.
بث محتوى الملف
بالنسبة للملفات الكبيرة، فإن قراءة المحتوى بالكامل في الذاكرة يمكن أن تؤدي إلى استهلاك مفرط للذاكرة وأخطاء محتملة في نفاد الذاكرة. نهج أكثر كفاءة من حيث الذاكرة هو بث الملف مقطعًا تلو الآخر. الدالة shutil.copyfileobj ممتازة لهذا الغرض، ولكن نحتاج إلى تكييفها للعمليات غير المتزامنة.
from fastapi import FastAPI, File, UploadFile
import aiofiles # Install using: pip install aiofiles
app = FastAPI()
@app.post("/files/stream/")
async def stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
content = await file.read()
await out_file.write(content)
return {"info": f"file '{file.filename}' streamed and saved at '{file_location}'"}
باستخدام aiofiles، يمكننا بث محتوى الملف الذي تم تحميله بكفاءة إلى ملف وجهة دون تحميل الملف بأكمله في الذاكرة دفعة واحدة. await file.read() في هذا السياق لا يزال يقرأ الملف بأكمله، ولكن aiofiles يتعامل مع الكتابة بكفاءة أكبر. للبث الفعلي مقطعًا بمقطع مع UploadFile، ستقوم عادةً بالتكرار عبر await file.read(chunk_size)، ولكن aiofiles.open و await out_file.write(content) هو نمط شائع وفعال للحفظ.
نهج بث أكثر وضوحًا باستخدام التقطيع:
from fastapi import FastAPI, File, UploadFile
import aiofiles
app = FastAPI()
CHUNK_SIZE = 1024 * 1024 # 1MB chunk size
@app.post("/files/chunked_stream/")
async def chunked_stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
while content := await file.read(CHUNK_SIZE):
await out_file.write(content)
return {"info": f"file '{file.filename}' chunked streamed and saved at '{file_location}'"}
نقطة النهاية `chunked_stream_file` هذه تقرأ الملف في مقاطع حجمها 1 ميغابايت وتكتب كل مقطع إلى ملف الإخراج. هذه هي الطريقة الأكثر كفاءة من حيث الذاكرة للتعامل مع الملفات الكبيرة جدًا.
التعامل مع تحميلات الملفات المتعددة
غالبًا ما تتطلب تطبيقات الويب من المستخدمين تحميل ملفات متعددة في وقت واحد. يجعل FastAPI هذا أمرًا سهلاً.
تحميل قائمة من الملفات
يمكنك قبول قائمة ملفات عن طريق وصف معلمتك بقائمة من UploadFile.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/multiple/")
async def create_multiple_files(
files: List[UploadFile] = File(...)
):
results = []
for file in files:
# Process each file, e.g., save it
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {"files_processed": results}
في هذا السيناريو، يحتاج العميل إلى إرسال أجزاء متعددة بنفس اسم حقل النموذج (على سبيل المثال ، files). سيقوم FastAPI بجمعها في قائمة بايثون من كائنات UploadFile.
مزج الملفات وبيانات النماذج الأخرى
من الشائع أن تحتوي النماذج على كل من حقول الملفات وحقول النص العادية. يتعامل FastAPI مع هذا بالسماح لك بوصف معلمات أخرى باستخدام الأوصاف القياسية، جنبًا إلى جنب مع Form لحقول النماذج التي ليست ملفات.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/mixed/")
async def upload_mixed_data(
description: str = Form(...),
files: List[UploadFile] = File(...) # Accepts multiple files with the name 'files'
):
results = []
for file in files:
# Process each file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {
"description": description,
"files_processed": results
}
عند استخدام أدوات مثل Swagger UI أو Postman، ستحدد description كحقل نموذج عادي ثم تضيف أجزاء متعددة لحقل files، كل منها بنوع محتواه مضبوط على نوع الصورة/المستند المناسب.
الميزات المتقدمة وأفضل الممارسات
بالإضافة إلى معالجة الملفات الأساسية، هناك العديد من الميزات المتقدمة وأفضل الممارسات الضرورية لبناء واجهات برمجة تطبيقات تحميل ملفات قوية.
حدود حجم الملف
السماح بتحميل ملفات غير محدودة يمكن أن يؤدي إلى هجمات حجب الخدمة أو استهلاك مفرط للموارد. في حين أن FastAPI نفسه لا يفرض حدودًا صارمة افتراضيًا على مستوى الإطار، يجب عليك تنفيذ فحوصات:
- على مستوى التطبيق: تحقق من حجم الملف بعد استلامه ولكن قبل المعالجة أو الحفظ.
- على مستوى خادم الويب/الوكيل: قم بتكوين خادم الويب الخاص بك (على سبيل المثال ، Nginx ، Uvicorn مع عوامل) لرفض الطلبات التي تتجاوز حجم حمولة معين.
مثال على فحص حجم على مستوى التطبيق:
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
MAX_FILE_SIZE_MB = 10
MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
@app.post("/files/limited_size/")
async def upload_with_size_limit(file: UploadFile = File(...)):
if len(await file.read()) > MAX_FILE_SIZE_BYTES:
raise HTTPException(status_code=400, detail=f"File is too large. Maximum size is {MAX_FILE_SIZE_MB}MB.")
# Reset file pointer to read content again
await file.seek(0)
# Proceed with saving or processing the file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully."}
هام: بعد قراءة الملف للتحقق من حجمه، يجب عليك استخدام await file.seek(0) لإعادة تعيين مؤشر الملف إلى البداية إذا كنت تنوي قراءة محتواه مرة أخرى (على سبيل المثال ، لحفظه).
أنواع الملفات المسموح بها (أنواع MIME)
تقييد التحميلات على أنواع ملفات محددة يعزز الأمان ويضمن سلامة البيانات. يمكنك التحقق من السمة content_type لكائن UploadFile.
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
ALLOWED_FILE_TYPES = {"image/jpeg", "image/png", "application/pdf"}
@app.post("/files/restricted_types/")
async def upload_restricted_types(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_FILE_TYPES:
raise HTTPException(status_code=400, detail=f"Unsupported file type: {file.content_type}. Allowed types are: {', '.join(ALLOWED_FILE_TYPES)}")
# Proceed with saving or processing the file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully and is of an allowed type."}
لفحص أنواع أكثر قوة، خاصة للصور، قد تفكر في استخدام مكتبات مثل Pillow لفحص محتوى الملف الفعلي، حيث يمكن في بعض الأحيان تزوير أنواع MIME.
معالجة الأخطاء وملاحظات المستخدم
قدم رسائل خطأ واضحة وقابلة للتنفيذ للمستخدم. استخدم HTTPException من FastAPI لاستجابات الأخطاء القياسية لـ HTTP.
- ملف غير موجود/مفقود: إذا لم يتم إرسال معلمة ملف مطلوبة.
- تجاوز حجم الملف: كما هو موضح في مثال حد الحجم.
- نوع ملف غير صالح: كما هو موضح في مثال قيد النوع.
- أخطاء الخادم: للمشكلات أثناء حفظ الملف أو معالجته (على سبيل المثال ، القرص ممتلئ ، أخطاء الأذونات).
اعتبارات الأمان
تقدم تحميلات الملفات مخاطر أمنية:
- ملفات ضارة: تحميل ملفات تنفيذية (
.exe،.sh) أو نصوص برمجية متنكرة كأنواع ملفات أخرى. تحقق دائمًا من أنواع الملفات وفكر في فحص الملفات التي تم تحميلها بحثًا عن برامج ضارة. - اجتياز المسار: قم بتنظيف أسماء الملفات لمنع المهاجمين من تحميل ملفات إلى أدلة غير مقصودة (على سبيل المثال ، باستخدام أسماء ملفات مثل
../../etc/passwd). يقومUploadFileفي FastAPI بإجراء تنظيف أساسي لاسم الملف، ولكن الحذر الإضافي مفيد. - حجب الخدمة: قم بتنفيذ حدود حجم الملف وربما تحديد المعدل على نقاط النهاية للتحميل.
- البرمجة النصية عبر المواقع (XSS): إذا قمت بعرض أسماء الملفات أو محتوى الملف مباشرة على صفحة ويب، فتأكد من أنها معدلة بشكل صحيح لمنع هجمات XSS.
أفضل الممارسات: قم بتخزين الملفات التي تم تحميلها خارج جذر مستند خادم الويب الخاص بك، وقم بتقديمها من خلال نقطة نهاية مخصصة مع ضوابط وصول مناسبة، أو استخدم شبكة توصيل محتوى (CDN).
استخدام نماذج Pydantic مع تحميلات الملفات
بينما UploadFile هو النوع الأساسي للملفات، يمكنك دمج تحميلات الملفات في نماذج Pydantic لهياكل بيانات أكثر تعقيدًا. ومع ذلك، لا يتم دعم حقول تحميل الملفات المباشرة داخل نماذج Pydantic القياسية بشكل أصلي للنماذج المتعددة الأجزاء. بدلاً من ذلك، تتلقى عادةً الملف كمعلمة منفصلة ثم تعالجه ربما إلى تنسيق يمكن تخزينه أو التحقق منه بواسطة نموذج Pydantic.
النمط الشائع هو وجود نموذج Pydantic للبيانات الوصفية ثم تلقي الملف بشكل منفصل:
from fastapi import FastAPI, File, UploadFile, Form
from pydantic import BaseModel
from typing import Optional
class UploadMetadata(BaseModel):
title: str
description: Optional[str] = None
app = FastAPI()
@app.post("/files/model_metadata/")
async def upload_with_metadata(
metadata: str = Form(...), # Receive metadata as a JSON string
file: UploadFile = File(...)
):
import json
try:
metadata_obj = UploadMetadata(**json.loads(metadata))
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format for metadata")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error parsing metadata: {e}")
# Now you have metadata_obj and file
# Proceed with saving file and using metadata
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {
"message": "File uploaded successfully with metadata",
"metadata": metadata_obj,
"filename": file.filename
}
في هذا النمط، يرسل العميل البيانات الوصفية كسلسلة JSON ضمن حقل نموذج (على سبيل المثال ، metadata) والملف كجزء متعدد الأجزاء منفصل. ثم يقوم الخادم بتحليل سلسلة JSON إلى كائن Pydantic.
تحميلات الملفات الكبيرة والتقطيع
بالنسبة للملفات الكبيرة جدًا (على سبيل المثال ، جيجابايت)، حتى البث قد يواجه قيودًا من خادم الويب أو من جانب العميل. تقنية أكثر تقدمًا هي التحميلات المقطعة، حيث يقوم العميل بتقسيم الملف إلى أجزاء أصغر وتحميلها بشكل متسلسل أو بالتوازي. ثم يقوم الخادم بإعادة تجميع هذه الأجزاء. يتطلب هذا عادةً منطقًا مخصصًا من جانب العميل ونقطة نهاية خادم مصممة للتعامل مع إدارة الأجزاء (على سبيل المثال ، تحديد الأجزاء ، التخزين المؤقت ، والتجميع النهائي).
على الرغم من أن FastAPI لا يوفر دعمًا مدمجًا للتحميلات المقطعة التي يبدأها العميل، يمكنك تنفيذ هذا المنطق داخل نقاط نهاية FastAPI الخاصة بك. يتضمن ذلك إنشاء نقاط نهاية تقوم بما يلي:
- استلام أجزاء الملف الفردية.
- تخزين هذه الأجزاء مؤقتًا، ربما مع بيانات وصفية تشير إلى ترتيبها والعدد الإجمالي للأجزاء.
- توفير نقطة نهاية أو آلية للإشارة عندما تم تحميل جميع الأجزاء، مما يؤدي إلى عملية إعادة التجميع.
هذا مسعى أكثر تعقيدًا وغالبًا ما يتضمن مكتبات JavaScript على جانب العميل.
اعتبارات التدويل والعولمة
عند بناء واجهات برمجة تطبيقات لجمهور عالمي، تتطلب تحميلات الملفات اهتمامًا خاصًا:
- أسماء الملفات: قد يستخدم المستخدمون في جميع أنحاء العالم أحرفًا غير ASCII في أسماء الملفات (على سبيل المثال ، علامات التشكيل، الصور الرمزية). تأكد من أن نظامك يتعامل مع أسماء الملفات هذه ويخزنها بشكل صحيح. ترميز UTF-8 قياسي بشكل عام، ولكن التوافق العميق قد يتطلب ترميزًا / فك ترميز وتنظيفًا دقيقين.
- وحدات حجم الملف: في حين أن ميغابايت وجيجابايت شائعة، كن على دراية بكيفية إدراك المستخدمين لأحجام الملفات. يعد عرض الحدود بطريقة سهلة الاستخدام أمرًا مهمًا.
- أنواع المحتوى: قد يقوم المستخدمون بتحميل ملفات بأنواع MIME أقل شيوعًا. تأكد من أن قائمة الأنواع المسموح بها شاملة أو مرنة بما يكفي لحالة الاستخدام الخاصة بك.
- اللوائح الإقليمية: كن على دراية بقوانين ولوائح الإقامة في البيانات في بلدان مختلفة. قد يتطلب تخزين الملفات التي تم تحميلها الامتثال لهذه القواعد.
- واجهة المستخدم: يجب أن تكون الواجهة من جانب العميل لتحميل الملفات بديهية وتدعم لغة المستخدم والموقع.
أدوات ومكتبات للاختبار
يعد اختبار نقاط نهاية تحميل الملفات أمرًا بالغ الأهمية. إليك بعض الأدوات الشائعة:
- Swagger UI (وثائق API التفاعلية): يقوم FastAPI تلقائيًا بإنشاء وثائق Swagger UI. يمكنك اختبار تحميلات الملفات مباشرة من واجهة المتصفح. ابحث عن حقل إدخال الملف وانقر فوق الزر "Choose File".
- Postman: أداة شائعة لتطوير واختبار API. لإرسال طلب تحميل ملف:
- اضبط طريقة الطلب على POST.
- أدخل عنوان URL لنقطة نهاية API الخاصة بك.
- انتقل إلى علامة التبويب "Body".
- حدد "form-data" كنوع.
- في أزواج المفتاح والقيمة، أدخل اسم معلمة الملف الخاصة بك (على سبيل المثال ،
file). - غيّر النوع من "Text" إلى "File".
- انقر فوق "Choose Files" لتحديد ملف من نظامك المحلي.
- إذا كانت لديك حقول نماذج أخرى، فأضفها بشكل مماثل، مع الاحتفاظ بنوعها كـ "Text".
- أرسل الطلب.
- cURL: أداة سطر أوامر لإجراء طلبات HTTP.
- لملف واحد:
curl -X POST -F "file=@/path/to/your/local/file.txt" http://localhost:8000/files/ - لملفات متعددة:
curl -X POST -F "files=@/path/to/file1.txt" -F "files=@/path/to/file2.png" http://localhost:8000/files/multiple/ - للبيانات المختلطة:
curl -X POST -F "description=My description" -F "files=@/path/to/file.txt" http://localhost:8000/files/mixed/ - مكتبة `requests` في بايثون: للاختبار البرمجي.
import requests
url = "http://localhost:8000/files/save/"
files = {'file': open('/path/to/your/local/file.txt', 'rb')}
response = requests.post(url, files=files)
print(response.json())
# For multiple files
url_multiple = "http://localhost:8000/files/multiple/"
files_multiple = {
'files': [('file1.txt', open('/path/to/file1.txt', 'rb')),
('image.png', open('/path/to/image.png', 'rb'))]
}
response_multiple = requests.post(url_multiple, files=files_multiple)
print(response_multiple.json())
# For mixed data
url_mixed = "http://localhost:8000/files/mixed/"
data = {'description': 'Test description'}
files_mixed = {'files': open('/path/to/another_file.txt', 'rb')}
response_mixed = requests.post(url_mixed, data=data, files=files_mixed)
print(response_mixed.json())
خاتمة
يوفر FastAPI طريقة قوية وفعالة وبديهية للتعامل مع تحميلات الملفات المتعددة الأجزاء. من خلال الاستفادة من نوع UploadFile والبرمجة غير المتزامنة، يمكن للمطورين بناء واجهات برمجة تطبيقات قوية تدمج بسلاسة إمكانيات معالجة الملفات. تذكر إعطاء الأولوية للأمان، وتنفيذ معالجة الأخطاء المناسبة، والنظر في احتياجات قاعدة المستخدمين العالمية من خلال معالجة جوانب مثل ترميز اسم الملف والامتثال التنظيمي.
سواء كنت تبني خدمة مشاركة صور بسيطة أو منصة معالجة مستندات معقدة، فإن إتقان ميزات تحميل الملفات في FastAPI سيكون رصيدًا كبيرًا. استمر في استكشاف قدراته، وتطبيق أفضل الممارسات، وتقديم تجارب مستخدم استثنائية لجمهورك الدولي.