From 502a32a96fa5bb46e087837c2c61777d4c29edde Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:11:27 -0500 Subject: [PATCH] update fastapi and unpin starlette fastapi 0.116 capped starlette below 0.49, which kept us on a version with several open advisories, the earliest of which is fixed in 0.49.1. fastapi moves to 0.142 and the starlette pin is removed so the latest (1.7.0 today) is pulled in. fastapi 0.137 and later stopped flattening included routers into app.routes, so the spec generator found no routes to classify and every endpoint failed validation. The generator now reads routes from the routers directly. The spec is regenerated on the new versions. --- docker/main/requirements-wheels.txt | 3 +- docs/static/frigate-api.yaml | 9 ++++- generate_api_auth_spec.py | 57 ++++++++++++++++------------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index bbfe91f824..5bb3b4c511 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -2,9 +2,8 @@ aiofiles == 25.1.* click == 8.5.* # FastAPI aiohttp == 3.12.* -starlette == 0.47.* starlette-context == 0.5.* -fastapi[standard-no-fastapi-cloud-cli] == 0.116.* +fastapi[standard-no-fastapi-cloud-cli] == 0.142.* uvicorn == 0.52.* slowapi == 0.1.* joserfc == 1.6.* diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml index 97acaf942c..b0f72808e8 100644 --- a/docs/static/frigate-api.yaml +++ b/docs/static/frigate-api.yaml @@ -8017,7 +8017,7 @@ components: properties: file: type: string - format: binary + contentMediaType: application/octet-stream title: File type: object required: @@ -8027,7 +8027,7 @@ components: properties: file: type: string - format: binary + contentMediaType: application/octet-stream title: File type: object required: @@ -9601,6 +9601,11 @@ components: type: type: string title: Error Type + input: + title: Input + ctx: + type: object + title: Context type: object required: - loc diff --git a/generate_api_auth_spec.py b/generate_api_auth_spec.py index 3193836927..6b5b8b0eaf 100644 --- a/generate_api_auth_spec.py +++ b/generate_api_auth_spec.py @@ -136,33 +136,36 @@ ACCESS_NOTES = { } +# Mirrors the router set wired up in frigate.api.fastapi_app. +ROUTERS = [ + auth.router, + camera.router, + chat.router, + classification.router, + review.router, + main_app.router, + preview.router, + notification.router, + export.router, + hardware.router, + notices.router, + event.router, + media.router, + motion_search.router, + record.router, + debug_replay.router, +] + + def build_app() -> FastAPI: """Build a bare app with every router mounted. - This mirrors the router set wired up in frigate.api.fastapi_app. It omits - the global admin dependency and all runtime state; the OpenAPI route table - and the per-route dependencies are all we need to export and classify. + It omits the global admin dependency and all runtime state; the OpenAPI + route table and the per-route dependencies are all we need to export and + classify. """ app = FastAPI() - routers = [ - auth.router, - camera.router, - chat.router, - classification.router, - review.router, - main_app.router, - preview.router, - notification.router, - export.router, - hardware.router, - notices.router, - event.router, - media.router, - motion_search.router, - record.router, - debug_replay.router, - ] - for router in routers: + for router in ROUTERS: app.include_router(router) return app @@ -318,13 +321,17 @@ def _classify_base( def build_access_map( - app: FastAPI, exempt_paths: set[str], exempt_prefixes: tuple[str, ...], ) -> dict[tuple[str, str], dict]: """Map (path, lowercase method) -> classification details.""" access_map: dict[tuple[str, str], dict] = {} - for route in app.routes: + + # app.routes holds opaque wrappers for included routers on newer FastAPI. + # The routers mount without a prefix, so their own routes carry final paths. + routes = [route for router in ROUTERS for route in router.routes] + + for route in routes: if not isinstance(route, APIRoute): continue level, roles, flag = classify_route(route, exempt_paths, exempt_prefixes) @@ -515,7 +522,7 @@ def render(spec: dict) -> str: def build_spec() -> tuple[dict, dict, list, list, list]: app = build_app() exempt_paths, exempt_prefixes = read_exempt_rules() - access_map = build_access_map(app, exempt_paths, exempt_prefixes) + access_map = build_access_map(exempt_paths, exempt_prefixes) spec = base_document(app.openapi()) normalized = strip_volatile_defaults(spec)