fastapi-guard 7.2.0__tar.gz → 7.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,8 @@ version_selector: true
2
2
  title_switch: true
3
3
  versions_file: docs/versions/versions.json
4
4
  versions:
5
+ - 7.2.2
6
+ - 7.2.1
5
7
  - 7.2.0
6
8
  - 7.1.1
7
9
  - 7.1.0
@@ -46,4 +48,4 @@ versions:
46
48
  - 0.3.2
47
49
  - latest
48
50
  aliases:
49
- latest: 7.2.0
51
+ latest: 7.2.2
@@ -3,6 +3,26 @@ Release Notes
3
3
 
4
4
  ___
5
5
 
6
+ v7.2.2 (2026-07-01)
7
+ -------------------
8
+
9
+ include_router route resolution now descends the FastAPI wrapper (v7.2.2)
10
+ ------------------------------------------------------------------------
11
+
12
+ - **Fixed** — Per-route decorator config now resolves on `include_router` routes under FastAPI >= 0.115. 7.2.1 resolved routes with `route.matches()` but stopped at FastAPI's `_IncludedRouter` wrapper: its `matches()` returns `Match.FULL` with an empty child-scope and the real routes nested inside. Older FastAPI flattened included routers onto the app (so `matches()` found the leaves directly and CI passed), but newer FastAPI keeps them inside the wrapper — so 7.2.1 resolved nothing there and every per-route decorator (`rate_limit`, `max_request_size`, `content_type_filter`, `custom_validation`, `detection_exclusion`, `suspicious_detection`, `usage_monitor`, `honeypot_detection`, behavioral rules) was still inert. Resolution now descends recursively into matched sub-routers/mounts (`.routes`, `original_router.routes`, `.app.routes`), so per-route config fires on `include_router` and path-parameter routes. Direct routes and single-level `include_router` (prefixed or not) are covered; deeply-nested prefixed routers remain a known limitation of the wrapper's child-scope handling. Supersedes the incomplete 7.2.1 fix.
13
+
14
+ ___
15
+
16
+ v7.2.1 (2026-07-01)
17
+ -------------------
18
+
19
+ Per-route config resolves on parameterised and included routes (v7.2.1)
20
+ -----------------------------------------------------------------------
21
+
22
+ - **Fixed** — Per-route decorator config now resolves on routes with path parameters and routes added via `include_router`. `SecurityMiddleware` is a `BaseHTTPMiddleware`, so it runs before the router and `scope["route"]` is unset; it resolved the active route by exact-path string comparison (`r.path == request.url.path`), which never matches a templated path like `/items/{id}`. As a result every per-route decorator (`rate_limit`, `max_request_size`, `content_type_filter`, `custom_validation`, `detection_exclusion`, `suspicious_detection`, behavioral rules, …) silently did nothing on parameterised or router-included routes. Route resolution now replicates Starlette's own `route.matches(scope)` matching, so per-route config fires on those routes too. Static, non-parameterised routes are unaffected.
23
+
24
+ ___
25
+
6
26
  v7.2.0 (2026-06-23)
7
27
  -------------------
8
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi_guard
3
- Version: 7.2.0
3
+ Version: 7.2.2
4
4
  Summary: Production-ready security middleware for FastAPI — IP filtering, rate limiting, penetration detection, and 20+ per-route security decorators.
5
5
  Project-URL: Homepage, https://guard-core.com
6
6
  Project-URL: Documentation, https://rennf93.github.io/fastapi-guard/latest/
@@ -28,6 +28,7 @@ from guard_core.utils import (
28
28
  )
29
29
  from starlette.middleware.base import BaseHTTPMiddleware
30
30
  from starlette.responses import Response as StarletteResponse
31
+ from starlette.routing import Match
31
32
  from starlette.types import ASGIApp
32
33
 
33
34
  from guard.adapters import (
@@ -372,24 +373,54 @@ class SecurityMiddleware(BaseHTTPMiddleware):
372
373
  route = request.scope.get("route")
373
374
  if route:
374
375
  return route
375
-
376
376
  app = request.scope.get("app")
377
- if not app or not hasattr(app, "routes"):
377
+ routes = getattr(app, "routes", None)
378
+ if not routes:
378
379
  return None
379
-
380
- path = request.url.path
381
- method = request.method
382
- for r in app.routes:
383
- if (
384
- hasattr(r, "path")
385
- and hasattr(r, "methods")
386
- and r.path == path
387
- and method in r.methods
388
- and hasattr(r, "endpoint")
389
- ):
380
+ return self._match_route(routes, request.scope, 0)
381
+
382
+ def _match_route(self, routes: Any, scope: Any, depth: int) -> Any:
383
+ # This middleware is a BaseHTTPMiddleware, so it runs before the router
384
+ # and scope["route"] is unset. Replicate Starlette's matching, descending
385
+ # into sub-routers / mounts (FastAPI's include_router inserts a wrapper
386
+ # route whose real routes are nested inside), so per-route decorator
387
+ # config resolves for path-parameter and include_router routes, not only
388
+ # routes flattened directly onto the app.
389
+ for r in routes:
390
+ matched, child = self._route_full_match(r, scope)
391
+ if not matched:
392
+ continue
393
+ if hasattr(r, "endpoint"):
390
394
  return r
395
+ if depth >= 8:
396
+ continue
397
+ found = self._match_route(
398
+ self._sub_routes(r), {**scope, **child}, depth + 1
399
+ )
400
+ if found is not None:
401
+ return found
391
402
  return None
392
403
 
404
+ def _route_full_match(self, route: Any, scope: Any) -> tuple[bool, Any]:
405
+ matches = getattr(route, "matches", None)
406
+ if matches is None:
407
+ return False, {}
408
+ try:
409
+ match, child = matches(scope)
410
+ except Exception:
411
+ return False, {}
412
+ return match == Match.FULL, child
413
+
414
+ def _sub_routes(self, route: Any) -> Any:
415
+ included = getattr(route, "original_router", None)
416
+ mounted = getattr(route, "app", None)
417
+ return (
418
+ getattr(route, "routes", None)
419
+ or getattr(included, "routes", None)
420
+ or getattr(mounted, "routes", None)
421
+ or []
422
+ )
423
+
393
424
  def _populate_guard_state(
394
425
  self, guard_request: StarletteGuardRequest, request: Request
395
426
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fastapi_guard"
3
- version = "7.2.0"
3
+ version = "7.2.2"
4
4
  description = "Production-ready security middleware for FastAPI — IP filtering, rate limiting, penetration detection, and 20+ per-route security decorators."
5
5
  authors = [
6
6
  {name = "Renzo Franceschini", email = "rennf93@users.noreply.github.com"}
File without changes
File without changes
File without changes
File without changes
File without changes