Client gửi thêm trường is_admin: true vào body đăng ký, Pydantic mặc định lặng lẽ bỏ qua — tài khoản vẫn tạo thành công mà bạn không hề biết dữ liệu lạ đã lọt vào. Giải pháp là khai báo model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, frozen=True) trên model: field lạ bị trả 422, chuỗi tự cắt khoảng trắng thừa, và model bị khóa sau khi tạo. Dùng cách này cho mọi request model nhận dữ liệu từ client, đặc biệt là form đăng ký, tạo đơn hàng và cập nhật hồ sơ.
Vấn đề
Pydantic v2 mặc định đặt extra="ignore": mọi field không khai báo đều bị loại bỏ trong im lặng. Client gửi nhầm tên field — ví dụ usernmae thay vì username — sẽ không nhận lỗi nào, dữ liệu cứ thế trôi vào database thiếu mất một trường, và bạn chỉ phát hiện khi người dùng kêu ca. Tệ hơn, field lạ như is_admin gửi kèm body đăng ký chỉ bị bỏ qua ở tầng validate — nếu code phía sau tự đọc request.json() thì nó vẫn nằm đó.
Hai rắc rối khác cũng hay bị bỏ qua. Chuỗi nhập từ form thường dính khoảng trắng thừa ở hai đầu, kiểu " Linh ", rồi được lưu nguyên vào database — sau này tìm kiếm hoặc so sánh email sẽ lệch. Và model Pydantic mặc định cho phép gán lại thuộc tính sau khi tạo, nên một object đã validate xong vẫn có thể bị sửa đổi trước khi lưu, khiến dữ liệu cuối cùng khác với dữ liệu đã kiểm tra.
from pydantic import BaseModelclass UserCreate(BaseModel): name: str email: struser = UserCreate(name=" Linh ", email="linh@example.com", is_admin=True)user.model_dump()# {'name': ' Linh ', 'email': 'linh@example.com'} — is_admin biến mất, không một cảnh báo
Giải pháp
Khai báo model_config bằng ConfigDict ngay trong model. Ba tùy chọn dưới đây xử lý gọn cả ba vấn đề trên, và FastAPI tự chuyển ValidationError thành lỗi 422 nên bạn không cần viết thêm code xử lý lỗi:
from pydantic import BaseModel, ConfigDictclass UserCreate(BaseModel): model_config = ConfigDict( extra="forbid", str_strip_whitespace=True, frozen=True, ) name: str email: str
extra="forbid" khiến mọi field không khai báo bị từ chối ngay với lỗi extra_forbidden — client gửi nhầm tên field sẽ thấy 422 và sửa ngay. str_strip_whitespace=True tự cắt khoảng trắng ở hai đầu mọi field kiểu str, nên " Linh " thành "Linh" trước khi chạm tới database. frozen=True khóa model sau khi tạo: gán lại thuộc tính sẽ ném lỗi frozen_instance, và model còn dùng được làm key trong dict hay phần tử trong set vì đã hashable.
Ba hành vi này đã kiểm chứng trên Pydantic 2.14.3: field lạ bị chặn, chuỗi được cắt khoảng trắng, gán lại thuộc tính bị lỗi.
Áp dụng vào dự án của bạn
Đừng lặp lại ConfigDict ở từng model — tạo một base model dùng chung rồi cho mọi request model kế thừa, vì config được kế thừa xuống model con. Chỉ bật frozen=True cho model nào thật sự cần khóa, còn extra="forbid" và str_strip_whitespace=True thì bật cho toàn bộ request model. Lưu ý: model lồng nhau dùng config của chính nó, nên nó cũng cần kế thừa base model nếu bạn muốn nó được chặn field lạ và cắt khoảng trắng.
class StrictModel(BaseModel): model_config = ConfigDict( extra="forbid", str_strip_whitespace=True, )class UserCreate(StrictModel): name: str email: str
Bài viết liên quan
- FastAPI: validate dữ liệu đầu vào chuẩn chỉnh bằng Pydantic Field và validator tùy chỉnh
- FastAPI: tính trường suy ra và validate chéo bằng computed_field và model_validator trong Pydantic v2
- FastAPI: gom cả chục query param vào một Pydantic model, validate tập trung trong 5 phút
Câu hỏi thường gặp
extra=”forbid” khác gì extra=”ignore” và extra=”allow”?
extra="ignore" là mặc định: field lạ bị loại bỏ lặng lẽ. extra="allow" giữ lại field lạ trong model. extra="forbid" từ chối luôn và trả lỗi validate — nên dùng cho mọi model nhận dữ liệu từ client.
str_strip_whitespace có áp dụng cho model lồng nhau không?
Không. Mỗi model dùng config của chính nó, nên model con bên trong không tự thừa hưởng việc cắt khoảng trắng. Cho model lồng nhau kế thừa cùng base model là cách gọn nhất.
frozen=True có làm model hashable không?
Có. Model frozen có thể dùng làm key trong dict hoặc phần tử trong set, tiện khi bạn cần cache hoặc loại trùng theo object.
Có nên bật extra=”forbid” cho mọi model không?
Không. Chỉ bật cho request model nhận dữ liệu từ client. Với model đọc dữ liệu từ webhook hoặc API bên ngoài — nơi field mới có thể xuất hiện bất cứ lúc nào — extra="ignore" an toàn hơn.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Pydantic documentation — Configuration: https://docs.pydantic.dev/latest/concepts/config/
- FastAPI documentation — Request Body: https://fastapi.tiangolo.com/tutorial/body/
