How Flask-Jeroboam Works Internally¶
Flask-Jeroboam sits between your view function and Flask’s request/response cycle. When you decorate a function with @app.get(), Jeroboam wraps it with logic that inspects your function signature, extracts request data, validates it against your type hints, then validates the response before it leaves.
The Three Stages of Request Handling¶
1. Registration Time (When the decorator runs)¶
When you write:
@app.get("/wines/<int:wine_id>")
def get_wine(wine_id: int, include_notes: bool = False):
...
Jeroboam’s InboundHandler runs immediately. It inspects your function’s signature and builds a plan for how to handle requests. This is where the heavy lifting happens—it figures out that wine_id comes from the URL, that include_notes comes from query parameters, what types they should be, and what validations apply.
The key insight: we do expensive introspection once at registration, not on every request.
2. Request Time (When a request arrives)¶
A client hits your endpoint. Flask routes it, then Jeroboam’s JeroboamView intercepts before your function runs. It:
Extracts data from the request (path parameters, query string, body, headers, cookies)
Passes it through the validation plan built at registration time
Calls your function with validated arguments
Catches the return value to validate the response
Your function sees clean, validated arguments. No defensive coding needed. If the request is invalid, Jeroboam returns a 422 error before your code runs.
3. Response Time (After your function returns)¶
Your function returns data. Jeroboam validates it against your response model, serializes it to JSON, and sends it back. If your function returns a wine missing a field, Jeroboam catches that in development and fails loudly.
This is a safety net—in production, misconfigured responses get caught early.
Inside the Request Validator¶
Here’s what happens in more detail when a request arrives.
Gathering Data¶
Flask has already parsed the URL and extracted path parameters. Jeroboam adds:
Query parameters (from
request.args)Request body (from
request.get_json(), using Pydantic’s model validation)Headers (from
request.headers)Cookies (from
request.cookies)Path parameters (already parsed by Flask)
Each data source is kept separate—the plan built at registration time knows which parameter comes from where.
The Validation Step¶
Here’s where Pydantic enters. Jeroboam builds a temporary Pydantic model that has one field for each of your function parameters. For each source (query, body, headers), it creates a Pydantic model and validates the incoming data.
Why this approach? Pydantic is fast, thorough, and handles hundreds of edge cases we don’t want to reinvent. We leverage it completely.
Type Adaptation¶
Each parameter is wrapped in a TypeAdapter—Pydantic’s tool for validating arbitrary types, not just BaseModel subclasses. This means you can accept lists, unions, literal strings, anything. Pydantic handles it.
How Responses Are Validated¶
When you specify a response_model, Jeroboam doesn’t let anything slip through.
class WineOut(BaseModel):
name: str
vintage: int
region: str = "Unknown"
@app.get("/wines/<int:wine_id>", response_model=WineOut)
def get_wine(wine_id: int):
return {"name": "Château Lafite", "vintage": 2015} # Missing region!
Your function returns a dict missing region. In development mode, Jeroboam throws an error. You see it immediately. In production, the response validation is still active by default, so misconfigured endpoints fail safely.
You can also return a list, nested structures, or any type your annotation supports:
@app.get("/wines", response_model=List[WineOut])
def list_wines():
...
List validation uses Pydantic’s TypeAdapter to handle the container and each element.
The Registration Plan¶
At decoration time, Jeroboam builds a SolvedArgument for each parameter. This contains:
Where the parameter comes from (query, body, path, header, cookie)
The expected type and any validation constraints
A Pydantic
TypeAdapterready to validate dataSerialization metadata (field aliases, field exclusions, etc.)
This is precomputed. On every request, Jeroboam just runs the already-built validators. No signature introspection on each request.
Why This Matters¶
The separation of registration time from request time gives Jeroboam several properties:
Speed: Expensive introspection happens once. Requests are fast.
Clarity: Your function signature is the source of truth. No separate schema files, no decorators on function parameters (unless you want them). Jeroboam reads your types and builds everything else.
Safety: Invalid requests are rejected before your code runs. Responses are checked. Bad data doesn’t silently reach your business logic.
Debugging: Error messages are precise because Pydantic tells you exactly what failed and why. Not “bad request”, but “field ‘vintage’ should be an integer, got ‘abc’”.
Integration with Flask¶
Jeroboam subclasses Flask and Blueprint. When you use Jeroboam(__name__) instead of Flask(__name__), you get the same Flask object with request parsing and response validation layered on top.
This means:
Drop-in compatible with Flask middleware
Works with Flask extensions
Same
requestandgobjectsDecorators like
@app.before_requestwork as expected
The wrapping happens at the view level—Flask’s routing, middleware, and error handling are untouched.
OpenAPI Generation¶
Jeroboam automatically generates OpenAPI specs and serves interactive documentation at /docs. How? By reading the information it already gathered at registration time.
Your function signature + type hints + response model + docstring + any explicit parameter descriptions = everything needed for OpenAPI. No separate schema definitions, no duplication.
The /docs endpoint reflects your actual API. Rename a parameter, and the docs update. Add a constraint to a Pydantic field, and the docs show it. This happens because Jeroboam is reading live from your code.
A Note on Pydantic’s Role¶
Pydantic handles the hard parts of validation: type coercion, constraint checking, nested model validation, discriminated unions, computed fields, serialization aliases, and much more.
Jeroboam sits on top, coordinating between Flask’s request/response cycle and Pydantic’s validation. You don’t write validation code—your type hints and Pydantic models are the specification, and Pydantic enforces them automatically.