Skip to content

Pydantic in Sillo

Pydantic is the validation engine underneath every Sillo route. What it does, where it appears, and a map of this section.

Pydantic is not an optional add-on in Sillo. It is the engine underneath every request that gets validated, every response model, every query parameter with a type, and the OpenAPI document generated from all of them.

from pydantic import BaseModel, Field
from sillo import HttpContext, json
class PostCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
body: str
tags: list[str] = []
@app.post("/posts", request_model=PostCreate)
async def create_post(ctx: HttpContext, payload):
post = await Post.create(**payload.model_dump())
return json(post.to_dict(), status_code=201)

That declaration does four things at once: it parses the body, coerces the types, rejects bad input with a 422 naming each failure, and puts the shape in your OpenAPI schema. One statement, and the published contract cannot drift from the runtime behaviour because they are generated from the same object.

Most Sillo applications spend more time in Pydantic than they expect. The validation layer is where a surprising amount of correctness lives: a min_length on a slug, a Decimal instead of a float on money, a validator that normalises an email before it reaches the database.

This section covers Pydantic itself in enough depth that you should not need to leave for its own documentation, and covers it as Sillo uses it, so the examples are handlers and models rather than standalone scripts.

Sillo uses Pydantic v2. The v1 API (@validator, .dict(), class Config) is deprecated and behaves differently; if you find a snippet online using it, the migration notes say what changed.

In SilloPage
request_model= on a routeRequest models
response_model= on a routeResponse models
Query, Path, Header, Cookie, Form, FileParameters
The generated OpenAPI documentOpenAPI
Schemas generated from ORM modelsThe ORM bridge
The 422 body a client receivesValidation errors

Sillo has three layers that can reject bad data, and they are not interchangeable:

LayerCatchesProduces
Pydantic, before the handlerWrong shape, wrong type, out of range422 with field-level detail
Model validationInvariants, however the row was writtenAn exception
Database constraintsWhat must be true for every writerAn IntegrityError

Pydantic is the outermost and the only one that can produce a decent error message for a client: it knows which field, in which location, and why. Use it for everything about the request.

Use the layers underneath for what must hold regardless of who wrote the row: including a console command, a migration, or another service.