Chapter 7: strict mode. The single biggest semantic choice in Pydantic.

Default: lax

class M(BaseModel):
    age: int

M.model_validate({"age": "25"})       # → age=25 (coerced)
M.model_validate({"age": 25.0})       # → age=25 (coerced)
M.model_validate({"age": True})       # → age=1 (coerced; bool is subclass of int!)

Lax mode coerces compatible types. Useful when input is JSON / query params (strings) but you want native types.

Strict per model

class M(BaseModel):
    model_config = {"strict": True}
    age: int

M.model_validate({"age": "25"})  # ValidationError

No coercion. Type must match exactly.

Strict per field

from pydantic import StrictInt, StrictStr, StrictFloat, StrictBool

class M(BaseModel):
    age: StrictInt
    name: StrictStr

Or via Annotated:

from typing import Annotated
from pydantic import Strict

class M(BaseModel):
    age: Annotated[int, Strict()]

Per-call strict

M.model_validate({"age": "25"}, strict=True)

Override at call time without changing the model.

Strict in JSON

For model_validate_json:

M.model_validate_json('{"age": "25"}', strict=True)

Note: JSON itself doesn’t coerce; “25” is a string in JSON. Strict mode here checks “string in JSON → int in model” doesn’t auto-coerce.

When to use strict

  • Internal RPC where types are guaranteed.
  • Strict API contracts where type-mismatch should fail loudly.
  • Tests to catch unintended coercion.

When NOT:

  • Query parameters (always strings).
  • Form data (always strings).
  • CSV parsing.
  • Loose external APIs.

For FastAPI: lax for request bodies (good UX); strict for internal services if you control both ends.

Coercion rules in lax mode

For int:

  • str → if numeric, parse.
  • bool → 0 or 1 (gotcha: True becomes 1).
  • float → if integer-valued, accept; else error.

For float:

  • str → if numeric, parse.
  • int → accept.
  • bool → 0.0 or 1.0.

For str:

  • Most types → str(…). Note: True → “True”, not “true”.

For bool:

  • 0, 1 → False, True.
  • "true", "false", "yes", "no", "on", "off" → bool.
  • Lots of variants. See pydantic docs.

For datetime:

  • ISO 8601 string → datetime.
  • Unix timestamp number → datetime (PG, naive).
  • Strict: only datetime.

Bool is a subclass of int

class M(BaseModel):
    age: int

M.model_validate({"age": True})  # age=1 (yes!)

Python True == 1. Pydantic accepts. To reject: use Strict or:

class M(BaseModel):
    age: int
    
    @field_validator("age", mode="before")
    @classmethod
    def reject_bool(cls, v):
        if isinstance(v, bool):
            raise ValueError("must be int, not bool")
        return v

JSON-specific behavior

model_validate_json parses JSON with stricter rules than model_validate(dict):

class M(BaseModel):
    age: int

M.model_validate({"age": "25"})       # → 25 (lax dict)
M.model_validate_json('{"age": "25"}') # → ValidationError (JSON doesn't coerce strings)

JSON has clear types; Pydantic respects them.

Constrained types and coercion

class M(BaseModel):
    age: int = Field(ge=0)

M.model_validate({"age": "25"})  # → age=25 (after coercion); passes ge=0
M.model_validate({"age": "-5"})  # → -5 (after coercion); fails ge=0

Coerce first; then constraints.

Strict on root

from pydantic import RootModel

class Names(RootModel[list[str]]):
    model_config = {"strict": True}

Common mistakes

1. Strict for query params

Query params always come as strings. Strict rejects them. Use lax for FastAPI request validation.

2. Forgetting bool is int

age: int accepts True as 1. If unwanted, strict or pre-validator.

3. Lax in internal code

Service A sends int; Service B accepts int as str by accident. Strict between services helps catch protocol mismatch.

4. Mixing modes inconsistently

Some models strict; others lax; same data flows through. Pick one mode per layer.

5. Not testing edge cases

"True", "FALSE", 0, "0" — all coerce differently. Tests for the actual inputs you’ll see.

What’s next

Chapter 8: JSON Schema generation.

Read this next


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .