Short story, I have an API that doesn’t run on FastAPI, but I still use FastAPI to generate documentation in OpenAPI format.
So I have models for endpoints and I want them to work with and without FastAPI, because in their pure version FastAPI is not deployed. (To make a long story short, installing FastAPI makes the package a bit heavier to deploy, but especially the cold starts of the AWS lambdas because of the Sentry initialization delay. Sentry detects that FastAPI is present in the environment and automatically adds monitoring, which takes a good 500ms longer than when it’s missing. Measured in December 2023).
Anyway, drawing heavily on a StackOverflow answer, I produced this.
# models.py
from typing import Annotated, Any, cast
from pydantic import BaseModel, Field
from pydantic.fields import FieldInfo
try:
from fastapi import Query # type:ignore[import-not-found]
def FieldQuery(*args: Any, **kwargs: Any) -> FieldInfo: # noqa:N802
return cast(FieldInfo, Field(Query(*args, **kwargs)))
except ImportError:
def FieldQuery(*args: Any, **kwargs: Any) -> FieldInfo: # noqa:N802
return cast(FieldInfo, Field(*args, **kwargs)) # type: ignore[pydantic-field]
class QueryParams(BaseModel):
limit: Annotated[
int,
FieldQuery(
-1,
description="The limit of values to fetch. -1 means no limit.",
),
]
And for the routes:
# routes.py
from fastapi import FastAPI
from models import QueryParams
app = FastAPI()
@app.get("/things")
def get_things(
params: Annotated[QueryParams, Depends()],
) -> dict:
...
Is it perfect? With the “type: ignore”, “noqa”, “import try/except”, not at all. It looks like a big hack.
Does it work? Oh yeah!
Note: The “noqa” and “type: ignore” tags are respectively linked to ruff and mypy linters.