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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Centralizing validation in a dependency keeps every endpoint's handler focused on business logic.
- 2A Pydantic model_validator lets you enforce cross-field invariants that single-field checks can't express.
- 3Returning a validated object from a dependency gives handlers a clean, trustworthy input contract.
Related explainers
python
from flask import Blueprint, jsonify from sqlalchemy import text from sqlalchemy.exc import SQLAlchemyError
Building a health check endpoint in Flask
health-check
blueprint
error-handling
Intermediate
6 steps
java
@Component public class RegionCacheWarmer implements SmartInitializingSingleton { private static final Logger log = LoggerFactory.getLogger(RegionCacheWarmer.class);
Warming a Spring cache at startup
caching
startup-hook
dependency-injection
Intermediate
7 steps
python
import pandas as pd import numpy as np
Cleaning a customer DataFrame with pandas
data-cleaning
regex
normalization
Intermediate
9 steps
java
public final class EmailNormalizer { private static final Pattern EMAIL_PATTERN = Pattern.compile( "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
Normalizing email addresses in Java
validation
regex
normalization
Intermediate
8 steps
python
from collections.abc import MutableMapping class CaseInsensitiveDict(MutableMapping):
Building a case-insensitive dict in Python
data structures
abstract base classes
dunder methods
Intermediate
8 steps
php
<?php namespace App\Http\Requests\DataObjects;
Typed request DTOs in Laravel
data-transfer-object
validation
immutability
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/validating-date-ranges-with-fastapi-dependencies-explained-python-57ad/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.