diff --git a/docs/sanic/routing.md b/docs/sanic/routing.md index 54ea56a680..c016bd7091 100644 --- a/docs/sanic/routing.md +++ b/docs/sanic/routing.md @@ -241,6 +241,45 @@ def handler(request): app.blueprint(bp) ``` +The behavior of how the `strict_slashes` flag follows a defined hierarchy which decides if a specific route +falls under the `strict_slashes` behavior. + +```bash +|___ Route + |___ Blueprint + |___ Application +``` + +Above hierarchy defines how the `strict_slashes` flag will behave. The first non `None` value of the `strict_slashes` +found in the above order will be applied to the route in question. + +```python +from sanic import Sanic, Blueprint +from sanic.response import text + +app = Sanic("sample_strict_slashes", strict_slashes=True) + +@app.get("/r1") +def r1(request): + return text("strict_slashes is applicable from App level") + +@app.get("/r2", strict_slashes=False) +def r2(request): + return text("strict_slashes is not applicable due to False value set in route level") + +bp = Blueprint("bp", strict_slashes=False) + +@bp.get("/r3", strict_slashes=True) +def r3(request): + return text("strict_slashes applicable from blueprint route level") + +bp1 = Blueprint("bp1", strict_slashes=True) + +@bp.get("/r4") +def r3(request): + return text("strict_slashes applicable from blueprint level") +``` + ## User defined route name A custom route name can be used by passing a `name` argument while registering the route which will diff --git a/sanic/blueprints.py b/sanic/blueprints.py index c50de64809..b4894f6254 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -37,7 +37,7 @@ def __init__( url_prefix=None, host=None, version=None, - strict_slashes=False, + strict_slashes=None, ): """ In *Sanic* terminology, a **Blueprint** is a logical collection of diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index f0a67bd743..16a973095f 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -687,3 +687,49 @@ def test_register_blueprint(app, debug): "version 1.0. Please use the blueprint method" " instead" ) + + +def test_strict_slashes_behavior_adoption(app): + app.strict_slashes = True + + @app.get("/test") + def handler_test(request): + return text("Test") + + assert app.test_client.get("/test")[1].status == 200 + assert app.test_client.get("/test/")[1].status == 404 + + bp = Blueprint("bp") + + @bp.get("/one", strict_slashes=False) + def one(request): + return text("one") + + @bp.get("/second") + def second(request): + return text("second") + + app.blueprint(bp) + + assert app.test_client.get("/one")[1].status == 200 + assert app.test_client.get("/one/")[1].status == 200 + + assert app.test_client.get("/second")[1].status == 200 + assert app.test_client.get("/second/")[1].status == 404 + + bp2 = Blueprint("bp2", strict_slashes=False) + + @bp2.get("/third") + def third(request): + return text("third") + + app.blueprint(bp2) + assert app.test_client.get("/third")[1].status == 200 + assert app.test_client.get("/third/")[1].status == 200 + + @app.get("/f1", strict_slashes=False) + def f1(request): + return text("f1") + + assert app.test_client.get("/f1")[1].status == 200 + assert app.test_client.get("/f1/")[1].status == 200 diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index c6fc0831c1..672d78ac19 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -24,7 +24,9 @@ class ReusableSanicConnectionPool(httpcore.ConnectionPool): async def acquire_connection(self, origin): global old_conn - connection = self.active_connections.pop_by_origin(origin, http2_only=True) + connection = self.active_connections.pop_by_origin( + origin, http2_only=True + ) if connection is None: connection = self.keepalive_connections.pop_by_origin(origin) @@ -187,11 +189,7 @@ async def _local_request(self, method, url, *args, **kwargs): self._session = ResusableSanicSession() try: response = await getattr(self._session, method.lower())( - url, - verify=False, - timeout=request_keepalive, - *args, - **kwargs, + url, verify=False, timeout=request_keepalive, *args, **kwargs ) except NameError: raise Exception(response.status_code) diff --git a/tests/test_redirect.py b/tests/test_redirect.py index 86c4ace3fe..8e6c35f0c9 100644 --- a/tests/test_redirect.py +++ b/tests/test_redirect.py @@ -110,7 +110,7 @@ def test_redirect_with_header_injection(redirect_app): @pytest.mark.parametrize("test_str", ["sanic-test", "sanictest", "sanic test"]) -async def test_redirect_with_params(app, test_client, test_str): +async def test_redirect_with_params(app, sanic_client, test_str): @app.route("/api/v1/test//") async def init_handler(request, test): assert test == test_str @@ -121,7 +121,7 @@ async def target_handler(request, test): assert test == test_str return text("OK") - test_cli = await test_client(app) + test_cli = await sanic_client(app) response = await test_cli.get("/api/v1/test/{}/".format(quote(test_str))) assert response.status == 200 diff --git a/tests/test_request_cancel.py b/tests/test_request_cancel.py index e9499f6d78..916f4d144d 100644 --- a/tests/test_request_cancel.py +++ b/tests/test_request_cancel.py @@ -4,7 +4,7 @@ from sanic.response import stream, text -async def test_request_cancel_when_connection_lost(loop, app, test_client): +async def test_request_cancel_when_connection_lost(loop, app, sanic_client): app.still_serving_cancelled_request = False @app.get("/") @@ -14,7 +14,7 @@ async def handler(request): app.still_serving_cancelled_request = True return text("OK") - test_cli = await test_client(app) + test_cli = await sanic_client(app) # schedule client call task = loop.create_task(test_cli.get("/")) @@ -33,7 +33,7 @@ async def handler(request): assert app.still_serving_cancelled_request is False -async def test_stream_request_cancel_when_conn_lost(loop, app, test_client): +async def test_stream_request_cancel_when_conn_lost(loop, app, sanic_client): app.still_serving_cancelled_request = False @app.post("/post/", stream=True) @@ -53,7 +53,7 @@ async def streaming(response): return stream(streaming) - test_cli = await test_client(app) + test_cli = await sanic_client(app) # schedule client call task = loop.create_task(test_cli.post("/post/1")) diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py index d845dc8507..65472a1e84 100644 --- a/tests/test_request_stream.py +++ b/tests/test_request_stream.py @@ -111,7 +111,6 @@ async def patch(request): result += body.decode("utf-8") return text(result) - assert app.is_request_stream is True request, response = app.test_client.get("/get") diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index 3a41e46225..e3e02d7c61 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -13,15 +13,12 @@ def __init__(self, request_delay=None, *args, **kwargs): self._request_delay = request_delay super().__init__(*args, **kwargs) - async def send( - self, - request, - stream=False, - ssl=None, - timeout=None, - ): + async def send(self, request, stream=False, ssl=None, timeout=None): connection = await self.acquire_connection(request.url.origin) - if connection.h11_connection is None and connection.h2_connection is None: + if ( + connection.h11_connection is None + and connection.h2_connection is None + ): await connection.connect(ssl=ssl, timeout=timeout) if self._request_delay: await asyncio.sleep(self._request_delay) diff --git a/tests/test_response.py b/tests/test_response.py index 4e30519136..c47dd1db6d 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -231,9 +231,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app): assert response.text == "foo,bar" -def test_non_chunked_streaming_adds_correct_headers( - non_chunked_streaming_app -): +def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): request, response = non_chunked_streaming_app.test_client.get("/") assert "Transfer-Encoding" not in response.headers assert response.headers["Content-Type"] == "text/csv"