From 8c5f6165b7a66712f3e7e3ceab502069a5c5939a Mon Sep 17 00:00:00 2001 From: Leonardo Rayner Date: Wed, 10 Jun 2026 02:21:55 -0400 Subject: [PATCH 1/3] fix: Replace deprecated @app.route with app.add_route for Starlette 1.0 compatibility --- docs/examples.md | 16 ++++++++++------ tests/test_starlette_extension.py | 25 ++++++++++++++++--------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 1ee072e..0b3f1b8 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -29,10 +29,11 @@ The tests show a lot of different use cases that are not all covered here. ## Exempt a route from the global limit ```python - @app.route("/someroute") - @limiter.exempt def t(request: Request): return PlainTextResponse("I'm unlimited") + + limiter.exempt(handler) + app.add_route("/someroute", handler) ``` ## Disable the limiter entirely @@ -44,10 +45,11 @@ Simply pass `enabled=False` to the constructor. ```python limiter = Limiter(key_func=get_remote_address, enabled=False) - @app.route("/someroute") - @limiter.exempt def t(request: Request): return PlainTextResponse("I'm unlimited") + + limiter.exempt(handler) + app.add_route("/someroute", handler) ``` You can always switch this during the lifetime of the limiter: @@ -76,10 +78,11 @@ Define a function which takes a request as parameter and returns a cost and pass def get_hit_cost(request: Request) -> int: return len(request) - @app.route("/someroute") @limiter.limit("100/minute", cost=get_hit_cost) def t(request: Request): return PlainTextResponse("I'm limited by the request size") + + app.add_route("/someroute", handler) ``` ## WSGI vs ASGI Middleware @@ -107,9 +110,10 @@ app.add_middleware(SlowAPIASGIMiddleware) Let's use this route as an example: ```python -@app.route("/some_route/{some_param}") def my_func(some_param): ... + +app.add_route("/someroute/{some_param}", handler) ``` ```python diff --git a/tests/test_starlette_extension.py b/tests/test_starlette_extension.py index 0e26baa..c086f75 100644 --- a/tests/test_starlette_extension.py +++ b/tests/test_starlette_extension.py @@ -175,15 +175,17 @@ def test_headers_no_breach(self, build_starlette_app): headers_enabled=True, key_func=get_remote_address ) - @app.route("/t1") @limiter.limit("10/minute") def t1(request: Request): return PlainTextResponse("test") + + app.add_route("/t1", t1) - @app.route("/t2") @limiter.limit("2/second; 5 per minute; 10/hour") def t2(request: Request): return PlainTextResponse("test") + + app.add_route("/t2", t2) with hiro.Timeline().freeze(): with TestClient(app) as cli: @@ -208,10 +210,11 @@ def test_headers_breach(self, build_starlette_app): headers_enabled=True, key_func=get_remote_address ) - @app.route("/t1") @limiter.limit("2/second; 10 per minute; 20/hour") def t(request: Request): return PlainTextResponse("test") + + app.add_route("/t1", t) with hiro.Timeline().freeze() as timeline: with TestClient(app) as cli: @@ -233,10 +236,11 @@ def test_retry_after(self, build_starlette_app): headers_enabled=True, key_func=get_remote_address ) - @app.route("/t1") @limiter.limit("1/minute") def t(request: Request): return PlainTextResponse("test") + + app.add_route("/t1", t) with hiro.Timeline().freeze() as timeline: with TestClient(app) as cli: @@ -254,9 +258,10 @@ def test_exempt_decorator(self, build_starlette_app): default_limits=["1/minute"], ) - @app.route("/t1") def t1(request: Request): return PlainTextResponse("test") + + app.add_route("/t1", t1) with TestClient(app) as cli: resp = cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.10"}) @@ -264,11 +269,12 @@ def t1(request: Request): resp2 = cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.10"}) assert resp2.status_code == 429 - @app.route("/t2") - @limiter.exempt def t2(request: Request): """Exempt a sync route""" return PlainTextResponse("test") + + limiter.exempt(t2) + app.add_route("/t2", t2) with TestClient(app) as cli: resp = cli.get("/t2", headers={"X_FORWARDED_FOR": "127.0.0.10"}) @@ -276,11 +282,12 @@ def t2(request: Request): resp2 = cli.get("/t2", headers={"X_FORWARDED_FOR": "127.0.0.10"}) assert resp2.status_code == 200 - @app.route("/t3") - @limiter.exempt async def t3(request: Request): """Exempt an async route""" return PlainTextResponse("test") + + limiter.exempt(t3) + app.add_route("/t3", t3) with TestClient(app) as cli: resp = cli.get("/t3", headers={"X_FORWARDED_FOR": "127.0.0.10"}) From 755ae7d72b861bc5221b6a424fc097da49290c12 Mon Sep 17 00:00:00 2001 From: Leonardo Rayner Date: Thu, 11 Jun 2026 20:20:47 -0400 Subject: [PATCH 2/3] Apply suggestion from @dotlambda Update to examples.md to mirror project handler names Co-authored-by: dotlambda --- docs/examples.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 0b3f1b8..a819fad 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -32,8 +32,8 @@ The tests show a lot of different use cases that are not all covered here. def t(request: Request): return PlainTextResponse("I'm unlimited") - limiter.exempt(handler) - app.add_route("/someroute", handler) + limiter.exempt(t) + app.add_route("/someroute", t) ``` ## Disable the limiter entirely From 4b84105730ed11f2b0db5e22d726c52e635c6388 Mon Sep 17 00:00:00 2001 From: Leonardo Rayner Date: Fri, 12 Jun 2026 04:36:07 -0400 Subject: [PATCH 3/3] fix: Use correct function names in examples.md --- docs/examples.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 0b3f1b8..e7f5fae 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -32,8 +32,8 @@ The tests show a lot of different use cases that are not all covered here. def t(request: Request): return PlainTextResponse("I'm unlimited") - limiter.exempt(handler) - app.add_route("/someroute", handler) + limiter.exempt(t) + app.add_route("/someroute", t) ``` ## Disable the limiter entirely @@ -48,8 +48,8 @@ Simply pass `enabled=False` to the constructor. def t(request: Request): return PlainTextResponse("I'm unlimited") - limiter.exempt(handler) - app.add_route("/someroute", handler) + limiter.exempt(t) + app.add_route("/someroute", t) ``` You can always switch this during the lifetime of the limiter: @@ -82,7 +82,7 @@ Define a function which takes a request as parameter and returns a cost and pass def t(request: Request): return PlainTextResponse("I'm limited by the request size") - app.add_route("/someroute", handler) + app.add_route("/someroute", t) ``` ## WSGI vs ASGI Middleware @@ -113,7 +113,7 @@ Let's use this route as an example: def my_func(some_param): ... -app.add_route("/someroute/{some_param}", handler) +app.add_route("/someroute/{some_param}", my_func) ``` ```python