How to combine boundary validation, precise Python types and static analysis without confusing runtime parsing with domain correctness.
Validate untrusted data once, convert it into precise application types and run strict static analysis over the code that makes decisions.
def create_order(data: dict) -> dict:
customer_id = data.get("customer_id")
total = data.get("total", 0)
return {"id": save(customer_id, total)}class CreateOrderRequest(BaseModel):
customer_id: UUID
lines: list[OrderLineRequest] = Field(min_length=1)
@dataclass(frozen=True)
class CreateOrder:
customer_id: CustomerId
lines: tuple[OrderLine, ...]
def create_order(command: CreateOrder) -> OrderResult: ...Validate at boundaries
Pydantic is strongest where untrusted JSON becomes an accepted request or where internal results become a documented response. Put size limits, formats and cross-field validation close to that boundary.
Do not pass transport models through every layer. Convert accepted input into application commands and domain value objects so framework configuration, optional request fields and serialization aliases do not spread into business logic.
Prefer precise types over annotations everywhere
Annotating a value as str does not distinguish an email address, order ID and currency code. Small value types and enums can encode meaning, while dataclasses make immutable commands explicit.
Avoid Any at integration boundaries. If a third-party library is untyped, isolate it behind a typed adapter and validate its result. A cast should record verified knowledge, not silence a warning the program cannot justify.
Turn static analysis into a gate
Run mypy or Pyright in CI with a documented strictness baseline. Ratchet weak modules toward stricter checking rather than introducing thousands of ignored errors at once.
Static analysis cannot prove transaction safety or business correctness. Its evidence is narrower but valuable: consistent call contracts, exhaustiveness, null handling and fewer refactoring surprises. Pair it with runtime validation and behavioural tests.
Evidence to take into review
- External input is validated before application logic.
- Transport models do not become domain models by default.
- Important identifiers and states use precise types.
- Any and type ignores are isolated and justified.
- Static analysis runs in CI with a maintained baseline.
Explore all engineering notes.
Use PRODUCTION-7 to connect this concern with the other dimensions of a trustworthy backend.
View all articles Get the checklist