python 42 lines · 6 steps

Validating date ranges with FastAPI dependencies

A reusable dependency parses and validates a query date range before any endpoint sees it.

Explained by highlit
1from datetime import date, timedelta
2from typing import Annotated
3 
4from fastapi import APIRouter, Depends, Query
5from pydantic import BaseModel, model_validator
6 
7router = APIRouter(prefix="/reports", tags=["reports"])
8 
9MAX_RANGE = timedelta(days=366)
10 
11 
12class DateRange(BaseModel):
13 start: date
14 end: date
15 
16 @model_validator(mode="after")
17 def check_range(self) -> "DateRange":
18 if self.start > self.end:
19 raise ValueError("start must not be after end")
20 if self.end - self.start > MAX_RANGE:
21 raise ValueError("range must not exceed 366 days")
22 if self.end > date.today():
23 raise ValueError("end must not be in the future")
24 return self
25 
26 
27def date_range(
28 start: Annotated[date, Query(description="Inclusive start date (YYYY-MM-DD)")],
29 end: Annotated[date | None, Query(description="Inclusive end date; defaults to today")] = None,
30) -> DateRange:
31 return DateRange(start=start, end=end or date.today())
32 
33 
34@router.get("/revenue")
35async def revenue_report(rng: Annotated[DateRange, Depends(date_range)]):
36 rows = await revenue_service.aggregate(rng.start, rng.end)
37 return {
38 "start": rng.start,
39 "end": rng.end,
40 "days": (rng.end - rng.start).days + 1,
41 "rows": rows,
42 }
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Centralizing validation in a dependency keeps every endpoint's handler focused on business logic.
  2. 2A Pydantic model_validator lets you enforce cross-field invariants that single-field checks can't express.
  3. 3Returning a validated object from a dependency gives handlers a clean, trustworthy input contract.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Validating date ranges with FastAPI dependencies — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code