All Articles
Deep Diveยท5 min read

Why Your Pydantic Validation Silently Passes Bad Data

You added Pydantic models expecting airtight validation, but malformed data keeps slipping through. The cause is usually type coercion and loose defaults.

Part of: Python Programming & Development

You reached for Pydantic to stop bad data at the door. Yet somehow strings become numbers, empty values sail through, and downstream code still explodes. Pydantic did exactly what you told it to โ€” the problem is what you told it.

The Symptom

A model that looks strict:

from pydantic import BaseModel class Order(BaseModel): quantity: int coupon: str = "" Order(quantity="5") # passes โ€” quantity is now 5 Order(quantity=True) # passes โ€” quantity is now 1

You expected "5" and True to be rejected. Instead Pydantic coerced them.

Why It Happens

By default Pydantic v2 runs in lax mode: it will convert compatible types rather than reject them. A string that looks like a number becomes a number; a bool becomes an int. This is convenient for parsing JSON from HTTP, but it means "typed" is not the same as "validated."

The empty-string default is a second trap. coupon: str = "" makes the field optional and silently accepts blanks as valid.

The Fix

Turn on strict mode where coercion is unwanted:

from pydantic import BaseModel, StrictInt, Field class Order(BaseModel): quantity: StrictInt = Field(gt=0) coupon: str | None = Field(default=None, min_length=3) Order(quantity="5") # now raises ValidationError Order(quantity=0) # raises โ€” gt=0 constraint

StrictInt refuses non-int inputs, and Field(gt=0) enforces a real business rule instead of just a type.

Validate the values, not just the shapes. Use field constraints (min_length, max_length, ge, le, pattern) and custom validators for anything with meaning:

from pydantic import field_validator class Order(BaseModel): email: str @field_validator("email") @classmethod def must_have_at(cls, v: str) -> str: if "@" not in v: raise ValueError("invalid email") return v

Prevention

  • โ–ธDecide per field whether coercion is acceptable; use Strict* types where it is not.
  • โ–ธReplace bare = "" and = None defaults with explicit constraints.
  • โ–ธSet model_config = ConfigDict(extra="forbid") so unexpected keys are rejected instead of ignored.

The takeaway: Pydantic validates against the rules you declare, and a plain type annotation is a weak rule. Strict types plus real constraints turn a model from a shape hint into an actual gate.

#Python#Pydantic#Validation#FastAPI

Want to go deeper?

Python Programming & Development

View Course