هيكل الطلب والتحقق من البيانات — دليل عمقي متعمق لـ Pydantic V2
التحقق من البيانات يشبه أمن المطارات — كل راكب (طلب) يجب أن يمر عبر التحقق من الهوية (فحص الأنواع)، وفحص الأمتعة (التحقق من القيود)، ومراجعة إقراره (المتحققون المخصصون) قبل صعود الطائرة (دخول منطق الأعمال).
1. ما ستتعلمه
- Pydantic V2
BaseModel: يحلfield_validator/model_validatorمحل@validatorفي V1 Field()القيود المتقدمة:gt/lt/pattern/examplesوتوليد JSON Schema- النماذج المتداخلة وتوليفات النماذج: تطبيقات عملية لـ
OptionalوUnionوLiteral model_config: يحلConfigDictمحلclass Configوfrom_attributes=Trueفي وضع ORM لـ V1- سيناريو Alice: تصميم نموذج Pydantic كامل لعمليات إرسال أسعار منتجات PriceTracker
2. القصة الحقيقية لـ Alice
(1) نقطة الألم: إرسال بيانات الأسعار والتحقق منها مليئة بالأخطاء
يتلقى PriceTracker الخاص بـ Alice بيانات أسعار مقدمة من الموردين، مما يتطلب أن تكون الأسعار أكبر من 0، وأن تحدد العملات برموز ISO 4217 المكونة من ثلاثة أحرف، وألا تكون أسماء المنتجات فارغة. ومع ذلك، فإن واجهة Bob الأمامية ترسل أحيانًا أسعارًا سالبة أو عملات غير صالحة. كود التحقق المخصص المكتوب في Flask منتشر عبر 10 مواقع مختلفة، ويتجاهل الحالة الحدية حيث "السعر يساوي 0"، مما يؤدي إلى بيانات متسخة بسعر 0 في قاعدة البيانات.
(2) حل للتحقق التعريفي في Pydantic V2
يستبدل Pydantic V2 التحقق الإلزامي بنموذج تعريفي؛ يتم تعريف جميع القيود داخل النموذج، وبمجرد تعريفها، يتم تطبيقها عالميًا.
from pydantic import BaseModel, Field, field_validator
class PriceCreate(BaseModel):
product_id: int = Field(gt=0)
price: float = Field(gt=0, description="Price in USD")
currency: str = Field(pattern=r"^[A-Z]{3}$", examples=["USD", "EUR"])
@field_validator("currency")
@classmethod
def validate_currency(cls, v: str) -> str:
if v not in {"USD", "EUR", "GBP", "JPY", "CNY"}:
raise ValueError(f"Unsupported currency: {v}")
return v
(3) العائد
تم دمج كود التحقق من الأسعار من 10 كتل منطقية منفصلة في تعريف نموذج واحد؛ يتم تصفية القيم 0 والأسعار السالبة تلقائيًا، ويضمن التحقق من العملات نهجًا مزدوج الطبقة يجمع بين التعبيرات النمطية والمتحققين المخصصين، مما يقلل البيانات المتسخة من قاعدة البيانات.
3. تدفق بيانات Pydantic V2
(1) دورة الحياة الكاملة
flowchart TD
A[JSON Request Body] --> B[model_validate]
B --> C[Type Coercion]
C --> D[field_validator]
D --> E[model_validator]
E --> F[Valid Model Instance]
F --> G[model_dump]
G --> H[JSON Response]
F --> I[model_dump_json]
I --> J[JSON String]
| المرحلة | الطريقة | الوصف |
|---|---|---|
| تحليل الإدخال | model_validate(data) |
تحليل والتحقق من dict/JSON |
| تحويل النوع | تلقائي | "42" → 42, "9.99" → 9.99 |
| التحقق من الحقل | @field_validator |
تحقق مخصص لحقل واحد |
| التحقق من النموذج | @model_validator |
تحقق مشترك بين الحقول |
| تسلسل المخرجات | model_dump() / model_dump_json() |
تحويل النموذج إلى dict/JSON |
(1) ▶ مثال: النموذج الأساسي لـ Pydantic V2
from pydantic import BaseModel, Field
class ProductCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
category: str = Field(max_length=100)
base_price: float = Field(gt=0, description="Base price in USD")
# تحليل والتحقق
data = {"name": "Widget", "category": "electronics", "base_price": 29.99}
product = ProductCreate.model_validate(data)
print(product.model_dump())
الناتج:
{'name': 'Widget', 'category': 'electronics', 'base_price': 29.99}
4. field_validator و model_validator
(1) مقارنة الانتقال من V1 إلى V2
flowchart LR
V1[Pydantic V1] --> Migrate[V1 → V2 Migration]
Migrate --> V2[Pydantic V2]
V1 --- A["@validator('field')"]
V2 --- B["@field_validator('field')"]
V1 --- C["@root_validator"]
V2 --- D["@model_validator(mode='after')"]
V1 --- E["class Config:"]
V2 --- F["model_config = ConfigDict(...)"]
V1 --- G[".dict()"]
V2 --- H[".model_dump()"]
| بنية V1 | بنية V2 | الوصف |
|---|---|---|
@validator("field") |
@field_validator("field") |
محقق الحقل |
@root_validator |
@model_validator(mode="after") |
التحقق على مستوى النموذج |
class Config: orm_mode = True |
model_config = ConfigDict(from_attributes=True) |
نموذج ORM |
.dict() |
.model_dump() |
تسلسل إلى dict |
.json() |
.model_dump_json() |
تسلسل إلى JSON |
(1) ▶ مثال: field_validator — التحقق من حقل واحد
from pydantic import BaseModel, Field, field_validator
class PriceCreate(BaseModel):
product_id: int = Field(gt=0)
price: float = Field(gt=0)
currency: str = Field(default="USD", max_length=3)
@field_validator("price")
@classmethod
def price_precision(cls, v: float) -> float:
# تقريب إلى منزلتين عشريتين
return round(v, 2)
@field_validator("currency")
@classmethod
def valid_currency(cls, v: str) -> str:
allowed = {"USD", "EUR", "GBP", "JPY", "CNY"}
if v not in allowed:
raise ValueError(f"Currency must be one of {allowed}")
return v.upper()
# اختبار التحقق
p = PriceCreate(product_id=1, price=9.999, currency="usd")
print(p.model_dump())
الناتج:
{'product_id': 1, 'price': 10.0, 'currency': 'USD'}
(2) ▶ مثال: model_validator للتحقق المشترك بين الحقول
from pydantic import BaseModel, Field, model_validator
class PriceRangeQuery(BaseModel):
min_price: float = Field(ge=0, description="Min price in USD")
max_price: float = Field(ge=0, description="Max price in USD")
@model_validator(mode="after")
def check_range(self):
if self.min_price > self.max_price:
raise ValueError("min_price must be <= max_price")
return self
# صالح
valid = PriceRangeQuery(min_price=10, max_price=100)
print(valid.model_dump())
# غير صالح - يثير خطأ التحقق
# PriceRangeQuery(min_price=100, max_price=10)
الناتج:
{'min_price': 10.0, 'max_price': 100.0}
5. القيود المتقدمة لـ Field() و JSON Schema
(1) مرجع سريع لمعاملات Field()
| المعامل | النوع | الوصف | تعيين JSON Schema |
|---|---|---|---|
gt |
قيمة | أكبر من | exclusiveMinimum |
ge |
قيمة | أكبر من أو يساوي | minimum |
lt |
قيمة | أصغر من | exclusiveMaximum |
le |
قيمة | أصغر من أو يساوي | maximum |
min_length |
نص | الطول الأدنى | minLength |
max_length |
نص | الطول الأقصى | maxLength |
pattern |
نص | تعبير نمطي | pattern |
default |
أي | القيمة الافتراضية | default |
examples |
قائمة | قيم أمثلة | examples |
description |
نص | الوصف | description |
alias |
نص | اسم بديل للحقل | تعيين الاسم البديل |
(1) ▶ مثال: قيود Field و JSON Schema
from pydantic import BaseModel, Field
class ProductCreate(BaseModel):
name: str = Field(
min_length=1,
max_length=200,
description="Product display name",
examples=["Wireless Mouse", "USB Cable"],
)
sku: str = Field(
pattern=r"^[A-Z]{2}-\d{4,6}$",
description="SKU code: 2 letters + 4-6 digits",
examples=["EL-1234", "CB-567890"],
)
base_price: float = Field(
gt=0,
le=999999.99,
description="Base price in USD",
examples=[9.99, 49.99, 199.99],
)
# عرض JSON Schema المُولّد
print(ProductCreate.model_json_schema())
الناتج:
# التنفيذ ناجح
6. النماذج المتداخلة وتوليفات النماذج
(1) بنية النموذج المتداخل
(1) ▶ مثال: النموذج المتداخل لـ PriceTracker
from pydantic import BaseModel, Field
from typing import Optional
class PriceInfo(BaseModel):
amount: float = Field(gt=0, description="Price amount in USD")
currency: str = Field(default="USD", pattern=r"^[A-Z]{3}$")
source: str = Field(max_length=100, description="Price source")
class ProductCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
category: str = Field(max_length=100)
current_price: PriceInfo # نموذج متداخل
original_price: Optional[PriceInfo] = None # متداخل اختياري
# تحقق متداخل
data = {
"name": "Wireless Mouse",
"category": "electronics",
"current_price": {"amount": 29.99, "currency": "USD", "source": "Amazon"},
"original_price": {"amount": 49.99, "currency": "USD", "source": "Amazon"},
}
product = ProductCreate.model_validate(data)
print(product.model_dump())
الناتج:
# التنفيذ ناجح
(2) ▶ مثال: Union و Literal
from pydantic import BaseModel, Field
from typing import Union, Literal
class SinglePrice(BaseModel):
type: Literal["single"] = "single"
amount: float = Field(gt=0)
class RangePrice(BaseModel):
type: Literal["range"] = "range"
min_amount: float = Field(gt=0)
max_amount: float = Field(gt=0)
class ProductPrice(BaseModel):
product_id: int = Field(gt=0)
pricing: Union[SinglePrice, RangePrice] # اتحاد مميّز
# يستخدم FastAPI حقل "type" لتحديد النموذج المراد التحقق منه
data = {"product_id": 1, "pricing": {"type": "range", "min_amount": 10, "max_amount": 50}}
pp = ProductPrice.model_validate(data)
print(pp.model_dump())
الناتج:
# التنفيذ ناجح
(2) إعدادات ConfigDict
(3) ▶ مثال: model_config ونمط ORM
from pydantic import BaseModel, ConfigDict
class ProductResponse(BaseModel):
model_config = ConfigDict(
from_attributes=True, # تفعيل وضع ORM (القراءة من كائنات SQLAlchemy)
populate_by_name=True, # السماح باسم الحقل والاسم البديل معًا
json_schema_extra={
"examples": [{"id": 1, "name": "Widget", "price": 9.99}]
},
)
id: int
name: str
price: float
# مع from_attributes=True، يمكن الإنشاء من سمات الكائن
class FakeORMObject:
def __init__(self):
self.id = 1
self.name = "Widget"
self.price = 9.99
orm_obj = FakeORMObject()
response = ProductResponse.model_validate(orm_obj)
print(response.model_dump())
الناتج:
{'id': 1, 'name': 'Widget', 'price': 9.99}
7. مثال شامل
يتيح التحقق من الحقول والتحقق المشترك بين الحقول في Pydantic V2، مقترنًا بنمط ORM وهيكل طلب FastAPI، سير عمل كامل للتحقق من إدخال البيانات.
from fastapi import FastAPI
from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict
class PriceCreate(BaseModel):
product_name: str = Field(min_length=1, max_length=100)
price: float = Field(gt=0, description="The price must be greater than 0")
currency: str = "USD"
@field_validator("currency")
@classmethod
def validate_currency(cls, v: str) -> str:
if v not in ("USD", "EUR", "GBP"):
raise ValueError("currency must be USD/EUR/GBP")
return v
class PriceRangeQuery(BaseModel):
min_price: float = Field(ge=0)
max_price: float = Field(ge=0)
@model_validator(mode="after")
def validate_range(self):
if self.min_price > self.max_price:
raise ValueError("min_price must <= max_price")
return self
app = FastAPI()
@app.post("/prices")
async def create_price(data: PriceCreate):
return data.model_dump()
الناتج:
POST /prices {"product_name":"Widget","price":9.99} → {"product_name":"Widget","price":9.99,"currency":"USD"}
POST /prices {"product_name":"","price":-1} → 422 Validation Error
❓ أسئلة شائعة
field_validator و model_validator؟field_validator للتحقق من حقل واحد (مثل فحص التنسيق)، واستخدم model_validator للتحقق المشترك بين الحقول (مثل min_price <= max_price).from_attributes=True؟examples و json_schema_extra في Field()؟examples هو قائمة من قيم الأمثلة لحقل واحد، يتم تعيينها إلى أمثلة OpenAPI؛ json_schema_extra هو خاصية مخطط إضافية على مستوى النموذج.📖ملخص
- يستبدل Pydantic V2 كل من
@validatorو@root_validatorفي V1 بـ@field_validatorو@model_validator - يربط
Field()القيود تلقائيًا بـ JSON Schema، بينما يدعم كل من توثيق OpenAPI والتحقق من بيانات الطلب - النموذج المتداخل يدعم توليفات مرنة من
OptionalوUnionوLiteral، بينما ينفذLiteralنوع اتحاد تمييزي ConfigDict(from_attributes=True)يفعل وضع ORM لإنشاء نماذج Pydantic مباشرة من كائنات SQLAlchemy- جوهر الانتقال من V1 إلى V2:
.dict()→.model_dump()،class Config→model_config = ConfigDict(...)
📝تمارين
- مسألة أساسية (صعوبة ⭐): أنشئ نموذج
PriceCreate، الذي يتضمنproduct_id: int(> 0) وprice: float(> 0)، وتحقق أن الإدخال غير الصالح يثير خطأ تحقق. تلميح:BaseModel+Field(gt=0) - تمرين متقدم (صعوبة ⭐⭐): أضف إلى
PriceCreateمحقق@field_validatorللتحقق من أن حقلcurrencyيمكن أن يحتوي فقط على USD/EUR/GBP، وأضف@model_validatorلضمانmin_price <= max_price. تلميح:field_validator+model_validator(mode="after") - مسألة تحدي (صعوبة ⭐⭐⭐): صمم نظام نماذج متداخلة كامل لـ PriceTracker:
ProductCreateيحتوي علىPriceInfo(amount + currency)؛ العملة فيPriceInfoمقيدة بتعبير نمطي؛ و SKU فيProductCreateمقيد بالتنسيق فيpattern. تلميح:Field(pattern=...)+BaseModelمتداخل
---|



