Làm chủ Pydantic v2: Đừng để dữ liệu ‘rác’ làm hỏng ứng dụng Python của bạn

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Cài đặt và chạy thử trong 5 phút

Thay vì ngồi viết hàng chục dòng if-else chỉ để kiểm tra kiểu dữ liệu, hãy để Pydantic lo liệu việc đó. Thư viện này giúp bạn định nghĩa cấu trúc dữ liệu rõ ràng, minh bạch và cực kỳ an toàn.

Bắt đầu bằng lệnh cài đặt quen thuộc:

pip install pydantic

Hãy thử tạo một đoạn script nhỏ để validate thông tin người dùng. Bạn sẽ thấy sự khác biệt ngay lập tức:

from pydantic import BaseModel, ValidationError

class User(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool = True

try:
    # Chú ý: id truyền vào là string "123"
    user_data = {"id": "123", "username": "hoang_it", "email": "[email protected]"}
    user = User(**user_data)
    print(f"User hợp lệ: {user.username} - ID: {user.id} (Kiểu: {type(user.id)})")
except ValidationError as e:
    print(e.json())

Điều gì vừa xảy ra? Dù bạn truyền id là chuỗi "123", Pydantic đã tự động ép kiểu (coercion) sang int. Nếu dữ liệu không thể chuyển đổi, nó sẽ chặn đứng lỗi ngay tại “cửa ngõ” thay vì để nó len lỏi vào sâu trong logic nghiệp vụ.

Tại sao Pydantic v2 lại là lựa chọn số một?

Hồi mới vào nghề, mình hay dùng dict để chứa dữ liệu từ API. Hậu quả là code đầy rẫy if "key" in data:. Tệ hơn, hệ thống thường xuyên sập lúc nửa đêm với lỗi KeyError chỉ vì API đối tác thay đổi cấu trúc mà không báo trước.

Pydantic giải quyết triệt để vấn đề này bằng cách ép dữ liệu tuân thủ một “hợp đồng” (Schema) nghiêm ngặt. Đặc biệt, bản v2 được viết lại core bằng Rust. Theo các benchmark phổ biến, tốc độ parse của nó nhanh hơn từ 5 đến 50 lần so với bản v1. Khi mình xử lý file JSON 100.000 records, Pydantic v2 hoàn thành chỉ trong tích tắc, tiết kiệm đáng kể tài nguyên server.

Khai thác các tính năng cốt lõi

Ràng buộc dữ liệu thông minh với Field

Đôi khi chỉ định kiểu int hay str là chưa đủ. Bạn cần những quy tắc cụ thể như: tuổi phải trên 18, giá sản phẩm không được âm. Đây là lúc Field phát huy tác dụng.

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., min_length=3, max_length=50)
    price: float = Field(..., gt=0) # Phải lớn hơn 0
    stock: int = Field(default=0, ge=0) # Lớn hơn hoặc bằng 0

product = Product(name="Bàn phím cơ", price=150.5)
print(product)

Ký hiệu ... (Ellipsis) thông báo rằng trường này là bắt buộc. Code của bạn giờ đây vừa đóng vai trò là bộ lọc, vừa là tài liệu hướng dẫn (self-documenting) cực kỳ trực quan.

Custom Validators: Khi luật chơi là của bạn

Pydantic v2 cung cấp hai công cụ mạnh mẽ: @field_validator@model_validator. Giả sử bạn cần so khớp mật khẩu khi người dùng đăng ký:

from pydantic import BaseModel, field_validator, model_validator

class RegisterSchema(BaseModel):
    username: str
    password: str
    confirm_password: str

    @field_validator('username')
    @classmethod
    def no_spaces_in_username(cls, v: str):
        if ' ' in v:
            raise ValueError('Username không được chứa khoảng trắng')
        return v

    @model_validator(mode='after')
    def check_passwords_match(self):
        if self.password != self.confirm_password:
            raise ValueError('Mật khẩu xác nhận không khớp')
        return self

Xử lý dữ liệu lồng nhau (Nested Models)

Dữ liệu thực tế thường rất phức tạp. Một đơn hàng (Order) sẽ chứa danh sách sản phẩm (Products), và mỗi sản phẩm lại có danh mục (Category) riêng. Pydantic xử lý cấu trúc phân cấp này rất mượt mà.

from typing import List

class Tag(BaseModel):
    id: int
    label: str

class Post(BaseModel):
    title: str
    tags: List[Tag]

data = {
    "title": "Học Pydantic v2",
    "tags": [{"id": 1, "label": "Python"}, {"id": 2, "label": "Backend"}]
}

post = Post(**data)
print(post.tags[0].label) # Output: Python

Khi khởi tạo Post, Pydantic tự động đệ quy để tạo các object Tag bên trong. Nếu chỉ một tag bị thiếu trường id, toàn bộ quá trình sẽ thất bại ngay lập tức. Điều này đảm bảo dữ liệu trong app của bạn luôn “sạch” 100%.

Xuất dữ liệu (Serialization)

Sau khi xử lý xong, bạn cần gửi dữ liệu đi. Pydantic v2 thay thế phương thức dict() cũ bằng model_dump() linh hoạt hơn.

  • model_dump(): Chuyển model thành Python dictionary.
  • model_dump_json(): Chuyển thẳng sang chuỗi JSON.
# Chỉ lấy title và tags, bỏ qua các trường khác
print(post.model_dump(include={'title', 'tags'}))

# Loại bỏ các trường có giá trị None hoặc mặc định
print(post.model_dump(exclude_unset=True))

Áp dụng vào thực tế: Khi nào nên dùng?

Dựa trên kinh nghiệm của mình, có 3 thời điểm vàng để đưa Pydantic vào dự án:

  1. Làm việc với API: Dù là FastAPI, Flask hay Django, hãy dùng Pydantic để kiểm soát Request đầu vào và Response đầu ra.
  2. Quản lý cấu hình (Settings): Kết hợp với pydantic-settings để đọc biến môi trường (ENV) một cách an toàn.
  3. Migrate dữ liệu: Mình từng dùng Pydantic để chuyển 50.000 dòng CSV cũ sang PostgreSQL. Pydantic chỉ đích danh dòng nào, cột nào bị sai format, giúp mình tiết kiệm cả ngày trời debug.

Mẹo nhỏ tối ưu code

Sử dụng Alias: Nếu API bên thứ ba trả về key kiểu First-Name (vi phạm quy tắc đặt tên biến Python), hãy dùng alias để map dữ liệu một cách êm đẹp.

Chế độ Strict Mode: Nếu bạn muốn thắt chặt kỷ luật, không cho phép ép kiểu tự động (ví dụ: không cho phép "123" biến thành 123), hãy bật strict=True.

Pydantic v2 không chỉ là một thư viện validate, nó là tiêu chuẩn mới để viết code Python chuyên nghiệp. Hãy bắt đầu áp dụng ngay hôm nay để bảo vệ hệ thống của bạn khỏi những lỗi dữ liệu ngớ ngẩn.

Share: