Skip to content

Commit 2efe423

Browse files
authored
Support fastapi >= 0.137 (#929)
- fix add_route_dependencies to support FastAPI >= 0.137 by recursively unwrapping _IncludedRouter objects
1 parent 652779a commit 2efe423

3 files changed

Lines changed: 56 additions & 45 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
### Fixed
1818

1919
- fix mypy type errors in transaction extension for Python 3.14 compatibility (mypy 1.20.0) ([#895](https://github.com/stac-utils/stac-fastapi/pull/895))
20+
- fix `add_route_dependencies` to support FastAPI >= 0.137 by recursively unwrapping `_IncludedRouter` objects ([#929](https://github.com/stac-utils/stac-fastapi/pull/929))
2021

2122
## [6.2.1] - 2026-02-10
2223

stac_fastapi/api/stac_fastapi/api/routes.py

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,45 @@ class Scope(TypedDict, total=False):
8383
type: Optional[str]
8484

8585

86+
def _is_endpoint_route(route: BaseRoute) -> bool:
87+
"""Check if route is a standard endpoint (not a mount or sub-router)."""
88+
return (
89+
hasattr(route, "path") and hasattr(route, "methods") and route.methods is not None
90+
)
91+
92+
93+
def _apply_dependencies_to_route(
94+
route: BaseRoute, scopes: List[Scope], dependencies: List[params.Depends]
95+
) -> None:
96+
"""Apply dependencies to a single route matching the given scopes."""
97+
for scope in scopes:
98+
_scope = copy.deepcopy(scope)
99+
100+
if scope["path"] == "*":
101+
_scope["path"] = route.path # type: ignore
102+
103+
if scope["method"] == "*":
104+
_scope["method"] = list(route.methods)[0] # type: ignore
105+
106+
match, _ = route.matches({"type": "http", **_scope})
107+
if match != Match.FULL:
108+
continue
109+
110+
if not hasattr(route, "dependant"):
111+
continue
112+
113+
for depends in dependencies[::-1]:
114+
route.dependant.dependencies.insert(
115+
0,
116+
get_parameterless_sub_dependant(
117+
depends=depends,
118+
path=route.path_format, # type: ignore
119+
),
120+
)
121+
122+
route.dependencies.extend(dependencies) # type: ignore
123+
124+
86125
def add_route_dependencies(
87126
routes: List[BaseRoute], scopes: List[Scope], dependencies: List[params.Depends]
88127
) -> None:
@@ -96,48 +135,19 @@ def add_route_dependencies(
96135
Returns:
97136
None
98137
"""
99-
for scope in scopes:
100-
_scope = copy.deepcopy(scope)
101-
for route in routes:
102-
if scope["path"] == "*":
103-
# NOTE: ignore type, because BaseRoute has no "path" attribute
104-
# but APIRoute does.
105-
_scope["path"] = route.path # type: ignore
106-
107-
# NOTE: ignore type, because BaseRoute has no "method" attribute
108-
# but APIRoute does.
109-
if scope["method"] == "*":
110-
_scope["method"] = list(route.methods)[0] # type: ignore
111-
112-
match, _ = route.matches({"type": "http", **_scope})
113-
if match != Match.FULL:
114-
continue
115-
116-
# Ignore paths without dependants, e.g. /api, /api.html, /docs/oauth2-redirect
117-
if not hasattr(route, "dependant"):
118-
continue
119-
120-
# Mimicking how APIRoute handles dependencies:
121-
# https://github.com/tiangolo/fastapi/blob/1760da0efa55585c19835d81afa8ca386036c325/fastapi/routing.py#L408-L412
122-
for depends in dependencies[::-1]:
123-
route.dependant.dependencies.insert(
124-
0,
125-
get_parameterless_sub_dependant(
126-
# NOTE: ignore type, because BaseRoute has no "path_format"
127-
# attribute but APIRoute does.
128-
depends=depends,
129-
path=route.path_format, # type: ignore
130-
),
131-
)
132-
133-
# Register dependencies directly on route so that they aren't ignored if
134-
# the routes are later associated with an app (e.g.
135-
# app.include_router(router))
136-
# https://github.com/tiangolo/fastapi/blob/58ab733f19846b4875c5b79bfb1f4d1cb7f4823f/fastapi/applications.py#L337-L360
137-
# https://github.com/tiangolo/fastapi/blob/58ab733f19846b4875c5b79bfb1f4d1cb7f4823f/fastapi/routing.py#L677-L678
138-
# NOTE: ignore type, because BaseRoute has no "dependencies" attribute
139-
# but APIRoute does.
140-
route.dependencies.extend(dependencies) # type: ignore
138+
for route in routes:
139+
if hasattr(route, "original_router"):
140+
add_route_dependencies(route.original_router.routes, scopes, dependencies)
141+
continue
142+
143+
if hasattr(route, "routes") and route.routes:
144+
add_route_dependencies(route.routes, scopes, dependencies)
145+
continue
146+
147+
if not _is_endpoint_route(route):
148+
continue
149+
150+
_apply_dependencies_to_route(route, scopes, dependencies)
141151

142152

143153
def add_direct_response(app: FastAPI) -> None:

uv.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)