Pydantic ابزاری قدرتمند برای اعتبارسنجی و سریال سازی داده ها در پایتون است که بر پایه تایپ هینت ها کار می کند. انعطاف پذیری پایتون در مدیریت انواع داده ها اگرچه سرعت توسعه را بالا می برد، اما هنگام دریافت ورودی های نامعتبر از APIها، فایل های پیکربندی یا فرم های کاربری می تواند منجر به خطاهای زمان اجرا شود. هدف Pydantic این است که با استفاده از تایپ هینت ها، ساختار دقیق داده های مورد انتظار را تعریف کند و قوانین را به صورت خودکار اعمال نماید. در این راهنما با ساخت مدل ها، اعمال محدودیت ها، نوشتن اعتبارسنج های سفارشی، مدیریت ساختارهای تو در تو و سریال سازی آشنا می شوید تا در پروژه های واقعی از آن استفاده کنید.

نصب و آغاز کار

برای شروع کار با Pydantic باید بسته آن را نصب کنید. این کار با دستور زیر انجام می شود:

pip install pydantic

پس از نصب می توانید کدهای مثال را قدم به قدم اجرا کنید و با قابلیت های این کتابخانه آشنا شوید.

مدل های پایه

برخلاف روش های سنتی که نیاز به بررسی های دستی و دستورات if متعدد دارند، Pydantic مستقیما با تایپ هینت ها کار می کند و آنها را به منطق اعتبارسنجی تبدیل می نماید. زمانی که داده با مشخصات تعریف شده همخوانی نداشته باشد، به جای خطاهای مبهم، پیام های خطای واضح و قابل فهم دریافت خواهید کرد. مدل ها از BaseModel ارث بری می کنند و ساختار داده را با تایپ هینت مشخص می سازند:

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str

user = User(name="Alice", age="25", email="alice@example.com")
print(user.age)
print(type(user.age))

خروجی:

25
<class 'int'>

در این مثال مقدار “25” به طور خودکار به عدد صحیح تبدیل می شود. اگر تبدیل ممکن نباشد، خطای اعتبارسنجی شفاف دریافت خواهید کرد. این ویژگی هنگام کار با JSON یا فرم ها بسیار مفید است.

فیلدهای اختیاری و مقادیر پیش فرض

در پروژه های واقعی برخی فیلدها ممکن است اجباری نباشند. با استفاده از Optional و تعریف مقدار پیش فرض می توانید این نیاز را برطرف کنید. همچنین Field امکان اعمال محدودیت هایی مانند حداقل طول را فراهم می کند:

from pydantic import BaseModel, Field
from typing import Optional

class Product(BaseModel):
    name: str
    price: float
    description: Optional[str] = None
    in_stock: bool = True
    category: str = Field(default="general", min_length=1)

product1 = Product(name="Widget", price=9.99)
product2 = Product(name="Gadget", price=15.50, description="Useful tool")

در این کد description می تواند رشته یا None باشد. فیلدهایی که مقدار پیش فرض دارند در ورودی الزامی نیستند. با Field شرط حداقل یک کاراکتر برای category اعمال شده تا داده های ناقص به صورت کنترل شده مدیریت شوند.

اعتبارسنج های سفارشی

گاهی اوقات نیاز به قواعدی فراتر از بررسی نوع داده دارید. با اعتبارسنج های سفارشی می توانید منطق دلخواه خود را پیاده سازی کنید و حتی داده را نرمال سازی نمایید:

from pydantic import BaseModel, field_validator
import re

class Account(BaseModel):
    username: str
    email: str
    password: str

    @field_validator('username')
    def validate_username(cls, v):
        if len(v) < 3:
            raise ValueError('Username must be at least 3 characters')
        if not v.isalnum():
            raise ValueError('Username must be alphanumeric')
        return v.lower()

    @field_validator('email')
    def validate_email(cls, v):
        pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
        if not re.match(pattern, v):
            raise ValueError('Invalid email format')
        return v

    @field_validator('password')
    def validate_password(cls, v):
        if len(v) < 8:
            raise ValueError('Password must be at least 8 characters')
        return v

account = Account(
    username="JohnDoe123",
    email="john@example.com",
    password="secretpass123"
)

اعتبارسنج ها در هنگام ساخت شیء اجرا می شوند. آنها می توانند مقدار را تغییر دهند یا با پیام های واضح آن را رد کنند. ترتیب تعریف همان ترتیب اجراست و در صورت نیاز می توان به مقادیر اعتبارسنجی شده قبلی دسترسی داشت.

مدل های تو در تو و ساختارهای پیچیده

در کاربردهای واقعی داده ها معمولا ساختار سلسله مراتبی دارند. Pydantic اعتبارسنجی داده های تو در تو را ساده می کند:

from pydantic import BaseModel, field_validator
from typing import List, Optional
from datetime import datetime

class Address(BaseModel):
    street: str
    city: str
    state: str
    zip_code: str

    @field_validator('zip_code')
    def validate_zip(cls, v):
        if not v.isdigit() or len(v) != 5:
            raise ValueError('ZIP code must be 5 digits')
        return v

class Contact(BaseModel):
    name: str
    phone: str
    email: Optional[str] = None

class Company(BaseModel):
    name: str
    founded: datetime
    address: Address
    contacts: List[Contact]
    employee_count: int
    is_public: bool = False

company_data = {
    "name": "Tech Corp",
    "founded": "2020-01-15T10:00:00",
    "address": {
        "street": "123 Main St",
        "city": "San Francisco",
        "state": "CA",
        "zip_code": "94105"
    },
    "contacts": [
        {"name": "John Smith", "phone": "555-0123"},
        {"name": "Jane Doe", "phone": "555-0456", "email": "jane@techcorp.com"}
    ],
    "employee_count": 150
}

