Warming up the neural circuits...
By the end of this chapter you will:
Most production bugs are not algorithm bugs. They are boundary bugs:
Pydantic is the contract layer that rejects bad data before business logic runs.
The principle is simple:
Without this layer, every function becomes defensive clutter.
from pydantic import BaseModel, EmailStr, Field
class SignupRequest(BaseModel):
name: str = Field(min_length=2, max_length=50)
email: EmailStr
age: int = Field(ge=13, le=120)Strong field types turn vague dictionaries into explicit contracts.
from pydantic import BaseModel, field_validator
class UsernamePayload(BaseModel):
username: str
@field_validator("username")
@classmethod
def validate_username(cls, value: str) -> str
from pydantic import BaseModel, model_validator
class TimeWindow(BaseModel):
start_minute: int
end_minute: int
@model_validator(mode="after")
def check_order(self):
if self.
from pydantic import BaseModel, Field
class UserOut(BaseModel):
user_id: int = Field(serialization_alias="id")
display_name: str = Field(serialization_alias="name")
email:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "python-mastery-api"
debug: bool = False
database_url: str
model_config = SettingsConfigDict(env_file=".env"
payload = {"name": "Ava", "email": "ava@example.com", "age": 21}
validated = SignupRequest.model_validate(payload)Pydantic is not tied to FastAPI. It is a standalone engine.
Accept broad external , normalize once at the boundary, and keep the of your code operating on validated domain objects.
| Need | Better pattern | Why |
|---|---|---|
| Single field cleanup | field_validator | Localized rule, easy to reason about |
| Cross-field consistency | model_validator | Enforces business invariants |
| External naming compatibility | Aliases with model_dump(by_alias=True) | Safe contract evolution |
| App configuration | BaseSettings | Typed and centralized environment parsing |
| Shared payload checks | Reusable model classes | Avoid duplicated validation logic |
| Mistake | Why it hurts | Better move |
|---|---|---|
| Doing DB/API calls inside validators | Slow and side-effectful validation | Keep validators pure and deterministic |
Overusing untyped dict | Weak contracts and hidden failures | Model payloads explicitly |
| Returning internal models directly | Accidental field leakage | Separate input/output schemas |
| Ignoring detailed 422 errors | Slower debugging for clients | Surface actionable validation details |
| Storing secrets in model defaults | Security and rotation risk | Load sensitive values from environment |
name, email, ageInvalid email or age range triggers validation errorRaw username string from userNormalized username or clear validation errorstart_minute and end_minuteError for invalid order; accepted for valid rangeInternal fields user_id/display_nameSerialized response using id/name keysAPP_DATABASE_URL and APP_DEBUGSettings object with validated fieldsBeginner:
"Why validate request payloads with models instead of plain dict access?"
Models make constraints explicit, produce consistent errors, and keep invalid input out of business logic.
"When would you use field_validator versus model_validator?"
Use field validators for per-field rules and model validators for rules involving multiple fields.
Senior:
"How do you evolve API contracts without breaking clients?"
Introduce aliases/versioned models, keep backward-compatible output during transition, and deprecate with clear timelines.
"What validation responsibilities should not live in Pydantic validators?"
Side-effectful operations such as database lookups, network calls, and authorization decisions should stay in service layers.
Validate at boundaries, trust inside
field_validator -> single-field rules
model_validator -> cross-field invariants
response_model + aliases -> stable external contracts
BaseSettings -> typed environment configWhen should model_validator generally be used instead of field_validator?
Validators should normalize and validate, not call external services.
Use model validators for relationship constraints, not per-field formatting.
Aliases help with compatibility during migrations and third-party contracts.
This keeps config typed, testable, and twelve-factor friendly.