From f41435fae375e91e4eef4c00526445604e0d3bbe Mon Sep 17 00:00:00 2001 From: tomaszdrozdz Date: Mon, 19 Oct 2020 10:12:20 +0200 Subject: [PATCH 01/15] Improving documentation. --- docs/sanic/static_files.rst | 2 +- sanic/exceptions.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sanic/static_files.rst b/docs/sanic/static_files.rst index 1c93a682..bef5f6fb 100644 --- a/docs/sanic/static_files.rst +++ b/docs/sanic/static_files.rst @@ -88,5 +88,5 @@ When `stream_large_files` is `True`, Sanic will use `file_stream()` instead of ` app = Sanic(__name__) - chunk_size = 1024 * 1024 * 8 # Set chunk size to 8KB + chunk_size = 1024 * 1024 * 8 # Set chunk size to 8MiB app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=chunk_size) diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 000b9e76..8c747f60 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -175,8 +175,8 @@ def abort(status_code, message=None): message appropriate for the given status code, unless provided. :param status_code: The HTTP status code to return. - :param message: The HTTP response body. Defaults to the messages - in response.py for the given status code. + :param message: The HTTP response body. Defaults to the messages in + STATUS_CODES from sanic.helpers for the given status code. """ if message is None: message = STATUS_CODES.get(status_code) From 19b84ce9f0623fb62c678446dadef743e56d4e3c Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 15:11:39 +0200 Subject: [PATCH 02/15] Update changelog for 19.12.3 and 20.9.1 --- CHANGELOG.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 006bad7c..e0ee0d93 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,28 @@ +Version 20.9.1 +=============== + +Bugfixes +******** + + * + `#1954 `_ + Fix static route registration on blueprints + * + `#1957 `_ + Removes duplicate headers in ASGI streaming body + + +Version 19.12.3 +=============== + +Bugfixes +******** + + * + `#1959 `_ + Removes duplicate headers in ASGI streaming body + + Version 20.9.0 =============== From 217a7c5161b1d67a40a64d31bd600ff93e469a8f Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 20:09:42 +0200 Subject: [PATCH 03/15] Small changes to sanic-cli to make it more user friendly --- sanic/__main__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index c9fa2e52..0a55ae67 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -1,10 +1,10 @@ import os import sys - from argparse import ArgumentParser from importlib import import_module from typing import Any, Dict, Optional +from sanic import __version__ from sanic.app import Sanic from sanic.log import logger @@ -22,6 +22,9 @@ def main(): ) parser.add_argument("--workers", dest="workers", type=int, default=1) parser.add_argument("--debug", dest="debug", action="store_true") + parser.add_argument( + "--version", action="version", version=f"Sanic {__version__}", + ) parser.add_argument("module") args = parser.parse_args() @@ -30,9 +33,12 @@ def main(): if module_path not in sys.path: sys.path.append(module_path) - module_parts = args.module.split(".") - module_name = ".".join(module_parts[:-1]) - app_name = module_parts[-1] + if ":" in args.module: + module_name, app_name = args.module.rsplit(":", 1) + else: + module_parts = args.module.split(".") + module_name = ".".join(module_parts[:-1]) + app_name = module_parts[-1] module = import_module(module_name) app = getattr(module, app_name, None) From 7b1bce8d9078b1e172d265135f78edd42bbbfab4 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 20:21:09 +0200 Subject: [PATCH 04/15] Add some help messages and a user friendly cli experience --- sanic/__main__.py | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index 0a55ae67..a916c3ba 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -11,21 +11,51 @@ from sanic.log import logger def main(): parser = ArgumentParser(prog="sanic") - parser.add_argument("--host", dest="host", type=str, default="127.0.0.1") - parser.add_argument("--port", dest="port", type=int, default=8000) - parser.add_argument("--unix", dest="unix", type=str, default="") parser.add_argument( - "--cert", dest="cert", type=str, help="location of certificate for SSL" + "-H", + "--host", + dest="host", + type=str, + default="127.0.0.1", + help="Host address [default 127.0.0.1]", ) parser.add_argument( - "--key", dest="key", type=str, help="location of keyfile for SSL." + "-p", + "--port", + dest="port", + type=int, + default=8000, + help="Port to serve on [default 8000]", + ) + parser.add_argument( + "-u", + "--unix", + dest="unix", + type=str, + default="", + help="Location of unix socket", + ) + parser.add_argument( + "--cert", dest="cert", type=str, help="Location of certificate for SSL" + ) + parser.add_argument( + "--key", dest="key", type=str, help="Location of keyfile for SSL." + ) + parser.add_argument( + "-w", + "--workers", + dest="workers", + type=int, + default=1, + help="Number of worker processes [default 1]", ) - parser.add_argument("--workers", dest="workers", type=int, default=1) parser.add_argument("--debug", dest="debug", action="store_true") parser.add_argument( - "--version", action="version", version=f"Sanic {__version__}", + "-v", "--version", action="version", version=f"Sanic {__version__}", + ) + parser.add_argument( + "module", help="Path to your Sanic app. Example: path.to.server:app" ) - parser.add_argument("module") args = parser.parse_args() try: From a026cd719538c8fce40fa0d12323d1c80cfef750 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 20:36:22 +0200 Subject: [PATCH 05/15] add --access-logs flag to sanic cli --- sanic/__main__.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index a916c3ba..6ad35bac 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -9,15 +9,25 @@ from sanic.app import Sanic from sanic.log import logger +class SanicArgumentParser(ArgumentParser): + def add_bool_arguments(self, *args, **kwargs): + group = self.add_mutually_exclusive_group() + group.add_argument(*args, action="store_true", **kwargs) + kwargs["help"] = "no " + kwargs["help"] + group.add_argument( + "--no-" + args[0][2:], *args[1:], action="store_false", **kwargs + ) + + def main(): - parser = ArgumentParser(prog="sanic") + parser = SanicArgumentParser(prog="sanic") parser.add_argument( "-H", "--host", dest="host", type=str, default="127.0.0.1", - help="Host address [default 127.0.0.1]", + help="host address [default 127.0.0.1]", ) parser.add_argument( "-p", @@ -25,7 +35,7 @@ def main(): dest="port", type=int, default=8000, - help="Port to serve on [default 8000]", + help="port to serve on [default 8000]", ) parser.add_argument( "-u", @@ -33,13 +43,13 @@ def main(): dest="unix", type=str, default="", - help="Location of unix socket", + help="location of unix socket", ) parser.add_argument( - "--cert", dest="cert", type=str, help="Location of certificate for SSL" + "--cert", dest="cert", type=str, help="location of certificate for SSL" ) parser.add_argument( - "--key", dest="key", type=str, help="Location of keyfile for SSL." + "--key", dest="key", type=str, help="location of keyfile for SSL." ) parser.add_argument( "-w", @@ -47,14 +57,17 @@ def main(): dest="workers", type=int, default=1, - help="Number of worker processes [default 1]", + help="number of worker processes [default 1]", ) parser.add_argument("--debug", dest="debug", action="store_true") + parser.add_bool_arguments( + "--access-logs", dest="access_log", help="display access logs" + ) parser.add_argument( "-v", "--version", action="version", version=f"Sanic {__version__}", ) parser.add_argument( - "module", help="Path to your Sanic app. Example: path.to.server:app" + "module", help="path to your Sanic app. Example: path.to.server:app" ) args = parser.parse_args() @@ -93,6 +106,7 @@ def main(): unix=args.unix, workers=args.workers, debug=args.debug, + access_log=args.access_log, ssl=ssl, ) except ImportError as e: From 33ee4c21b303b4b9fd93e10a27ca4de8be297a64 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 20:45:06 +0200 Subject: [PATCH 06/15] Add BASE_LOGO to sanic cli --- sanic/__main__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index 6ad35bac..5dec13f5 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -1,11 +1,12 @@ import os import sys -from argparse import ArgumentParser +from argparse import ArgumentParser, RawDescriptionHelpFormatter from importlib import import_module from typing import Any, Dict, Optional from sanic import __version__ from sanic.app import Sanic +from sanic.config import BASE_LOGO from sanic.log import logger @@ -20,7 +21,9 @@ class SanicArgumentParser(ArgumentParser): def main(): - parser = SanicArgumentParser(prog="sanic") + parser = SanicArgumentParser( + prog="sanic", description=BASE_LOGO, formatter_class=RawDescriptionHelpFormatter + ) parser.add_argument( "-H", "--host", From d18a776964083442cacc46f135fc6ee658c19bc3 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 21:22:19 +0200 Subject: [PATCH 07/15] squash --- sanic/__main__.py | 10 ++++++++-- tests/test_blueprints.py | 5 +++-- tests/test_load_module_from_file_location.py | 4 +++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index 5dec13f5..6619705c 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -1,5 +1,6 @@ import os import sys + from argparse import ArgumentParser, RawDescriptionHelpFormatter from importlib import import_module from typing import Any, Dict, Optional @@ -22,7 +23,9 @@ class SanicArgumentParser(ArgumentParser): def main(): parser = SanicArgumentParser( - prog="sanic", description=BASE_LOGO, formatter_class=RawDescriptionHelpFormatter + prog="sanic", + description=BASE_LOGO, + formatter_class=RawDescriptionHelpFormatter, ) parser.add_argument( "-H", @@ -67,7 +70,10 @@ def main(): "--access-logs", dest="access_log", help="display access logs" ) parser.add_argument( - "-v", "--version", action="version", version=f"Sanic {__version__}", + "-v", + "--version", + action="version", + version=f"Sanic {__version__}", ) parser.add_argument( "module", help="path to your Sanic app. Example: path.to.server:app" diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 6e9fb20f..60bc8221 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -735,6 +735,7 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name): _, response = app.test_client.get("/static/test.file/") assert response.status == 200 + @pytest.mark.parametrize("file_name", ["test.file"]) def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): current_file = inspect.getfile(inspect.currentframe()) @@ -745,7 +746,7 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): bp = Blueprint(name="test_mw", url_prefix="") - @bp.middleware('request') + @bp.middleware("request") def bp_mw1(request): nonlocal triggered triggered = True @@ -754,7 +755,7 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): "/test.file", get_file_path(static_file_directory, file_name), strict_slashes=True, - name="static" + name="static", ) app.blueprint(bp) diff --git a/tests/test_load_module_from_file_location.py b/tests/test_load_module_from_file_location.py index 5dc42d5c..c47913dd 100644 --- a/tests/test_load_module_from_file_location.py +++ b/tests/test_load_module_from_file_location.py @@ -20,7 +20,9 @@ def test_load_module_from_file_location(loaded_module_from_file_location): @pytest.mark.dependency(depends=["test_load_module_from_file_location"]) -def test_loaded_module_from_file_location_name(loaded_module_from_file_location,): +def test_loaded_module_from_file_location_name( + loaded_module_from_file_location, +): name = loaded_module_from_file_location.__name__ if "C:\\" in name: name = name.split("\\")[-1] From 4ca3e98082b58e019c5b3624d19505de442251a4 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Mon, 26 Oct 2020 05:31:34 +1000 Subject: [PATCH 08/15] Add `pytest-dependency` requirement to tests_require list in setup.py (#1955) Co-authored-by: Adam Hopkins --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f0351b91..3a44fb5a 100644 --- a/setup.py +++ b/setup.py @@ -96,6 +96,7 @@ tests_require = [ "pytest-sanic", "pytest-sugar", "pytest-benchmark", + "pytest-dependency", ] docs_require = [ From c0839afddea13501a3098f8e27e686a6a95a71ee Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Thu, 5 Nov 2020 14:53:48 +1000 Subject: [PATCH 09/15] Fix Chunked Transport-Encoding in ASGI streaming response In ASGI-mode, don't do sanic-side response chunk encoding, leave that to the ASGI-response-transport Don't set content-length when using chunked-encoding in ASGI mode, this is incompatible with ASGI Chunked Transport-Encoding. --- sanic/app.py | 2 ++ sanic/asgi.py | 19 +++++++++++++++---- sanic/response.py | 2 ++ tests/test_response.py | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 3f48f1fd..e8cb5c17 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -1454,6 +1454,8 @@ class Sanic: asgi_app = await ASGIApp.create(self, scope, receive, send) await asgi_app() + _asgi_single_callable = True # We conform to ASGI 3.0 single-callable + # -------------------------------------------------------------------- # # Configuration # -------------------------------------------------------------------- # diff --git a/sanic/asgi.py b/sanic/asgi.py index 2a3c4540..8e13342c 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -312,13 +312,17 @@ class ASGIApp: callback = None if self.ws else self.stream_callback await handler(self.request, None, callback) + _asgi_single_callable = True # We conform to ASGI 3.0 single-callable + async def stream_callback(self, response: HTTPResponse) -> None: """ Write the response. """ headers: List[Tuple[bytes, bytes]] = [] cookies: Dict[str, str] = {} + content_length: List[str] = [] try: + content_length = response.headers.popall("content-length", []) cookies = { v.key: v for _, v in list( @@ -351,10 +355,17 @@ class ASGIApp: ] response.asgi = True - - if "content-length" not in response.headers and not isinstance( - response, StreamingHTTPResponse - ): + is_streaming = isinstance(response, StreamingHTTPResponse) + if is_streaming and getattr(response, "chunked", False): + # disable sanic chunking, this is done at the ASGI-server level + response.chunked = False + # content-length header is removed to signal to the ASGI-server + # to use automatic-chunking if it supports it + elif len(content_length) > 0: + headers += [ + (b"content-length", str(content_length[0]).encode("latin-1")) + ] + elif not is_streaming: headers += [ (b"content-length", str(len(response.body)).encode("latin-1")) ] diff --git a/sanic/response.py b/sanic/response.py index 1f7b12de..9841bb2d 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -100,6 +100,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): """ data = self._encode_body(data) + # `chunked` will always be False in ASGI-mode, even if the underlying + # ASGI Transport implements Chunked transport. That does it itself. if self.chunked: await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) else: diff --git a/tests/test_response.py b/tests/test_response.py index 6e2f2a9a..847246d3 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -238,7 +238,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app): @pytest.mark.asyncio async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): request, response = await streaming_app.asgi_client.get("/") - assert response.text == "4\r\nfoo,\r\n3\r\nbar\r\n0\r\n\r\n" + assert response.text == "foo,bar" def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): From 75994cd915973c161fd9cb0f3f8c140d2f4a003e Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 5 Nov 2020 08:49:55 +0200 Subject: [PATCH 10/15] Fixes for linting and type hints --- sanic/asgi.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sanic/asgi.py b/sanic/asgi.py index 8e13342c..f6bb27bf 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -314,7 +314,9 @@ class ASGIApp: _asgi_single_callable = True # We conform to ASGI 3.0 single-callable - async def stream_callback(self, response: HTTPResponse) -> None: + async def stream_callback( + self, response: Union[HTTPResponse, StreamingHTTPResponse] + ) -> None: """ Write the response. """ @@ -358,7 +360,7 @@ class ASGIApp: is_streaming = isinstance(response, StreamingHTTPResponse) if is_streaming and getattr(response, "chunked", False): # disable sanic chunking, this is done at the ASGI-server level - response.chunked = False + setattr(response, "chunked", False) # content-length header is removed to signal to the ASGI-server # to use automatic-chunking if it supports it elif len(content_length) > 0: @@ -367,7 +369,10 @@ class ASGIApp: ] elif not is_streaming: headers += [ - (b"content-length", str(len(response.body)).encode("latin-1")) + ( + b"content-length", + str(len(getattr(response, "body", b""))).encode("latin-1"), + ) ] if "content-type" not in response.headers: From b4fe2c8a6b79cb840aa4f57dc315f908363f7c3e Mon Sep 17 00:00:00 2001 From: 7 Date: Thu, 5 Nov 2020 22:32:04 -0800 Subject: [PATCH 11/15] bump up aiofile version constraint (#1967) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3a44fb5a..6fc0ecbb 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ requirements = [ "httptools>=0.0.10", uvloop, ujson, - "aiofiles>=0.3.0", + "aiofiles>=0.6.0", "websockets>=8.1,<9.0", "multidict==5.0.0", "httpx==0.15.4", From d0f0e73e96fc7bd22e49c90cff75e33cf8befbd9 Mon Sep 17 00:00:00 2001 From: allandialpad Date: Mon, 16 Nov 2020 15:08:27 -0500 Subject: [PATCH 12/15] remove upper bound for multidict --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6fc0ecbb..ba97188c 100644 --- a/setup.py +++ b/setup.py @@ -80,13 +80,13 @@ requirements = [ ujson, "aiofiles>=0.6.0", "websockets>=8.1,<9.0", - "multidict==5.0.0", + "multidict>=5.0,<6.0", "httpx==0.15.4", ] tests_require = [ "pytest==5.2.1", - "multidict==5.0.0", + "multidict>=5.0,<6.0", "gunicorn", "pytest-cov", "httpcore==0.3.0", From 63567c2ae45086989aef5b33c969464c0de2442e Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Thu, 19 Nov 2020 04:18:25 -0500 Subject: [PATCH 13/15] Add py.typed file (#1970) --- changelogs/1970.misc.rst | 1 + sanic/py.typed | 0 setup.py | 1 + 3 files changed, 2 insertions(+) create mode 100644 changelogs/1970.misc.rst create mode 100644 sanic/py.typed diff --git a/changelogs/1970.misc.rst b/changelogs/1970.misc.rst new file mode 100644 index 00000000..725ecd79 --- /dev/null +++ b/changelogs/1970.misc.rst @@ -0,0 +1 @@ +Adds py.typed file to expose type information to other packages. diff --git a/sanic/py.typed b/sanic/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/setup.py b/setup.py index ba97188c..f4677215 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ setup_kwargs = { ), "long_description": long_description, "packages": ["sanic"], + "package_data": {"sanic": ["py.typed"]}, "platforms": "any", "python_requires": ">=3.6", "classifiers": [ From bde0428d0c07396ed296c89d992ff52a2e6ec5be Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Mon, 23 Nov 2020 02:02:33 +0200 Subject: [PATCH 14/15] Update README.rst (#1973) Change `.org` to `.com` for transition in Travis. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index cfdde631..941179a7 100644 --- a/README.rst +++ b/README.rst @@ -26,8 +26,8 @@ Sanic | Build fast. Run fast. :target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg :target: https://codecov.io/gh/huge-success/sanic -.. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master - :target: https://travis-ci.org/huge-success/sanic +.. |Build Status| image:: https://travis-ci.com/huge-success/sanic.svg?branch=master + :target: https://travis-ci.com/huge-success/sanic .. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true :target: https://ci.appveyor.com/project/huge-success/sanic .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest From 614be40438a87b31fd98de3978c95373c604d347 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 29 Nov 2020 23:26:12 +0200 Subject: [PATCH 15/15] Name endpoints at startup (#1972) * Name endpoints at startup * Beautify * Fix reformatting --- sanic/app.py | 18 ++++++------------ sanic/router.py | 24 ++++++++++++++++++++---- sanic/views.py | 1 + tests/conftest.py | 4 ++-- tests/test_app.py | 2 +- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index e8cb5c17..047879ca 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -68,7 +68,7 @@ class Sanic: self.name = name self.asgi = False - self.router = router or Router() + self.router = router or Router(self) self.request_class = request_class self.error_handler = error_handler or ErrorHandler() self.config = Config(load_env=load_env) @@ -900,7 +900,9 @@ class Sanic: name = None try: # Fetch handler from router - handler, args, kwargs, uri, name = self.router.get(request) + handler, args, kwargs, uri, name, endpoint = self.router.get( + request + ) # -------------------------------------------- # # Request Middleware @@ -922,16 +924,8 @@ class Sanic: "handler from the router" ) ) - else: - if not getattr(handler, "__blueprintname__", False): - request.endpoint = self._build_endpoint_name( - handler.__name__ - ) - else: - request.endpoint = self._build_endpoint_name( - getattr(handler, "__blueprintname__", ""), - handler.__name__, - ) + + request.endpoint = endpoint # Run response handler response = handler(request, *args, **kwargs) diff --git a/sanic/router.py b/sanic/router.py index a608f1a2..ffd98f15 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -11,7 +11,16 @@ from sanic.views import CompositionView Route = namedtuple( - "Route", ["handler", "methods", "pattern", "parameters", "name", "uri"] + "Route", + [ + "handler", + "methods", + "pattern", + "parameters", + "name", + "uri", + "endpoint", + ], ) Parameter = namedtuple("Parameter", ["name", "cast"]) @@ -79,7 +88,8 @@ class Router: routes_always_check = None parameter_pattern = re.compile(r"<(.+?)>") - def __init__(self): + def __init__(self, app): + self.app = app self.routes_all = {} self.routes_names = {} self.routes_static_files = {} @@ -299,11 +309,15 @@ class Router: handler_name = f"{bp_name}.{name or handler.__name__}" else: - handler_name = name or getattr(handler, "__name__", None) + handler_name = name or getattr( + handler, "__name__", handler.__class__.__name__ + ) if route: route = merge_route(route, methods, handler) else: + endpoint = self.app._build_endpoint_name(handler_name) + route = Route( handler=handler, methods=methods, @@ -311,6 +325,7 @@ class Router: parameters=parameters, name=handler_name, uri=uri, + endpoint=endpoint, ) self.routes_all[uri] = route @@ -449,7 +464,8 @@ class Router: route_handler = route.handler if hasattr(route_handler, "handlers"): route_handler = route_handler.handlers[method] - return route_handler, [], kwargs, route.uri, route.name + + return route_handler, [], kwargs, route.uri, route.name, route.endpoint def is_stream_handler(self, request): """Handler for request is stream or not. diff --git a/sanic/views.py b/sanic/views.py index 30d0abcb..97ca6222 100644 --- a/sanic/views.py +++ b/sanic/views.py @@ -90,6 +90,7 @@ class CompositionView: def __init__(self): self.handlers = {} + self.name = self.__class__.__name__ def add(self, methods, handler, stream=False): if stream: diff --git a/tests/conftest.py b/tests/conftest.py index 5c217cdd..45963863 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -95,10 +95,10 @@ class RouteStringGenerator: @pytest.fixture(scope="function") -def sanic_router(): +def sanic_router(app): # noinspection PyProtectedMember def _setup(route_details: tuple) -> (Router, tuple): - router = Router() + router = Router(app) added_router = [] for method, route in route_details: try: diff --git a/tests/test_app.py b/tests/test_app.py index c9c44c39..ad85c3c2 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -117,7 +117,7 @@ def test_app_route_raise_value_error(app): def test_app_handle_request_handler_is_none(app, monkeypatch): def mockreturn(*args, **kwargs): - return None, [], {}, "", "" + return None, [], {}, "", "", None # Not sure how to make app.router.get() return None, so use mock here. monkeypatch.setattr(app.router, "get", mockreturn)