Routing specs¶
Routing specs are small, composable predicates over a route. They are used to classify routes — for example to decide which routes are public — and to build the per-category OpenAPI documents.
A RoutingSpec is a callable evaluated against a route's metadata: its path,
methods, deprecated flag, response_model, response_class and
openapi_extra. It returns True, False, or None (unset / no opinion).
Building blocks¶
from fastapi_router_variants import (
And,
ApiVersion,
Methods,
Not,
Or,
Paths,
Public,
RoutingSpec,
)
public: RoutingSpec = Not(Paths("/internal", startswith=True))
v2_public: RoutingSpec = And(ApiVersion(2), Public())
read_only: RoutingSpec = Or(Methods("GET"), Methods("HEAD"))
Leaf specs¶
Yes()/No()— alwaysTrue/ alwaysFalse.Unset()— alwaysNone(no opinion).Methods(*methods)— true when the route's methods intersect the given set;Methods(Methods.ALL)matches any.Paths(*patterns, startswith=False, endswith=False)— match the path against string fragments (substring by default, or prefix/suffix with the flags) or compiledre.Patternobjects.OpenapiExtra(key, value)— true whenopenapi_extra[key] == value.ApiVersion(version)— shorthand forOpenapiExtra("x-api-version", version).Public()— shorthand forOpenapiExtra("x-public", True).
Combinators¶
And(*specs)— true unless one spec isFalse.Or(*specs)— true when any spec isTrue.Not(spec)— negates, propagatingNoneunchanged.
Combinators accept booleans as well as specs; a bare True/False is coerced to
Yes()/No().
Evaluating a spec¶
resolve(...) evaluates specs with a route-level, router-level and default-level
precedence, against the keyword route metadata:
from fastapi_router_variants import Methods, resolve
is_read = resolve(
Or(Methods("GET"), Methods("HEAD")),
path="/users",
methods=["GET"],
)
resolve returns a concrete bool; if the resolved spec yields None (unset)
and no Yes()/No() default is supplied, it raises UnsetResultError.
Composing across levels¶
By default the most specific non-unset spec wins (route over router over default). The reference specs let a spec at one level splice in another level's spec instead of shadowing it, and the without specs opt a level out:
DefaultsReference()— placeholder replaced by thedefault_spec.RouterReference()— placeholder replaced by therouter_spec.RouteReference()— placeholder replaced by theroute_spec.ChildReference()— placeholder replaced by the next level down (the router spec when resolving a default, the route spec when resolving a router).WithoutDefaults()/WithoutRouter()— mark a spec so resolution does not fall back to the default / router level.
from fastapi_router_variants import DefaultsReference, Methods, Or, resolve
# A router that adds POST on top of whatever the defaults already allow.
allowed = resolve(
router_spec=Or(Methods("POST"), DefaultsReference()),
default_spec=Methods("GET"),
methods=["GET"],
) # True — matched through the referenced default
Filtering routes¶
resolve_routes(routes, spec) lazily yields the routes of an iterable that match
a spec — handy for selecting a subset of an app's routes:
from fastapi_router_variants import Public, collect_app_routes, resolve_routes
public_routes = list(resolve_routes(collect_app_routes(app), Public()))
Internally this uses resolve_route, which reads a BaseRoute's metadata and
evaluates the spec against it with Yes() as the default.
Where the extensions come from¶
The x-api-version, x-path-prefix, x-deployment and x-public extensions
that ApiVersion, OpenapiExtra and Public match are set automatically by
RouterWrapper when it expands a route (from the route's version, prefix,
deployment, and the resolved public spec). That is what lets a spec written
against openapi_extra classify generated variants.