company = Company(**company_data)

در این مثال تمام ساختار به صورت بازگشتی اعتبارسنجی می شود. address بر اساس قواعد Address بررسی می شود، هر عضو contacts باید با مدل Contact سازگار باشد و رشته تاریخ به datetime تبدیل می گردد. اگر بخشی نامعتبر باشد، خطا با اشاره دقیق به محل مشکل نمایش داده می شود.

کار با API و JSON

داده های API معمولا با انواع مختلط، فرمت های زمانی متفاوت و فیلدهای اختیاری دریافت می شوند. Pydantic این چالش ها را به خوبی مدیریت می کند:

from pydantic import BaseModel, Field, field_validator
from typing import Union, Optional
from datetime import datetime
import json

class APIResponse(BaseModel):
    status: str
    message: Optional[str] = None
    data: Optional[dict] = None
    timestamp: datetime = Field(default_factory=datetime.now)

class UserProfile(BaseModel):
    id: int
    username: str
    full_name: Optional[str] = None
    age: Optional[int] = Field(None, ge=0, le=150)
    created_at: Union[datetime, str]
    is_verified: bool = False

    @field_validator('created_at', mode='before')
    def parse_created_at(cls, v):
        if isinstance(v, str):
            try:
                return datetime.fromisoformat(v.replace('Z', '+00:00'))
            except ValueError:
                raise ValueError('Invalid datetime format')
        return v

api_json = '''
{
    "status": "success",
    "data": {
        "id": 123,
        "username": "alice_dev",
        "full_name": "Alice Johnson",
        "age": "28",
        "created_at": "2023-01-15T10:30:00Z",
        "is_verified": true
    }
}
'''

response_data = json.loads(api_json)
api_response = APIResponse(**response_data)

if api_response.data:
    user = UserProfile(**api_response.data)
    print(f"User {user.username} created at {user.created_at}")

خروجی:

User alice_dev created at 2023-01-15 10:30:00+00:00

در این کد از mode=’before’ استفاده شده تا تبدیل قبل از تایپ کست انجام شود. همچنین با Field محدودیت سن تعریف شده تا ورودی های غیرمعقول پذیرفته نشوند.

مدیریت خطا و ValidationError

زمانی که ورودی نامعتبر است، Pydantic جزئیات خطا را به صورت ساختاریافته ارائه می دهد:

from pydantic import BaseModel, ValidationError, field_validator
from typing import List

class Order(BaseModel):
    order_id: int
    customer_email: str
    items: List[str]
    total: float

    @field_validator('total')
    def positive_total(cls, v):
        if v <= 0:
            raise ValueError('Total must be positive')
        return v

bad_data = {
    "order_id": "not_a_number",
    "customer_email": "invalid_email",
    "items": "should_be_list",
    "total": -10.50
}

try:
    order = Order(**bad_data)
except ValidationError as e:
    print("Validation errors:")
    for error in e.errors():
        field = error['loc'][0]
        message = error['msg']
        print(f"  {field}: {message}")
    print("\nJSON errors:")
    print(e.json(indent=2))

این ساختار خطا شامل نام فیلد، نوع خطا و پیام قابل فهم است. بنابراین چه برای نمایش به کاربر و چه برای ثبت در لاگ، اطلاعات کافی در دسترس خواهید داشت.

سریال سازی و خروجی گرفتن

تبدیل مدل ها به دیکشنری یا JSON بسیار ساده است و می توانید فیلدها را شامل یا حذف کنید:

from pydantic import BaseModel
from datetime import datetime

class Event(BaseModel):
    name: str
    date: datetime
    attendees: int
    is_public: bool = True

event = Event(
    name="Python Meetup",
    date=datetime(2024, 3, 15, 18, 30),
    attendees=45
)

event_dict = event.model_dump()
print(event_dict)

event_json = event.model_dump_json()
print(event_json)

public_data = event.model_dump(exclude={'attendees'})
print(public_data)

formatted_json = event.model_dump_json(indent=2)
print(formatted_json)

با model_dump و model_dump_json می توانید نمایش های متفاوتی از یک داده بسازید. برای مثال مخفی کردن فیلدهای حساس یا تولید JSON قالب بندی شده برای پاسخ API. این انعطاف باعث می شود یک مدل واحد نیازهای متنوع لایه های مختلف برنامه را پوشش دهد.

جمع بندی

Pydantic فرآیند اعتبارسنجی داده را از یک کار پرخطا و زمان بر به رویکردی اعلامی، تمیز و خودکار تبدیل می کند. با تکیه بر تایپ هینت ها همزمان خوانایی کد حفظ می شود و از طرفی تضمین زمان اجرا برای ساختار داده فراهم می آید. در این راهنما دیدید چگونه مدل های پایه بسازید، فیلدهای اختیاری و پیش فرض تعریف کنید، اعتبارسنج های سفارشی بنویسید، داده های تو در تو را مدیریت کنید و در نهایت خروجی های متناسب با نیاز هر سناریو تولید نمایید. اگر در پروژه های واقعی کار با ورودی های غیرقابل اعتماد، JSON خام یا پاسخ های ناهمگون API چالش شما بوده است، با Pydantic می توانید خطاها را زودتر شناسایی کنید، پیام های روشن تری به کاربر ارائه دهید و از تکرار کار جلوگیری نمایید. تمرین با همین الگوها بهترین راه برای تسلط است و هرجا که نیاز به قواعد دقیق تری داشتید کافی است یک اعتبارسنج جدید اضافه کنید یا از پیکربندی های پیشرفته استفاده نمایید.