Compare commits
	
		
			18 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 00493a6954 | ||
|   | c8d0c1bf28 | ||
|   | 6ea5c44566 | ||
|   | 1afea39cb2 | ||
|   | e4daf1ab21 | ||
|   | 469cb1663b | ||
|   | cdc5dd6b75 | ||
|   | 6fac60c6fe | ||
|   | 53b7412c01 | ||
|   | 65ba1942cc | ||
|   | 9adb6e8ec0 | ||
|   | ec35f5f2c8 | ||
|   | 9ae25e6744 | ||
|   | 758f10c513 | ||
|   | 140d27ef96 | ||
|   | 209840b771 | ||
|   | 20fd58b8d7 | ||
|   | a5a9658896 | 
| @@ -1,5 +1,4 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Sanic documentation build configuration file, created by | ||||
| # sphinx-quickstart on Sun Dec 25 18:07:21 2016. | ||||
| @@ -10,7 +9,6 @@ | ||||
| import os | ||||
| import sys | ||||
|  | ||||
|  | ||||
| # Add support for auto-doc | ||||
|  | ||||
|  | ||||
| @@ -19,8 +17,7 @@ import sys | ||||
| root_directory = os.path.dirname(os.getcwd()) | ||||
| sys.path.insert(0, root_directory) | ||||
|  | ||||
| import sanic | ||||
|  | ||||
| import sanic  # noqa: E402 | ||||
|  | ||||
| # -- General configuration ------------------------------------------------ | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,7 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ from random import randint | ||||
| from sanic import Sanic | ||||
| from sanic.response import text | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -25,5 +24,6 @@ def key_exist_handler(request): | ||||
|  | ||||
|     return text("num does not exist in request") | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     app.run(host="0.0.0.0", port=8000, debug=True) | ||||
|   | ||||
| @@ -1,11 +1,8 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| from functools import wraps | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from sanic import Blueprint, Sanic | ||||
| from sanic.response import text | ||||
|  | ||||
|  | ||||
| """ | ||||
| Demonstrates that blueprint request middleware are executed in the order they | ||||
| are added. And blueprint response middleware are executed in _reverse_ order. | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from sanic import Blueprint, Sanic | ||||
| from sanic.response import file, json | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
| blueprint = Blueprint("bp_example", url_prefix="/my_blueprint") | ||||
| blueprint2 = Blueprint("bp_example2", url_prefix="/my_blueprint2") | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from asyncio import sleep | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("DelayedResponseApp", strict_slashes=True) | ||||
| app.config.AUTO_EXTEND = False | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,6 @@ an external service. | ||||
| from sanic.exceptions import SanicException | ||||
| from sanic.handlers import ErrorHandler | ||||
|  | ||||
|  | ||||
| """ | ||||
| Imports and code relevant for our CustomHandler class | ||||
| (Ordinarily this would be in a separate file) | ||||
| @@ -39,7 +38,6 @@ server's error_handler to an instance of our CustomHandler | ||||
|  | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| handler = CustomHandler() | ||||
| app = Sanic("Example", error_handler=handler) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from sanic import Sanic, response, text | ||||
| from sanic.handlers import ErrorHandler | ||||
| from sanic.server.async_server import AsyncioServer | ||||
|  | ||||
|  | ||||
| HTTP_PORT = 9999 | ||||
| HTTPS_PORT = 8888 | ||||
|  | ||||
| @@ -36,9 +35,7 @@ def proxy(request, path): | ||||
|  | ||||
| @https.main_process_start | ||||
| async def start(app, _): | ||||
|     http_server = await http.create_server( | ||||
|         port=HTTP_PORT, return_asyncio_server=True | ||||
|     ) | ||||
|     http_server = await http.create_server(port=HTTP_PORT, return_asyncio_server=True) | ||||
|     app.add_task(runner(http, http_server)) | ||||
|     app.ctx.http_server = http_server | ||||
|     app.ctx.http = http | ||||
| @@ -69,5 +66,6 @@ async def runner(app: Sanic, app_server: AsyncioServer): | ||||
|         app.is_running = False | ||||
|         app.is_stopping = True | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     https.run(port=HTTPS_PORT, debug=True) | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import httpx | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
| sem = None | ||||
|   | ||||
| @@ -1,10 +1,8 @@ | ||||
| import logging | ||||
|  | ||||
| from contextvars import ContextVar | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import logging | ||||
| import socket | ||||
|  | ||||
| from os import getenv | ||||
| from platform import node | ||||
| from uuid import getnode as get_mac | ||||
| @@ -11,7 +10,6 @@ from sanic import Sanic | ||||
| from sanic.request import Request | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| log = logging.getLogger("logdna") | ||||
| log.setLevel(logging.INFO) | ||||
|  | ||||
| @@ -35,9 +33,7 @@ logdna_options = { | ||||
|     "mac": get_mac_address(), | ||||
| } | ||||
|  | ||||
| logdna_handler = LogDNAHandler( | ||||
|     getenv("LOGDNA_API_KEY"), options=logdna_options | ||||
| ) | ||||
| logdna_handler = LogDNAHandler(getenv("LOGDNA_API_KEY"), options=logdna_options) | ||||
|  | ||||
| logdna = logging.getLogger(__name__) | ||||
| logdna.setLevel(logging.INFO) | ||||
| @@ -48,7 +44,7 @@ app = Sanic("Example") | ||||
|  | ||||
| @app.middleware | ||||
| def log_request(request: Request): | ||||
|     logdna.info("I was Here with a new Request to URL: {}".format(request.url)) | ||||
|     logdna.info(f"I was Here with a new Request to URL: {request.url}") | ||||
|  | ||||
|  | ||||
| @app.route("/") | ||||
|   | ||||
| @@ -4,7 +4,6 @@ Modify header or status in response | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import logging | ||||
|  | ||||
| from sanic import Sanic, text | ||||
|  | ||||
|  | ||||
| logging_format = "[%(asctime)s] %(process)d-%(levelname)s " | ||||
| logging_format += "%(module)s::%(funcName)s():l%(lineno)d: " | ||||
| logging_format += "%(message)s" | ||||
|   | ||||
| @@ -11,7 +11,6 @@ Run with xdist params: | ||||
| import re | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from sanic_testing import SanicTestClient | ||||
| from sanic_testing.testing import PORT as PORT_BASE | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from sanic.response import stream, text | ||||
| from sanic.views import HTTPMethodView | ||||
| from sanic.views import stream as stream_decorator | ||||
|  | ||||
|  | ||||
| bp = Blueprint("bp_example") | ||||
| app = Sanic("Example") | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from sanic import Sanic, response | ||||
| from sanic.config import Config | ||||
| from sanic.exceptions import RequestTimeout | ||||
|  | ||||
|  | ||||
| Config.REQUEST_TIMEOUT = 1 | ||||
| app = Sanic("Example") | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,6 @@ from sanic import Sanic | ||||
| from sanic.exceptions import SanicException | ||||
| from sanic.handlers import ErrorHandler | ||||
|  | ||||
|  | ||||
| rollbar.init(getenv("ROLLBAR_API_KEY")) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,6 @@ from pathlib import Path | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -43,9 +42,7 @@ async def handler_file(request): | ||||
|  | ||||
| @app.route("/file_stream") | ||||
| async def handler_file_stream(request): | ||||
|     return await response.file_stream( | ||||
|         Path("../") / "setup.py", chunk_size=1024 | ||||
|     ) | ||||
|     return await response.file_stream(Path("../") / "setup.py", chunk_size=1024) | ||||
|  | ||||
|  | ||||
| @app.post("/stream", stream=True) | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import uvloop | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import asyncio | ||||
|  | ||||
| from signal import SIGINT, signal | ||||
|  | ||||
| import uvloop | ||||
| @@ -7,7 +6,6 @@ import uvloop | ||||
| from sanic import Sanic, response | ||||
| from sanic.server import AsyncioServer | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -35,11 +33,10 @@ async def after_server_stop(app, loop): | ||||
| async def test(request): | ||||
|     return response.json({"answer": "42"}) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     asyncio.set_event_loop(uvloop.new_event_loop()) | ||||
|     serv_coro = app.create_server( | ||||
|         host="0.0.0.0", port=8000, return_asyncio_server=True | ||||
|     ) | ||||
|     serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True) | ||||
|     loop = asyncio.get_event_loop() | ||||
|     serv_task = asyncio.ensure_future(serv_coro, loop=loop) | ||||
|     signal(SIGINT, lambda s, f: loop.stop()) | ||||
|   | ||||
| @@ -6,7 +6,6 @@ from sentry_sdk.integrations.sanic import SanicIntegration | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| sentry_init( | ||||
|     dsn=getenv("SENTRY_DSN"), | ||||
|     integrations=[SanicIntegration()], | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from sanic import Sanic | ||||
| from sanic.response import text | ||||
| from sanic.views import HTTPMethodView | ||||
|  | ||||
|  | ||||
| app = Sanic("some_name") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
| app.static("/", "./static") | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from sanic import Sanic | ||||
| from sanic import response as res | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from sanic import Sanic, response | ||||
| from sanic.exceptions import ServerError | ||||
| from sanic.log import logger as log | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -20,7 +19,7 @@ def test_sync(request): | ||||
|  | ||||
| @app.route("/dynamic/<name>/<i:int>") | ||||
| def test_params(request, name, i): | ||||
|     return response.text("yeehaww {} {}".format(name, i)) | ||||
|     return response.text(f"yeehaww {name} {i}") | ||||
|  | ||||
|  | ||||
| @app.route("/exception") | ||||
| @@ -43,9 +42,7 @@ async def test_file(request): | ||||
|  | ||||
| @app.route("/file_stream") | ||||
| async def test_file_stream(request): | ||||
|     return await response.file_stream( | ||||
|         os.path.abspath("setup.py"), chunk_size=1024 | ||||
|     ) | ||||
|     return await response.file_stream(os.path.abspath("setup.py"), chunk_size=1024) | ||||
|  | ||||
|  | ||||
| # ----------------------------------------------- # | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -14,7 +13,7 @@ async def index(request): | ||||
|  | ||||
| @app.route("/posts/<post_id>") | ||||
| async def post_handler(request, post_id): | ||||
|     return response.text("Post - {}".format(post_id)) | ||||
|     return response.text(f"Post - {post_id}") | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from sanic import Sanic | ||||
| from sanic.blueprints import Blueprint | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| app = Sanic(name="blue-print-group-version-example") | ||||
|  | ||||
| bp1 = Blueprint(name="ultron", url_prefix="/ultron") | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from sanic import Sanic, response | ||||
| from sanic.blueprints import Blueprint | ||||
|  | ||||
|  | ||||
| # Usage | ||||
| # curl -H "Host: example.com" localhost:8000 | ||||
| # curl -H "Host: sub.example.com" localhost:8000 | ||||
| @@ -12,9 +11,7 @@ app = Sanic("Example") | ||||
| bp = Blueprint("bp", host="bp.example.com") | ||||
|  | ||||
|  | ||||
| @app.route( | ||||
|     "/", host=["example.com", "somethingelse.com", "therestofyourdomains.com"] | ||||
| ) | ||||
| @app.route("/", host=["example.com", "somethingelse.com", "therestofyourdomains.com"]) | ||||
| async def hello_0(request): | ||||
|     return response.text("Some defaults") | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from sanic import Sanic | ||||
| from sanic.response import redirect | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -7,14 +7,10 @@ from emoji import EMOJI | ||||
| COLUMN_PATTERN = re.compile(r"---:1\s*(.*?)\s*:--:1\s*(.*?)\s*:---", re.DOTALL) | ||||
| PYTHON_HIGHLIGHT_PATTERN = re.compile(r"```python\{+.*?\}", re.DOTALL) | ||||
| BASH_HIGHLIGHT_PATTERN = re.compile(r"```bash\{+.*?\}", re.DOTALL) | ||||
| NOTIFICATION_PATTERN = re.compile( | ||||
|     r":::\s*(\w+)\s*(.*?)\n([\s\S]*?):::", re.MULTILINE | ||||
| ) | ||||
| NOTIFICATION_PATTERN = re.compile(r":::\s*(\w+)\s*(.*?)\n([\s\S]*?):::", re.MULTILINE) | ||||
| EMOJI_PATTERN = re.compile(r":(\w+):") | ||||
| CURRENT_DIR = Path(__file__).parent | ||||
| SOURCE_DIR = ( | ||||
|     CURRENT_DIR.parent.parent.parent.parent / "sanic-guide" / "src" / "en" | ||||
| ) | ||||
| SOURCE_DIR = CURRENT_DIR.parent.parent.parent.parent / "sanic-guide" / "src" / "en" | ||||
|  | ||||
|  | ||||
| def convert_columns(content: str): | ||||
|   | ||||
| @@ -22,7 +22,6 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: | ||||
| .cs { color: #a2a2a2; font-style: italic } /* Comment.Special */ | ||||
| .gd { color: #777777 } /* Generic.Deleted */ | ||||
| .ge { color: #777777 } /* Generic.Emph */ | ||||
| .ges { color: #777777 } /* Generic.EmphStrong */ | ||||
| .gr { color: #777777 } /* Generic.Error */ | ||||
| .gh { color: #777777 } /* Generic.Heading */ | ||||
| .gi { color: #777777 } /* Generic.Inserted */ | ||||
|   | ||||
							
								
								
									
										5
									
								
								guide/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								guide/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| [tool.ruff] | ||||
| extend = "../pyproject.toml" | ||||
|  | ||||
| [tool.ruff.isort] | ||||
| known-first-party = ["webapp"] | ||||
| @@ -1,8 +1,3 @@ | ||||
| sanic>=23.6.* | ||||
| sanic-ext>=23.6.* | ||||
| msgspec | ||||
| python-frontmatter | ||||
| pygments | ||||
| docstring-parser | ||||
| libsass | ||||
| mistune | ||||
|   | ||||
| @@ -1,11 +1,3 @@ | ||||
| """Sanic  User Guide | ||||
|  | ||||
| https://sanic.dev | ||||
|  | ||||
| Built using the SHH stack: | ||||
| - Sanic | ||||
| - html5tagger | ||||
| - HTMX""" | ||||
| from pathlib import Path | ||||
|  | ||||
| from webapp.worker.factory import create_app | ||||
|   | ||||
| @@ -13,9 +13,7 @@ def do_footer(builder: Builder, request: Request) -> None: | ||||
|  | ||||
|  | ||||
| def _pagination(request: Request) -> Builder: | ||||
|     return E.div( | ||||
|         _pagination_left(request), _pagination_right(request), class_="level" | ||||
|     ) | ||||
|     return E.div(_pagination_left(request), _pagination_right(request), class_="level") | ||||
|  | ||||
|  | ||||
| def _pagination_left(request: Request) -> Builder: | ||||
| @@ -64,9 +62,7 @@ def _content() -> Builder: | ||||
|             href="https://github.com/sanic-org/sanic/blob/master/LICENSE", | ||||
|             target="_blank", | ||||
|             rel="nofollow noopener noreferrer", | ||||
|         ).br()( | ||||
|             E.small(f"Copyright © 2018-{year} Sanic Community Organization") | ||||
|         ), | ||||
|         ).br()(E.small(f"Copyright © 2018-{year} Sanic Community Organization")), | ||||
|     ) | ||||
|     return E.div( | ||||
|         inner, | ||||
|   | ||||
| @@ -1,17 +1,14 @@ | ||||
| from webapp.display.layouts.models import MenuItem | ||||
|  | ||||
| from html5tagger import Builder, E  # type: ignore | ||||
| from sanic import Request | ||||
|  | ||||
| from webapp.display.layouts.models import MenuItem | ||||
|  | ||||
|  | ||||
| def do_navbar(builder: Builder, request: Request) -> None: | ||||
|     navbar_items = [ | ||||
|         _render_navbar_item(item, request) | ||||
|         for item in request.app.config.NAVBAR | ||||
|         _render_navbar_item(item, request) for item in request.app.config.NAVBAR | ||||
|     ] | ||||
|     container = E.div( | ||||
|         _search_form(request), *navbar_items, class_="navbar-end" | ||||
|     ) | ||||
|     container = E.div(_search_form(request), *navbar_items, class_="navbar-end") | ||||
|  | ||||
|     builder.nav( | ||||
|         E.div(container, class_="navbar-menu"), | ||||
| @@ -46,10 +43,7 @@ def _render_navbar_item(item: MenuItem, request: Request) -> Builder: | ||||
|         return E.div( | ||||
|             E.a(item.label, class_="navbar-link"), | ||||
|             E.div( | ||||
|                 *( | ||||
|                     _render_navbar_item(subitem, request) | ||||
|                     for subitem in item.items | ||||
|                 ), | ||||
|                 *(_render_navbar_item(subitem, request) for subitem in item.items), | ||||
|                 class_="navbar-dropdown", | ||||
|             ), | ||||
|             class_="navbar-item has-dropdown is-hoverable", | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| from webapp.display.layouts.models import MenuItem | ||||
| from webapp.display.text import slugify | ||||
|  | ||||
| from html5tagger import Builder, E  # type: ignore | ||||
| from sanic import Request | ||||
|  | ||||
| from webapp.display.layouts.models import MenuItem | ||||
| from webapp.display.text import slugify | ||||
|  | ||||
|  | ||||
| def do_sidebar(builder: Builder, request: Request) -> None: | ||||
|     builder.a(class_="burger")(E.span().span().span().span()) | ||||
| @@ -15,9 +15,7 @@ def _menu_items(request: Request) -> list[Builder]: | ||||
|         _sanic_logo(request), | ||||
|         *_sidebar_items(request), | ||||
|         E.hr(), | ||||
|         E.p("Current with version ").strong( | ||||
|             request.app.config.GENERAL.current_version | ||||
|         ), | ||||
|         E.p("Current with version ").strong(request.app.config.GENERAL.current_version), | ||||
|         E.hr(), | ||||
|         E.p("Want more? ").a( | ||||
|             "sanicbook.com", href="https://sanicbook.com", target="_blank" | ||||
| @@ -73,9 +71,7 @@ def _single_sidebar_item(item: MenuItem, request: Request) -> Builder: | ||||
|     kwargs = {} | ||||
|     classes: list[str] = [] | ||||
|     li_classes = "menu-item" | ||||
|     _, page, _ = request.app.ctx.get_page( | ||||
|         request.ctx.language, item.path or "" | ||||
|     ) | ||||
|     _, page, _ = request.app.ctx.get_page(request.ctx.language, item.path or "") | ||||
|     if request.path == path: | ||||
|         classes.append("is-active") | ||||
|     if item.href: | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| from contextlib import contextmanager | ||||
| from typing import Generator | ||||
|  | ||||
| from sanic import Request | ||||
|  | ||||
| from webapp.display.layouts.elements.footer import do_footer | ||||
| from webapp.display.layouts.elements.navbar import do_navbar | ||||
| from webapp.display.layouts.elements.sidebar import do_sidebar | ||||
|  | ||||
| from sanic import Request | ||||
|  | ||||
| from .base import BaseLayout | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import re | ||||
| from textwrap import dedent | ||||
|  | ||||
| from html5tagger import HTML, Builder, E  # type: ignore | ||||
| from mistune import HTMLRenderer, create_markdown, escape | ||||
| from mistune.directives import RSTDirective, TableOfContents | ||||
| from mistune.util import safe_entity | ||||
| @@ -8,8 +9,6 @@ from pygments import highlight | ||||
| from pygments.formatters import html | ||||
| from pygments.lexers import get_lexer_by_name | ||||
|  | ||||
| from html5tagger import HTML, Builder, E  # type: ignore | ||||
|  | ||||
| from .code_style import SanicCodeStyle | ||||
| from .plugins.attrs import Attributes | ||||
| from .plugins.columns import Column | ||||
| @@ -37,9 +36,9 @@ class DocsRenderer(HTMLRenderer): | ||||
|                     class_="code-block__copy", | ||||
|                     onclick="copyCode(this)", | ||||
|                 ): | ||||
|                     builder.div( | ||||
|                         class_="code-block__rectangle code-block__filled" | ||||
|                     ).div(class_="code-block__rectangle code-block__outlined") | ||||
|                     builder.div(class_="code-block__rectangle code-block__filled").div( | ||||
|                         class_="code-block__rectangle code-block__outlined" | ||||
|                     ) | ||||
|             else: | ||||
|                 builder.pre(E.code(escape(code))) | ||||
|         return str(builder) | ||||
| @@ -47,9 +46,7 @@ class DocsRenderer(HTMLRenderer): | ||||
|     def heading(self, text: str, level: int, **attrs) -> str: | ||||
|         ident = slugify(text) | ||||
|         if level > 1: | ||||
|             text += self._make_tag( | ||||
|                 "a", {"href": f"#{ident}", "class": "anchor"}, "#" | ||||
|             ) | ||||
|             text += self._make_tag("a", {"href": f"#{ident}", "class": "anchor"}, "#") | ||||
|         return self._make_tag( | ||||
|             f"h{level}", {"id": ident, "class": f"is-size-{level}"}, text | ||||
|         ) | ||||
| @@ -93,9 +90,7 @@ class DocsRenderer(HTMLRenderer): | ||||
|     def _make_tag( | ||||
|         self, tag: str, attributes: dict[str, str], text: str | None = None | ||||
|     ) -> str: | ||||
|         attrs = " ".join( | ||||
|             f'{key}="{value}"' for key, value in attributes.items() | ||||
|         ) | ||||
|         attrs = " ".join(f'{key}="{value}"' for key, value in attributes.items()) | ||||
|         if text is None: | ||||
|             return f"<{tag} {attrs} />" | ||||
|         return f"<{tag} {attrs}>{text}</{tag}>" | ||||
|   | ||||
| @@ -10,7 +10,6 @@ from html import escape | ||||
| from docstring_parser import Docstring, DocstringParam, DocstringRaises | ||||
| from docstring_parser import parse as parse_docstring | ||||
| from docstring_parser.common import DocstringExample | ||||
|  | ||||
| from html5tagger import HTML, Builder, E  # type: ignore | ||||
|  | ||||
| from ..markdown import render_markdown, slugify | ||||
| @@ -120,9 +119,7 @@ def _extract_docobjects(package_name: str) -> dict[str, DocObject]: | ||||
|     docstrings = {} | ||||
|     package = importlib.import_module(package_name) | ||||
|  | ||||
|     for _, name, _ in pkgutil.walk_packages( | ||||
|         package.__path__, package_name + "." | ||||
|     ): | ||||
|     for _, name, _ in pkgutil.walk_packages(package.__path__, package_name + "."): | ||||
|         module = importlib.import_module(name) | ||||
|         for obj_name, obj in inspect.getmembers(module): | ||||
|             if ( | ||||
| @@ -156,9 +153,7 @@ def _docobject_to_html( | ||||
| ) -> None: | ||||
|     anchor_id = slugify(docobject.full_name.replace(".", "-")) | ||||
|     anchor = E.a("#", class_="anchor", href=f"#{anchor_id}") | ||||
|     class_name, heading = _define_heading_and_class( | ||||
|         docobject, anchor, as_method | ||||
|     ) | ||||
|     class_name, heading = _define_heading_and_class(docobject, anchor, as_method) | ||||
|  | ||||
|     with builder.div(class_=class_name): | ||||
|         builder(heading) | ||||
| @@ -212,9 +207,7 @@ def _docobject_to_html( | ||||
|  | ||||
|         if docobject.docstring.params: | ||||
|             with builder.div(class_="box mt-5"): | ||||
|                 builder.h5( | ||||
|                     "Parameters", class_="is-size-5 has-text-weight-bold" | ||||
|                 ) | ||||
|                 builder.h5("Parameters", class_="is-size-5 has-text-weight-bold") | ||||
|                 _render_params(builder, docobject.docstring.params) | ||||
|  | ||||
|         if docobject.docstring.returns: | ||||
| @@ -239,9 +232,7 @@ def _signature_to_html( | ||||
|     parts = [] | ||||
|     parts.append("<span class='function-signature'>") | ||||
|     for decorator in decorators: | ||||
|         parts.append( | ||||
|             f"<span class='function-decorator'>@{decorator}</span><br>" | ||||
|         ) | ||||
|         parts.append(f"<span class='function-decorator'>@{decorator}</span><br>") | ||||
|     parts.append( | ||||
|         f"<span class='is-italic'>{object_type}</span> " | ||||
|         f"<span class='has-text-weight-bold'>{name}</span>(" | ||||
| @@ -255,9 +246,7 @@ def _signature_to_html( | ||||
|         annotation = "" | ||||
|         if param.annotation != inspect.Parameter.empty: | ||||
|             annotation = escape(str(param.annotation)) | ||||
|             parts.append( | ||||
|                 f": <span class='param-annotation'>{annotation}</span>" | ||||
|             ) | ||||
|             parts.append(f": <span class='param-annotation'>{annotation}</span>") | ||||
|         if param.default != inspect.Parameter.empty: | ||||
|             default = escape(str(param.default)) | ||||
|             if annotation == "str": | ||||
| @@ -268,9 +257,7 @@ def _signature_to_html( | ||||
|     parts.append(")") | ||||
|     if signature.return_annotation != inspect.Signature.empty: | ||||
|         return_annotation = escape(str(signature.return_annotation)) | ||||
|         parts.append( | ||||
|             f": -> <span class='return-annotation'>{return_annotation}</span>" | ||||
|         ) | ||||
|         parts.append(f": -> <span class='return-annotation'>{return_annotation}</span>") | ||||
|     parts.append("</span>") | ||||
|     return "".join(parts) | ||||
|  | ||||
| @@ -318,10 +305,7 @@ def _render_params(builder: Builder, params: list[DocstringParam]) -> None: | ||||
|             builder.dd( | ||||
|                 HTML( | ||||
|                     render_markdown( | ||||
|                         param.description | ||||
|                         or param.arg_name | ||||
|                         or param.type_name | ||||
|                         or "" | ||||
|                         param.description or param.arg_name or param.type_name or "" | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
| @@ -334,11 +318,7 @@ def _render_raises(builder: Builder, raises: list[DocstringRaises]) -> None: | ||||
|             with builder.dl(class_="mt-2"): | ||||
|                 builder.dt(raise_.type_name, class_="is-family-monospace") | ||||
|                 builder.dd( | ||||
|                     HTML( | ||||
|                         render_markdown( | ||||
|                             raise_.description or raise_.type_name or "" | ||||
|                         ) | ||||
|                     ) | ||||
|                     HTML(render_markdown(raise_.description or raise_.type_name or "")) | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| @@ -354,11 +334,7 @@ def _render_returns(builder: Builder, docobject: DocObject) -> None: | ||||
|         if not return_type or return_type == inspect.Signature.empty: | ||||
|             return_type = "N/A" | ||||
|  | ||||
|         term = ( | ||||
|             "Return" | ||||
|             if not docobject.docstring.returns.is_generator | ||||
|             else "Yields" | ||||
|         ) | ||||
|         term = "Return" if not docobject.docstring.returns.is_generator else "Yields" | ||||
|         builder.h5(term, class_="is-size-5 has-text-weight-bold") | ||||
|         with builder.dl(class_="mt-2"): | ||||
|             builder.dt(return_type, class_="is-family-monospace") | ||||
| @@ -373,17 +349,11 @@ def _render_returns(builder: Builder, docobject: DocObject) -> None: | ||||
|             ) | ||||
|  | ||||
|  | ||||
| def _render_examples( | ||||
|     builder: Builder, examples: list[DocstringExample] | ||||
| ) -> None: | ||||
| def _render_examples(builder: Builder, examples: list[DocstringExample]) -> None: | ||||
|     with builder.div(class_="box mt-5"): | ||||
|         builder.h5("Examples", class_="is-size-5 has-text-weight-bold") | ||||
|         for example in examples: | ||||
|             with builder.div(class_="mt-2"): | ||||
|                 builder( | ||||
|                     HTML( | ||||
|                         render_markdown( | ||||
|                             example.description or example.snippet or "" | ||||
|                         ) | ||||
|                     ) | ||||
|                     HTML(render_markdown(example.description or example.snippet or "")) | ||||
|                 ) | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from __future__ import annotations | ||||
|  | ||||
| from dataclasses import dataclass, field | ||||
| from pathlib import Path | ||||
| from typing import Type | ||||
|  | ||||
| from frontmatter import parse | ||||
|  | ||||
| @@ -12,10 +11,8 @@ from ..layouts.main import MainLayout | ||||
| from ..markdown import render_markdown | ||||
| from .docobject import organize_docobjects | ||||
|  | ||||
| _PAGE_CACHE: dict[ | ||||
|     str, dict[str, tuple[Page | None, Page | None, Page | None]] | ||||
| ] = {} | ||||
| _LAYOUTS_CACHE: dict[str, Type[BaseLayout]] = { | ||||
| _PAGE_CACHE: dict[str, dict[str, tuple[Page | None, Page | None, Page | None]]] = {} | ||||
| _LAYOUTS_CACHE: dict[str, type[BaseLayout]] = { | ||||
|     "home": HomeLayout, | ||||
|     "main": MainLayout, | ||||
| } | ||||
| @@ -43,7 +40,7 @@ class Page: | ||||
|  | ||||
|     DEFAULT_LANGUAGE = _DEFAULT | ||||
|  | ||||
|     def get_layout(self) -> Type[BaseLayout]: | ||||
|     def get_layout(self) -> type[BaseLayout]: | ||||
|         return _LAYOUTS_CACHE[self.meta.layout] | ||||
|  | ||||
|     @property | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from contextlib import contextmanager | ||||
| from typing import Type | ||||
|  | ||||
| from webapp.display.base import BaseRenderer | ||||
|  | ||||
| from html5tagger import HTML, Builder  # type: ignore | ||||
| from sanic import Request | ||||
|  | ||||
| from webapp.display.base import BaseRenderer | ||||
|  | ||||
| from ..layouts.base import BaseLayout | ||||
| from .page import Page | ||||
|  | ||||
| @@ -21,13 +20,9 @@ class PageRenderer(BaseRenderer): | ||||
|         self._body(request, builder, language, path) | ||||
|         return builder | ||||
|  | ||||
|     def _body( | ||||
|         self, request: Request, builder: Builder, language: str, path: str | ||||
|     ): | ||||
|     def _body(self, request: Request, builder: Builder, language: str, path: str): | ||||
|         prev_page, current_page, next_page = Page.get(language, path) | ||||
|         request.ctx.language = ( | ||||
|             Page.DEFAULT_LANGUAGE if language == "api" else language | ||||
|         ) | ||||
|         request.ctx.language = Page.DEFAULT_LANGUAGE if language == "api" else language | ||||
|         request.ctx.current_page = current_page | ||||
|         request.ctx.previous_page = prev_page | ||||
|         request.ctx.next_page = next_page | ||||
| @@ -39,9 +34,7 @@ class PageRenderer(BaseRenderer): | ||||
|  | ||||
|     @contextmanager | ||||
|     def _base(self, request: Request, builder: Builder, page: Page | None): | ||||
|         layout_type: Type[BaseLayout] = ( | ||||
|             page.get_layout() if page else BaseLayout | ||||
|         ) | ||||
|         layout_type: type[BaseLayout] = page.get_layout() if page else BaseLayout | ||||
|         layout = layout_type(builder) | ||||
|         with layout(request, builder.full): | ||||
|             yield | ||||
|   | ||||
| @@ -2,12 +2,11 @@ from re import Match | ||||
| from textwrap import dedent | ||||
| from typing import Any | ||||
|  | ||||
| from html5tagger import HTML, E | ||||
| from mistune.block_parser import BlockParser | ||||
| from mistune.core import BlockState | ||||
| from mistune.directives import DirectivePlugin | ||||
|  | ||||
| from html5tagger import HTML, E | ||||
|  | ||||
|  | ||||
| class Attributes(DirectivePlugin): | ||||
|     def __call__(self, directive, md): | ||||
| @@ -16,9 +15,7 @@ class Attributes(DirectivePlugin): | ||||
|         if md.renderer.NAME == "html": | ||||
|             md.renderer.register("attrs", self._render) | ||||
|  | ||||
|     def parse( | ||||
|         self, block: BlockParser, m: Match, state: BlockState | ||||
|     ) -> dict[str, Any]: | ||||
|     def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: | ||||
|         info = m.groupdict() | ||||
|         options = dict(self.parse_options(m)) | ||||
|         new_state = block.state_cls() | ||||
|   | ||||
| @@ -10,9 +10,7 @@ from mistune.markdown import Markdown | ||||
|  | ||||
|  | ||||
| class Column(DirectivePlugin): | ||||
|     def parse( | ||||
|         self, block: BlockParser, m: Match, state: BlockState | ||||
|     ) -> dict[str, Any]: | ||||
|     def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: | ||||
|         info = m.groupdict() | ||||
|  | ||||
|         new_state = block.state_cls() | ||||
| @@ -36,9 +34,7 @@ class Column(DirectivePlugin): | ||||
|  | ||||
|     def _render_column(self, renderer: HTMLRenderer, text: str, **attrs): | ||||
|         start = ( | ||||
|             '<div class="columns mt-3 is-multiline">\n' | ||||
|             if attrs.get("first") | ||||
|             else "" | ||||
|             '<div class="columns mt-3 is-multiline">\n' if attrs.get("first") else "" | ||||
|         ) | ||||
|         end = "</div>\n" if attrs.get("last") else "" | ||||
|         col = f'<div class="column is-half">{text}</div>\n' | ||||
|   | ||||
| @@ -16,16 +16,12 @@ class Hook(DirectivePlugin): | ||||
|             for type_ in ("column", "tab"): | ||||
|                 if token["type"] == type_: | ||||
|                     maybe_next = ( | ||||
|                         state.tokens[idx + 1] | ||||
|                         if idx + 1 < len(state.tokens) | ||||
|                         else None | ||||
|                         state.tokens[idx + 1] if idx + 1 < len(state.tokens) else None | ||||
|                     ) | ||||
|                     token.setdefault("attrs", {}) | ||||
|                     if prev and prev["type"] != type_: | ||||
|                         token["attrs"]["first"] = True | ||||
|                     if ( | ||||
|                         maybe_next and maybe_next["type"] != type_ | ||||
|                     ) or not maybe_next: | ||||
|                     if (maybe_next and maybe_next["type"] != type_) or not maybe_next: | ||||
|                         token["attrs"]["last"] = True | ||||
|  | ||||
|             prev = token | ||||
|   | ||||
| @@ -3,19 +3,16 @@ from re import Match | ||||
| from textwrap import dedent | ||||
| from typing import Any | ||||
|  | ||||
| from html5tagger import HTML, E | ||||
| from mistune import HTMLRenderer | ||||
| from mistune.block_parser import BlockParser | ||||
| from mistune.core import BlockState | ||||
| from mistune.directives import DirectivePlugin, RSTDirective | ||||
| from mistune.markdown import Markdown | ||||
|  | ||||
| from html5tagger import HTML, E | ||||
|  | ||||
|  | ||||
| class Mermaid(DirectivePlugin): | ||||
|     def parse( | ||||
|         self, block: BlockParser, m: Match, state: BlockState | ||||
|     ) -> dict[str, Any]: | ||||
|     def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: | ||||
|         info = m.groupdict() | ||||
|  | ||||
|         new_state = block.state_cls() | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from mistune.directives import Admonition | ||||
|  | ||||
| from html5tagger import HTML, E | ||||
| from mistune.directives import Admonition | ||||
|  | ||||
|  | ||||
| class Notification(Admonition): | ||||
| @@ -20,12 +19,8 @@ class Notification(Admonition): | ||||
|  | ||||
|         if md.renderer.NAME == "html": | ||||
|             md.renderer.register("admonition", self._render_admonition) | ||||
|             md.renderer.register( | ||||
|                 "admonition_title", self._render_admonition_title | ||||
|             ) | ||||
|             md.renderer.register( | ||||
|                 "admonition_content", self._render_admonition_content | ||||
|             ) | ||||
|             md.renderer.register("admonition_title", self._render_admonition_title) | ||||
|             md.renderer.register("admonition_content", self._render_admonition_content) | ||||
|  | ||||
|     def _render_admonition(self, _, text, name, **attrs) -> str: | ||||
|         return str( | ||||
|   | ||||
| @@ -10,9 +10,7 @@ from mistune.markdown import Markdown | ||||
|  | ||||
|  | ||||
| class Tabs(DirectivePlugin): | ||||
|     def parse( | ||||
|         self, block: BlockParser, m: Match, state: BlockState | ||||
|     ) -> dict[str, Any]: | ||||
|     def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: | ||||
|         info = m.groupdict() | ||||
|  | ||||
|         new_state = block.state_cls() | ||||
| @@ -41,9 +39,7 @@ class Tabs(DirectivePlugin): | ||||
|     def _render_tab(self, renderer: HTMLRenderer, text: str, **attrs): | ||||
|         start = '<div class="tabs mt-6"><ul>\n' if attrs.get("first") else "" | ||||
|         end = ( | ||||
|             '</ul></div><div class="tab-display"></div>\n' | ||||
|             if attrs.get("last") | ||||
|             else "" | ||||
|             '</ul></div><div class="tab-display"></div>\n' if attrs.get("last") else "" | ||||
|         ) | ||||
|         content = f'<div class="tab-content">{text}</div>\n' | ||||
|         tab = f'<li><a>{attrs["title"]}</a>{content}</li>\n' | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| from contextlib import contextmanager | ||||
| from urllib.parse import unquote | ||||
|  | ||||
| from webapp.display.search.search import Searcher | ||||
|  | ||||
| from html5tagger import Builder, E  # type: ignore | ||||
| from sanic import Request | ||||
|  | ||||
| from webapp.display.search.search import Searcher | ||||
|  | ||||
| from ..base import BaseRenderer | ||||
| from ..layouts.main import MainLayout | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from pathlib import Path | ||||
| from typing import ClassVar | ||||
|  | ||||
| from msgspec import Struct | ||||
|  | ||||
| from webapp.display.page import Page | ||||
|  | ||||
|  | ||||
| @@ -91,9 +92,7 @@ def _inverse_document_frequency(docs: list[Document]) -> dict[str, float]: | ||||
|     return {word: num_docs / count for word, count in word_count.items()} | ||||
|  | ||||
|  | ||||
| def _tf_idf_vector( | ||||
|     document: Document, idf: dict[str, float] | ||||
| ) -> dict[str, float]: | ||||
| def _tf_idf_vector(document: Document, idf: dict[str, float]) -> dict[str, float]: | ||||
|     """Calculate the TF-IDF vector for a document.""" | ||||
|     return { | ||||
|         word: tf * idf[word] | ||||
| @@ -102,9 +101,7 @@ def _tf_idf_vector( | ||||
|     } | ||||
|  | ||||
|  | ||||
| def _cosine_similarity( | ||||
|     vec1: dict[str, float], vec2: dict[str, float] | ||||
| ) -> float: | ||||
| def _cosine_similarity(vec1: dict[str, float], vec2: dict[str, float]) -> float: | ||||
|     """Calculate the cosine similarity between two vectors.""" | ||||
|     if not vec1 or not vec2: | ||||
|         return 0.0 | ||||
| @@ -126,9 +123,7 @@ def _search( | ||||
|     tf_idf_query = _tf_idf_vector( | ||||
|         Document(page=dummy_page, language=language).process(stemmer), idf | ||||
|     ) | ||||
|     similarities = [ | ||||
|         _cosine_similarity(tf_idf_query, vector) for vector in vectors | ||||
|     ] | ||||
|     similarities = [_cosine_similarity(tf_idf_query, vector) for vector in vectors] | ||||
|     return [ | ||||
|         (similarity, document) | ||||
|         for similarity, document in sorted( | ||||
| @@ -155,16 +150,13 @@ class Searcher: | ||||
|         } | ||||
|         self._vectors = { | ||||
|             language: [ | ||||
|                 _tf_idf_vector(document, self._idf[language]) | ||||
|                 for document in documents | ||||
|                 _tf_idf_vector(document, self._idf[language]) for document in documents | ||||
|             ] | ||||
|             for language, documents in self._documents.items() | ||||
|         } | ||||
|         self._stemmer = stemmer | ||||
|  | ||||
|     def search( | ||||
|         self, query: str, language: str | ||||
|     ) -> list[tuple[float, Document]]: | ||||
|     def search(self, query: str, language: str) -> list[tuple[float, Document]]: | ||||
|         return _search( | ||||
|             query, | ||||
|             language, | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| # from urllib.parse import unquote | ||||
|  | ||||
| from sanic import Blueprint, Request, Sanic, html | ||||
|  | ||||
| from webapp.display.page import Page | ||||
| from webapp.display.search.renderer import SearchRenderer | ||||
| from webapp.display.search.search import Document, Searcher, Stemmer | ||||
|  | ||||
| from sanic import Blueprint, Request, Sanic, html | ||||
|  | ||||
| bp = Blueprint("search", url_prefix="/<language>/search") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from pathlib import Path | ||||
|  | ||||
| from msgspec import yaml | ||||
|  | ||||
| from webapp.display.layouts.models import GeneralConfig, MenuItem | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| from pathlib import Path | ||||
|  | ||||
| from sanic import Request, Sanic, html, redirect | ||||
|  | ||||
| from webapp.display.layouts.models import MenuItem | ||||
| from webapp.display.page import Page, PageRenderer | ||||
| from webapp.endpoint.view import bp | ||||
| @@ -7,8 +9,6 @@ from webapp.worker.config import load_config, load_menu | ||||
| from webapp.worker.reload import setup_livereload | ||||
| from webapp.worker.style import setup_style | ||||
|  | ||||
| from sanic import Request, Sanic, html, redirect | ||||
|  | ||||
|  | ||||
| def _compile_sidebar_order(items: list[MenuItem]) -> list[str]: | ||||
|     order = [] | ||||
| @@ -28,13 +28,9 @@ def create_app(root: Path) -> Sanic: | ||||
|     app.config.STYLE_DIR = root / "style" | ||||
|     app.config.NODE_MODULES_DIR = root / "node_modules" | ||||
|     app.config.LANGUAGES = ["en"] | ||||
|     app.config.SIDEBAR = load_menu( | ||||
|         app.config.CONFIG_DIR / "en" / "sidebar.yaml" | ||||
|     ) | ||||
|     app.config.SIDEBAR = load_menu(app.config.CONFIG_DIR / "en" / "sidebar.yaml") | ||||
|     app.config.NAVBAR = load_menu(app.config.CONFIG_DIR / "en" / "navbar.yaml") | ||||
|     app.config.GENERAL = load_config( | ||||
|         app.config.CONFIG_DIR / "en" / "general.yaml" | ||||
|     ) | ||||
|     app.config.GENERAL = load_config(app.config.CONFIG_DIR / "en" / "general.yaml") | ||||
|  | ||||
|     setup_livereload(app) | ||||
|     setup_style(app) | ||||
| @@ -66,8 +62,6 @@ def create_app(root: Path) -> Sanic: | ||||
|  | ||||
|     @app.on_request | ||||
|     async def set_language(request: Request): | ||||
|         request.ctx.language = request.match_info.get( | ||||
|             "language", Page.DEFAULT_LANGUAGE | ||||
|         ) | ||||
|         request.ctx.language = request.match_info.get("language", Page.DEFAULT_LANGUAGE) | ||||
|  | ||||
|     return app | ||||
|   | ||||
| @@ -5,7 +5,6 @@ from queue import Empty, Queue | ||||
| from typing import Any | ||||
|  | ||||
| import ujson | ||||
|  | ||||
| from sanic import Request, Sanic, Websocket | ||||
|  | ||||
|  | ||||
| @@ -54,16 +53,12 @@ class Livereload: | ||||
|         "serverName": SERVER_NAME, | ||||
|     } | ||||
|  | ||||
|     def __init__( | ||||
|         self, reload_queue: Queue, debug: bool, state: dict[str, Any] | ||||
|     ): | ||||
|     def __init__(self, reload_queue: Queue, debug: bool, state: dict[str, Any]): | ||||
|         self.reload_queue = reload_queue | ||||
|         self.app = Sanic(self.SERVER_NAME) | ||||
|         self.debug = debug | ||||
|         self.state = state | ||||
|         self.app.static( | ||||
|             "/livereload.js", Path(__file__).parent / "livereload.js" | ||||
|         ) | ||||
|         self.app.static("/livereload.js", Path(__file__).parent / "livereload.js") | ||||
|         self.app.add_websocket_route( | ||||
|             self.livereload_handler, "/livereload", name="livereload" | ||||
|         ) | ||||
| @@ -109,7 +104,5 @@ class Livereload: | ||||
|                 break | ||||
|  | ||||
|  | ||||
| def _run_reload_server( | ||||
|     reload_queue: Queue, debug: bool, state: dict[str, Any] | ||||
| ): | ||||
| def _run_reload_server(reload_queue: Queue, debug: bool, state: dict[str, Any]): | ||||
|     Livereload(reload_queue, debug, state).run() | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| # from scss.compiler import compile_string | ||||
|  | ||||
| from pygments.formatters import html | ||||
| from sass import compile as compile_scss | ||||
| from webapp.display.code_style import SanicCodeStyle | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sass import compile as compile_scss | ||||
|  | ||||
| from webapp.display.code_style import SanicCodeStyle | ||||
|  | ||||
|  | ||||
| def setup_style(app: Sanic) -> None: | ||||
|   | ||||
| @@ -2,20 +2,28 @@ | ||||
| requires = ["setuptools", "wheel"] | ||||
| build-backend = "setuptools.build_meta" | ||||
|  | ||||
| [tool.black] | ||||
| line-length = 79 | ||||
| [tool.ruff] | ||||
| extend-select = ["I", "W", "UP", "C4", "ISC"] | ||||
| # Worth selecting but still too broken: ASYNC, S, B, DTZ, FA | ||||
| ignore = [ | ||||
| 	"D100", | ||||
| 	"D101", | ||||
| 	"D102", | ||||
| 	"D103", | ||||
| 	"E402", | ||||
| 	"E741", | ||||
| 	"F811", | ||||
| 	"F821", | ||||
| 	# ruff format complains about these: | ||||
| 	"ISC001", | ||||
| 	"W191", | ||||
| ] | ||||
| show-source = true | ||||
| show-fixes = true | ||||
|  | ||||
| [tool.isort] | ||||
| atomic = true | ||||
| default_section = "THIRDPARTY" | ||||
| include_trailing_comma = true | ||||
| known_first_party = "sanic" | ||||
| known_third_party = "pytest" | ||||
| line_length = 79 | ||||
| lines_after_imports = 2 | ||||
| lines_between_types = 1 | ||||
| multi_line_output = 3 | ||||
| profile = "black" | ||||
| [tool.ruff.isort] | ||||
| known-first-party = ["sanic"] | ||||
| known-third-party = ["pytest"] | ||||
|  | ||||
| [[tool.mypy.overrides]] | ||||
| module = [ | ||||
|   | ||||
| @@ -36,7 +36,6 @@ from sanic.response import ( | ||||
| ) | ||||
| from sanic.server.websockets.impl import WebsocketImplProtocol as Websocket | ||||
|  | ||||
|  | ||||
| DefaultSanic: TypeAlias = "Sanic[Config, SimpleNamespace]" | ||||
| """ | ||||
| A type alias for a Sanic app with a default config and namespace. | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from sanic.cli.app import SanicCLI | ||||
| from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support | ||||
|  | ||||
|  | ||||
| if OS_IS_WINDOWS: | ||||
|     enable_windows_color_support() | ||||
|  | ||||
|   | ||||
							
								
								
									
										379
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										379
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -5,7 +5,6 @@ import logging | ||||
| import logging.config | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from asyncio import ( | ||||
|     AbstractEventLoop, | ||||
|     CancelledError, | ||||
| @@ -32,19 +31,12 @@ from typing import ( | ||||
|     Callable, | ||||
|     ClassVar, | ||||
|     Coroutine, | ||||
|     Deque, | ||||
|     Dict, | ||||
|     Generic, | ||||
|     Iterable, | ||||
|     Iterator, | ||||
|     List, | ||||
|     Literal, | ||||
|     Optional, | ||||
|     Set, | ||||
|     Tuple, | ||||
|     Type, | ||||
|     TypeVar, | ||||
|     Union, | ||||
|     cast, | ||||
|     overload, | ||||
| ) | ||||
| @@ -96,7 +88,6 @@ from sanic.worker.inspector import Inspector | ||||
| from sanic.worker.loader import CertLoader | ||||
| from sanic.worker.manager import WorkerManager | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     try: | ||||
|         from sanic_ext import Extend  # type: ignore | ||||
| @@ -173,7 +164,7 @@ class Sanic( | ||||
|         "websocket_tasks", | ||||
|     ) | ||||
|  | ||||
|     _app_registry: ClassVar[Dict[str, "Sanic"]] = {} | ||||
|     _app_registry: ClassVar[dict[str, Sanic]] = {} | ||||
|     test_mode: ClassVar[bool] = False | ||||
|  | ||||
|     @overload | ||||
| @@ -182,19 +173,19 @@ class Sanic( | ||||
|         name: str, | ||||
|         config: None = None, | ||||
|         ctx: None = None, | ||||
|         router: Optional[Router] = None, | ||||
|         signal_router: Optional[SignalRouter] = None, | ||||
|         error_handler: Optional[ErrorHandler] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         request_class: Optional[Type[Request]] = None, | ||||
|         router: Router | None = None, | ||||
|         signal_router: SignalRouter | None = None, | ||||
|         error_handler: ErrorHandler | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         request_class: type[Request] | None = None, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
| @@ -202,21 +193,21 @@ class Sanic( | ||||
|     def __init__( | ||||
|         self: Sanic[config_type, SimpleNamespace], | ||||
|         name: str, | ||||
|         config: Optional[config_type] = None, | ||||
|         config: config_type | None = None, | ||||
|         ctx: None = None, | ||||
|         router: Optional[Router] = None, | ||||
|         signal_router: Optional[SignalRouter] = None, | ||||
|         error_handler: Optional[ErrorHandler] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         request_class: Optional[Type[Request]] = None, | ||||
|         router: Router | None = None, | ||||
|         signal_router: SignalRouter | None = None, | ||||
|         error_handler: ErrorHandler | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         request_class: type[Request] | None = None, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
| @@ -225,20 +216,20 @@ class Sanic( | ||||
|         self: Sanic[Config, ctx_type], | ||||
|         name: str, | ||||
|         config: None = None, | ||||
|         ctx: Optional[ctx_type] = None, | ||||
|         router: Optional[Router] = None, | ||||
|         signal_router: Optional[SignalRouter] = None, | ||||
|         error_handler: Optional[ErrorHandler] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         request_class: Optional[Type[Request]] = None, | ||||
|         ctx: ctx_type | None = None, | ||||
|         router: Router | None = None, | ||||
|         signal_router: SignalRouter | None = None, | ||||
|         error_handler: ErrorHandler | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         request_class: type[Request] | None = None, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
| @@ -246,42 +237,42 @@ class Sanic( | ||||
|     def __init__( | ||||
|         self: Sanic[config_type, ctx_type], | ||||
|         name: str, | ||||
|         config: Optional[config_type] = None, | ||||
|         ctx: Optional[ctx_type] = None, | ||||
|         router: Optional[Router] = None, | ||||
|         signal_router: Optional[SignalRouter] = None, | ||||
|         error_handler: Optional[ErrorHandler] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         request_class: Optional[Type[Request]] = None, | ||||
|         config: config_type | None = None, | ||||
|         ctx: ctx_type | None = None, | ||||
|         router: Router | None = None, | ||||
|         signal_router: SignalRouter | None = None, | ||||
|         error_handler: ErrorHandler | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         request_class: type[Request] | None = None, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         name: str, | ||||
|         config: Optional[config_type] = None, | ||||
|         ctx: Optional[ctx_type] = None, | ||||
|         router: Optional[Router] = None, | ||||
|         signal_router: Optional[SignalRouter] = None, | ||||
|         error_handler: Optional[ErrorHandler] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         request_class: Optional[Type[Request]] = None, | ||||
|         config: config_type | None = None, | ||||
|         ctx: ctx_type | None = None, | ||||
|         router: Router | None = None, | ||||
|         signal_router: SignalRouter | None = None, | ||||
|         error_handler: ErrorHandler | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         request_class: type[Request] | None = None, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|     ) -> None: | ||||
|         super().__init__(name=name) | ||||
|         # logging | ||||
| @@ -303,41 +294,39 @@ class Sanic( | ||||
|             self.config.INSPECTOR = inspector | ||||
|  | ||||
|         # Then we can do the rest | ||||
|         self._asgi_app: Optional[ASGIApp] = None | ||||
|         self._asgi_lifespan: Optional[Lifespan] = None | ||||
|         self._asgi_app: ASGIApp | None = None | ||||
|         self._asgi_lifespan: Lifespan | None = None | ||||
|         self._asgi_client: Any = None | ||||
|         self._blueprint_order: List[Blueprint] = [] | ||||
|         self._delayed_tasks: List[str] = [] | ||||
|         self._blueprint_order: list[Blueprint] = [] | ||||
|         self._delayed_tasks: list[str] = [] | ||||
|         self._future_registry: FutureRegistry = FutureRegistry() | ||||
|         self._inspector: Optional[Inspector] = None | ||||
|         self._manager: Optional[WorkerManager] = None | ||||
|         self._inspector: Inspector | None = None | ||||
|         self._manager: WorkerManager | None = None | ||||
|         self._state: ApplicationState = ApplicationState(app=self) | ||||
|         self._task_registry: Dict[str, Union[Task, None]] = {} | ||||
|         self._task_registry: dict[str, Task | None] = {} | ||||
|         self._test_client: Any = None | ||||
|         self._test_manager: Any = None | ||||
|         self.asgi = False | ||||
|         self.auto_reload = False | ||||
|         self.blueprints: Dict[str, Blueprint] = {} | ||||
|         self.certloader_class: Type[CertLoader] = ( | ||||
|             certloader_class or CertLoader | ||||
|         ) | ||||
|         self.blueprints: dict[str, Blueprint] = {} | ||||
|         self.certloader_class: type[CertLoader] = certloader_class or CertLoader | ||||
|         self.configure_logging: bool = configure_logging | ||||
|         self.ctx: ctx_type = cast(ctx_type, ctx or SimpleNamespace()) | ||||
|         self.error_handler: ErrorHandler = error_handler or ErrorHandler() | ||||
|         self.inspector_class: Type[Inspector] = inspector_class or Inspector | ||||
|         self.listeners: Dict[str, List[ListenerType[Any]]] = defaultdict(list) | ||||
|         self.named_request_middleware: Dict[str, Deque[Middleware]] = {} | ||||
|         self.named_response_middleware: Dict[str, Deque[Middleware]] = {} | ||||
|         self.request_class: Type[Request] = request_class or Request | ||||
|         self.request_middleware: Deque[Middleware] = deque() | ||||
|         self.response_middleware: Deque[Middleware] = deque() | ||||
|         self.inspector_class: type[Inspector] = inspector_class or Inspector | ||||
|         self.listeners: dict[str, list[ListenerType[Any]]] = defaultdict(list) | ||||
|         self.named_request_middleware: dict[str, deque[Middleware]] = {} | ||||
|         self.named_response_middleware: dict[str, deque[Middleware]] = {} | ||||
|         self.request_class: type[Request] = request_class or Request | ||||
|         self.request_middleware: deque[Middleware] = deque() | ||||
|         self.response_middleware: deque[Middleware] = deque() | ||||
|         self.router: Router = router or Router() | ||||
|         self.shared_ctx: SharedContext = SharedContext() | ||||
|         self.signal_router: SignalRouter = signal_router or SignalRouter() | ||||
|         self.sock: Optional[socket] = None | ||||
|         self.sock: socket | None = None | ||||
|         self.strict_slashes: bool = strict_slashes | ||||
|         self.websocket_enabled: bool = False | ||||
|         self.websocket_tasks: Set[Future[Any]] = set() | ||||
|         self.websocket_tasks: set[Future[Any]] = set() | ||||
|  | ||||
|         # Register alternative method names | ||||
|         self.go_fast = self.run | ||||
| @@ -396,15 +385,11 @@ class Sanic( | ||||
|         try: | ||||
|             _event = ListenerEvent[event.upper()] | ||||
|         except (ValueError, AttributeError): | ||||
|             valid = ", ".join( | ||||
|                 map(lambda x: x.lower(), ListenerEvent.__members__.keys()) | ||||
|             ) | ||||
|             valid = ", ".join(x.lower() for x in ListenerEvent.__members__.keys()) | ||||
|             raise BadRequest(f"Invalid event: {event}. Use one of: {valid}") | ||||
|  | ||||
|         if "." in _event: | ||||
|             self.signal(_event.value)( | ||||
|                 partial(self._listener, listener=listener) | ||||
|             ) | ||||
|             self.signal(_event.value)(partial(self._listener, listener=listener)) | ||||
|         else: | ||||
|             self.listeners[_event.value].append(listener) | ||||
|  | ||||
| @@ -412,11 +397,11 @@ class Sanic( | ||||
|  | ||||
|     def register_middleware( | ||||
|         self, | ||||
|         middleware: Union[MiddlewareType, Middleware], | ||||
|         middleware: MiddlewareType | Middleware, | ||||
|         attach_to: str = "request", | ||||
|         *, | ||||
|         priority: Union[Default, int] = _default, | ||||
|     ) -> Union[MiddlewareType, Middleware]: | ||||
|         priority: Default | int = _default, | ||||
|     ) -> MiddlewareType | Middleware: | ||||
|         """Register a middleware to be called before a request is handled. | ||||
|  | ||||
|         Args: | ||||
| @@ -461,7 +446,7 @@ class Sanic( | ||||
|         route_names: Iterable[str], | ||||
|         attach_to: str = "request", | ||||
|         *, | ||||
|         priority: Union[Default, int] = _default, | ||||
|         priority: Default | int = _default, | ||||
|     ): | ||||
|         """Used to register named middleqare (middleware typically on blueprints) | ||||
|  | ||||
| @@ -512,7 +497,7 @@ class Sanic( | ||||
|     def _apply_exception_handler( | ||||
|         self, | ||||
|         handler: FutureException, | ||||
|         route_names: Optional[List[str]] = None, | ||||
|         route_names: list[str] | None = None, | ||||
|     ): | ||||
|         """Decorate a function to be registered as a handler for exceptions | ||||
|  | ||||
| @@ -531,9 +516,7 @@ class Sanic( | ||||
|     def _apply_listener(self, listener: FutureListener): | ||||
|         return self.register_listener(listener.listener, listener.event) | ||||
|  | ||||
|     def _apply_route( | ||||
|         self, route: FutureRoute, overwrite: bool = False | ||||
|     ) -> List[Route]: | ||||
|     def _apply_route(self, route: FutureRoute, overwrite: bool = False) -> list[Route]: | ||||
|         params = route._asdict() | ||||
|         params["overwrite"] = overwrite | ||||
|         websocket = params.pop("websocket", False) | ||||
| @@ -567,7 +550,7 @@ class Sanic( | ||||
|     def _apply_middleware( | ||||
|         self, | ||||
|         middleware: FutureMiddleware, | ||||
|         route_names: Optional[List[str]] = None, | ||||
|         route_names: list[str] | None = None, | ||||
|     ): | ||||
|         with self.amend(): | ||||
|             if route_names: | ||||
| @@ -588,8 +571,8 @@ class Sanic( | ||||
|         self, | ||||
|         event: str, | ||||
|         *, | ||||
|         condition: Optional[Dict[str, str]] = None, | ||||
|         context: Optional[Dict[str, Any]] = None, | ||||
|         condition: dict[str, str] | None = None, | ||||
|         context: dict[str, Any] | None = None, | ||||
|         fail_not_found: bool = True, | ||||
|         inline: Literal[True], | ||||
|         reverse: bool = False, | ||||
| @@ -601,8 +584,8 @@ class Sanic( | ||||
|         self, | ||||
|         event: str, | ||||
|         *, | ||||
|         condition: Optional[Dict[str, str]] = None, | ||||
|         context: Optional[Dict[str, Any]] = None, | ||||
|         condition: dict[str, str] | None = None, | ||||
|         context: dict[str, Any] | None = None, | ||||
|         fail_not_found: bool = True, | ||||
|         inline: Literal[False] = False, | ||||
|         reverse: bool = False, | ||||
| @@ -613,12 +596,12 @@ class Sanic( | ||||
|         self, | ||||
|         event: str, | ||||
|         *, | ||||
|         condition: Optional[Dict[str, str]] = None, | ||||
|         context: Optional[Dict[str, Any]] = None, | ||||
|         condition: dict[str, str] | None = None, | ||||
|         context: dict[str, Any] | None = None, | ||||
|         fail_not_found: bool = True, | ||||
|         inline: bool = False, | ||||
|         reverse: bool = False, | ||||
|     ) -> Coroutine[Any, Any, Awaitable[Union[Task, Any]]]: | ||||
|     ) -> Coroutine[Any, Any, Awaitable[Task | Any]]: | ||||
|         """Dispatches an event to the signal router. | ||||
|  | ||||
|         Args: | ||||
| @@ -662,9 +645,7 @@ class Sanic( | ||||
|             fail_not_found=fail_not_found, | ||||
|         ) | ||||
|  | ||||
|     async def event( | ||||
|         self, event: str, timeout: Optional[Union[int, float]] = None | ||||
|     ) -> None: | ||||
|     async def event(self, event: str, timeout: int | float | None = None) -> None: | ||||
|         """Wait for a specific event to be triggered. | ||||
|  | ||||
|         This method waits for a named event to be triggered and can be used | ||||
| @@ -749,9 +730,7 @@ class Sanic( | ||||
|         async def report(exception: Exception) -> None: | ||||
|             await handler(self, exception) | ||||
|  | ||||
|         self.add_signal( | ||||
|             handler=report, event=Event.SERVER_EXCEPTION_REPORT.value | ||||
|         ) | ||||
|         self.add_signal(handler=report, event=Event.SERVER_EXCEPTION_REPORT.value) | ||||
|  | ||||
|         return report | ||||
|  | ||||
| @@ -780,13 +759,13 @@ class Sanic( | ||||
|  | ||||
|     def blueprint( | ||||
|         self, | ||||
|         blueprint: Union[Blueprint, Iterable[Blueprint], BlueprintGroup], | ||||
|         blueprint: Blueprint | (Iterable[Blueprint] | BlueprintGroup), | ||||
|         *, | ||||
|         url_prefix: Optional[str] = None, | ||||
|         version: Optional[Union[int, float, str]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         version_prefix: Optional[str] = None, | ||||
|         name_prefix: Optional[str] = None, | ||||
|         url_prefix: str | None = None, | ||||
|         version: int | float | str | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         version_prefix: str | None = None, | ||||
|         name_prefix: str | None = None, | ||||
|     ) -> None: | ||||
|         """Register a blueprint on the application. | ||||
|  | ||||
| @@ -812,7 +791,7 @@ class Sanic( | ||||
|             app.blueprint(bp, url_prefix='/blueprint') | ||||
|             ``` | ||||
|         """  # noqa: E501 | ||||
|         options: Dict[str, Any] = {} | ||||
|         options: dict[str, Any] = {} | ||||
|         if url_prefix is not None: | ||||
|             options["url_prefix"] = url_prefix | ||||
|         if version is not None: | ||||
| @@ -825,7 +804,7 @@ class Sanic( | ||||
|             options["name_prefix"] = name_prefix | ||||
|         if isinstance(blueprint, (Iterable, BlueprintGroup)): | ||||
|             for item in blueprint: | ||||
|                 params: Dict[str, Any] = {**options} | ||||
|                 params: dict[str, Any] = {**options} | ||||
|                 if isinstance(blueprint, BlueprintGroup): | ||||
|                     merge_from = [ | ||||
|                         options.get("url_prefix", ""), | ||||
| @@ -840,14 +819,12 @@ class Sanic( | ||||
|  | ||||
|                     for _attr in ["version", "strict_slashes"]: | ||||
|                         if getattr(item, _attr) is None: | ||||
|                             params[_attr] = getattr( | ||||
|                                 blueprint, _attr | ||||
|                             ) or options.get(_attr) | ||||
|                             params[_attr] = getattr(blueprint, _attr) or options.get( | ||||
|                                 _attr | ||||
|                             ) | ||||
|                     if item.version_prefix == "/v": | ||||
|                         if blueprint.version_prefix == "/v": | ||||
|                             params["version_prefix"] = options.get( | ||||
|                                 "version_prefix" | ||||
|                             ) | ||||
|                             params["version_prefix"] = options.get("version_prefix") | ||||
|                         else: | ||||
|                             params["version_prefix"] = blueprint.version_prefix | ||||
|                     name_prefix = getattr(blueprint, "name_prefix", None) | ||||
| @@ -857,17 +834,14 @@ class Sanic( | ||||
|             return | ||||
|         if blueprint.name in self.blueprints: | ||||
|             assert self.blueprints[blueprint.name] is blueprint, ( | ||||
|                 'A blueprint with the name "%s" is already registered.  ' | ||||
|                 "Blueprint names must be unique." % (blueprint.name,) | ||||
|                 f'A blueprint with the name "{blueprint.name}" is already registered.  ' | ||||
|                 "Blueprint names must be unique." | ||||
|             ) | ||||
|         else: | ||||
|             self.blueprints[blueprint.name] = blueprint | ||||
|             self._blueprint_order.append(blueprint) | ||||
|  | ||||
|         if ( | ||||
|             self.strict_slashes is not None | ||||
|             and blueprint.strict_slashes is None | ||||
|         ): | ||||
|         if self.strict_slashes is not None and blueprint.strict_slashes is None: | ||||
|             blueprint.strict_slashes = self.strict_slashes | ||||
|         blueprint.register(self, options) | ||||
|  | ||||
| @@ -923,7 +897,7 @@ class Sanic( | ||||
|             # http://subdomain.example.com/view-name | ||||
|         """  # noqa: E501 | ||||
|         # find the route by the supplied view name | ||||
|         kw: Dict[str, str] = {} | ||||
|         kw: dict[str, str] = {} | ||||
|         # special static files url_for | ||||
|  | ||||
|         if "." not in view_name: | ||||
| @@ -937,9 +911,7 @@ class Sanic( | ||||
|  | ||||
|         route = self.router.find_route_by_view_name(view_name, **kw) | ||||
|         if not route: | ||||
|             raise URLBuildError( | ||||
|                 f"Endpoint with name `{view_name}` was not found" | ||||
|             ) | ||||
|             raise URLBuildError(f"Endpoint with name `{view_name}` was not found") | ||||
|  | ||||
|         uri = route.path | ||||
|  | ||||
| @@ -978,9 +950,7 @@ class Sanic( | ||||
|         scheme = kwargs.pop("_scheme", "") | ||||
|         if route.extra.hosts and external: | ||||
|             if not host and len(route.extra.hosts) > 1: | ||||
|                 raise ValueError( | ||||
|                     f"Host is ambiguous: {', '.join(route.extra.hosts)}" | ||||
|                 ) | ||||
|                 raise ValueError(f"Host is ambiguous: {', '.join(route.extra.hosts)}") | ||||
|             elif host and host not in route.extra.hosts: | ||||
|                 raise ValueError( | ||||
|                     f"Requested host ({host}) is not available for this " | ||||
| @@ -1096,10 +1066,7 @@ class Sanic( | ||||
|             context={"request": request, "exception": exception}, | ||||
|         ) | ||||
|  | ||||
|         if ( | ||||
|             request.stream is not None | ||||
|             and request.stream.stage is not Stage.HANDLER | ||||
|         ): | ||||
|         if request.stream is not None and request.stream.stage is not Stage.HANDLER: | ||||
|             error_logger.exception(exception, exc_info=True) | ||||
|             logger.error( | ||||
|                 "The error response will not be sent to the client for " | ||||
| @@ -1146,10 +1113,7 @@ class Sanic( | ||||
|                     response = self.error_handler.default(request, e) | ||||
|                 elif self.debug: | ||||
|                     response = HTTPResponse( | ||||
|                         ( | ||||
|                             f"Error while handling error: {e}\n" | ||||
|                             f"Stack: {format_exc()}" | ||||
|                         ), | ||||
|                         (f"Error while handling error: {e}\n" f"Stack: {format_exc()}"), | ||||
|                         status=500, | ||||
|                     ) | ||||
|                 else: | ||||
| @@ -1194,9 +1158,7 @@ class Sanic( | ||||
|             ) | ||||
|             await response.eof() | ||||
|         else: | ||||
|             raise ServerError( | ||||
|                 f"Invalid response type {response!r} (need HTTPResponse)" | ||||
|             ) | ||||
|             raise ServerError(f"Invalid response type {response!r} (need HTTPResponse)") | ||||
|  | ||||
|     async def handle_request(self, request: Request) -> None:  # no cov | ||||
|         """Handles a request by dispatching it to the appropriate handler. | ||||
| @@ -1221,13 +1183,11 @@ class Sanic( | ||||
|  | ||||
|         # Define `response` var here to remove warnings about | ||||
|         # allocation before assignment below. | ||||
|         response: Optional[ | ||||
|             Union[ | ||||
|                 BaseHTTPResponse, | ||||
|                 Coroutine[Any, Any, Optional[BaseHTTPResponse]], | ||||
|                 ResponseStream, | ||||
|             ] | ||||
|         ] = None | ||||
|         response: ( | ||||
|             BaseHTTPResponse | ||||
|             | (Coroutine[Any, Any, BaseHTTPResponse | None] | ResponseStream) | ||||
|             | None | ||||
|         ) = None | ||||
|         run_middleware = True | ||||
|         try: | ||||
|             await self.dispatch( | ||||
| @@ -1285,10 +1245,8 @@ class Sanic( | ||||
|  | ||||
|                 if handler is None: | ||||
|                     raise ServerError( | ||||
|                         ( | ||||
|                             "'None' was returned while requesting a " | ||||
|                             "handler from the router" | ||||
|                         ) | ||||
|                         "'None' was returned while requesting a " | ||||
|                         "handler from the router" | ||||
|                     ) | ||||
|  | ||||
|                 # Run response handler | ||||
| @@ -1347,17 +1305,14 @@ class Sanic( | ||||
|             else: | ||||
|                 if not hasattr(handler, "is_websocket"): | ||||
|                     raise ServerError( | ||||
|                         f"Invalid response type {response!r} " | ||||
|                         "(need HTTPResponse)" | ||||
|                         f"Invalid response type {response!r} " "(need HTTPResponse)" | ||||
|                     ) | ||||
|  | ||||
|         except CancelledError:  # type: ignore | ||||
|             raise | ||||
|         except Exception as e: | ||||
|             # Response Generation Failed | ||||
|             await self.handle_exception( | ||||
|                 request, e, run_middleware=run_middleware | ||||
|             ) | ||||
|             await self.handle_exception(request, e, run_middleware=run_middleware) | ||||
|  | ||||
|     async def _websocket_handler( | ||||
|         self, handler, request, *args, subprotocols=None, **kwargs | ||||
| @@ -1436,9 +1391,7 @@ class Sanic( | ||||
|     # Execution | ||||
|     # -------------------------------------------------------------------- # | ||||
|  | ||||
|     async def _run_request_middleware( | ||||
|         self, request, middleware_collection | ||||
|     ):  # no cov | ||||
|     async def _run_request_middleware(self, request, middleware_collection):  # no cov | ||||
|         request._request_middleware_started = True | ||||
|  | ||||
|         for middleware in middleware_collection: | ||||
| @@ -1515,9 +1468,7 @@ class Sanic( | ||||
|             task.cancel() | ||||
|  | ||||
|     @staticmethod | ||||
|     async def _listener( | ||||
|         app: Sanic, loop: AbstractEventLoop, listener: ListenerType | ||||
|     ): | ||||
|     async def _listener(app: Sanic, loop: AbstractEventLoop, listener: ListenerType): | ||||
|         try: | ||||
|             maybe_coro = listener(app)  # type: ignore | ||||
|         except TypeError: | ||||
| @@ -1546,9 +1497,7 @@ class Sanic( | ||||
|                 if isawaitable(task): | ||||
|                     await task | ||||
|             except CancelledError: | ||||
|                 error_logger.warning( | ||||
|                     f"Task {task} was cancelled before it completed." | ||||
|                 ) | ||||
|                 error_logger.warning(f"Task {task} was cancelled before it completed.") | ||||
|                 raise | ||||
|             except Exception as e: | ||||
|                 await app.dispatch( | ||||
| @@ -1566,7 +1515,7 @@ class Sanic( | ||||
|         app, | ||||
|         loop, | ||||
|         *, | ||||
|         name: Optional[str] = None, | ||||
|         name: str | None = None, | ||||
|         register: bool = True, | ||||
|     ) -> Task: | ||||
|         if not isinstance(task, Future): | ||||
| @@ -1628,11 +1577,11 @@ class Sanic( | ||||
|  | ||||
|     def add_task( | ||||
|         self, | ||||
|         task: Union[Future[Any], Coroutine[Any, Any, Any], Awaitable[Any]], | ||||
|         task: Future[Any] | (Coroutine[Any, Any, Any] | Awaitable[Any]), | ||||
|         *, | ||||
|         name: Optional[str] = None, | ||||
|         name: str | None = None, | ||||
|         register: bool = True, | ||||
|     ) -> Optional[Task[Any]]: | ||||
|     ) -> Task[Any] | None: | ||||
|         """Schedule a task to run later, after the loop has started. | ||||
|  | ||||
|         While this is somewhat similar to `asyncio.create_task`, it can be | ||||
| @@ -1657,18 +1606,14 @@ class Sanic( | ||||
|         """  # noqa: E501 | ||||
|         try: | ||||
|             loop = self.loop  # Will raise SanicError if loop is not started | ||||
|             return self._loop_add_task( | ||||
|                 task, self, loop, name=name, register=register | ||||
|             ) | ||||
|             return self._loop_add_task(task, self, loop, name=name, register=register) | ||||
|         except SanicException: | ||||
|             task_name = f"sanic.delayed_task.{hash(task)}" | ||||
|             if not self._delayed_tasks: | ||||
|                 self.after_server_start(partial(self.dispatch_delayed_tasks)) | ||||
|  | ||||
|             if name: | ||||
|                 raise RuntimeError( | ||||
|                     "Cannot name task outside of a running application" | ||||
|                 ) | ||||
|                 raise RuntimeError("Cannot name task outside of a running application") | ||||
|  | ||||
|             self.signal(task_name)(partial(self.run_delayed_task, task=task)) | ||||
|             self._delayed_tasks.append(task_name) | ||||
| @@ -1679,18 +1624,14 @@ class Sanic( | ||||
|         ... | ||||
|  | ||||
|     @overload | ||||
|     def get_task( | ||||
|         self, name: str, *, raise_exception: Literal[False] | ||||
|     ) -> Optional[Task]: | ||||
|     def get_task(self, name: str, *, raise_exception: Literal[False]) -> Task | None: | ||||
|         ... | ||||
|  | ||||
|     @overload | ||||
|     def get_task(self, name: str, *, raise_exception: bool) -> Optional[Task]: | ||||
|     def get_task(self, name: str, *, raise_exception: bool) -> Task | None: | ||||
|         ... | ||||
|  | ||||
|     def get_task( | ||||
|         self, name: str, *, raise_exception: bool = True | ||||
|     ) -> Optional[Task]: | ||||
|     def get_task(self, name: str, *, raise_exception: bool = True) -> Task | None: | ||||
|         """Get a named task. | ||||
|  | ||||
|         This method is used to get a task by its name. Optionally, you can | ||||
| @@ -1708,15 +1649,13 @@ class Sanic( | ||||
|             return self._task_registry[name] | ||||
|         except KeyError: | ||||
|             if raise_exception: | ||||
|                 raise SanicException( | ||||
|                     f'Registered task named "{name}" not found.' | ||||
|                 ) | ||||
|                 raise SanicException(f'Registered task named "{name}" not found.') | ||||
|             return None | ||||
|  | ||||
|     async def cancel_task( | ||||
|         self, | ||||
|         name: str, | ||||
|         msg: Optional[str] = None, | ||||
|         msg: str | None = None, | ||||
|         *, | ||||
|         raise_exception: bool = True, | ||||
|     ) -> None: | ||||
| @@ -1751,7 +1690,7 @@ class Sanic( | ||||
|         """  # noqa: E501 | ||||
|         task = self.get_task(name, raise_exception=raise_exception) | ||||
|         if task and not task.cancelled(): | ||||
|             args: Tuple[str, ...] = () | ||||
|             args: tuple[str, ...] = () | ||||
|             if msg: | ||||
|                 if sys.version_info >= (3, 9): | ||||
|                     args = (msg,) | ||||
| @@ -1784,7 +1723,7 @@ class Sanic( | ||||
|         } | ||||
|  | ||||
|     def shutdown_tasks( | ||||
|         self, timeout: Optional[float] = None, increment: float = 0.1 | ||||
|         self, timeout: float | None = None, increment: float = 0.1 | ||||
|     ) -> None: | ||||
|         """Cancel all tasks except the server task. | ||||
|  | ||||
| @@ -1822,11 +1761,7 @@ class Sanic( | ||||
|             Iterable[Task[Any]]: The tasks that are currently registered with | ||||
|                 the application. | ||||
|         """ | ||||
|         return ( | ||||
|             task | ||||
|             for task in iter(self._task_registry.values()) | ||||
|             if task is not None | ||||
|         ) | ||||
|         return (task for task in iter(self._task_registry.values()) if task is not None) | ||||
|  | ||||
|     # -------------------------------------------------------------------- # | ||||
|     # ASGI | ||||
| @@ -1853,7 +1788,7 @@ class Sanic( | ||||
|     # Configuration | ||||
|     # -------------------------------------------------------------------- # | ||||
|  | ||||
|     def update_config(self, config: Union[bytes, str, dict, Any]) -> None: | ||||
|     def update_config(self, config: Any) -> None: | ||||
|         """Update the application configuration. | ||||
|  | ||||
|         This method is used to update the application configuration. It can | ||||
| @@ -1863,7 +1798,7 @@ class Sanic( | ||||
|         See [Configuration](/en/guide/deployment/configuration) for details. | ||||
|  | ||||
|         Args: | ||||
|             config (Union[bytes, str, dict, Any]): The configuration object, | ||||
|             config (bytes | str | dict | Any): The configuration object, | ||||
|                 dictionary, or path to a configuration file. | ||||
|         """ | ||||
|  | ||||
| @@ -1903,7 +1838,7 @@ class Sanic( | ||||
|         return self._state | ||||
|  | ||||
|     @property | ||||
|     def reload_dirs(self) -> Set[Path]: | ||||
|     def reload_dirs(self) -> set[Path]: | ||||
|         """The directories that are monitored for auto-reload. | ||||
|  | ||||
|         Returns: | ||||
| @@ -1948,9 +1883,9 @@ class Sanic( | ||||
|     def extend( | ||||
|         self, | ||||
|         *, | ||||
|         extensions: Optional[List[Type[Extension]]] = None, | ||||
|         extensions: list[type[Extension]] | None = None, | ||||
|         built_in_extensions: bool = True, | ||||
|         config: Optional[Union[Config, Dict[str, Any]]] = None, | ||||
|         config: Config | dict[str, Any] | None = None, | ||||
|         **kwargs, | ||||
|     ) -> Extend: | ||||
|         """Extend Sanic with additional functionality using Sanic Extensions. | ||||
| @@ -2068,9 +2003,7 @@ class Sanic( | ||||
|             del cls._app_registry[name] | ||||
|  | ||||
|     @classmethod | ||||
|     def get_app( | ||||
|         cls, name: Optional[str] = None, *, force_create: bool = False | ||||
|     ) -> Sanic: | ||||
|     def get_app(cls, name: str | None = None, *, force_create: bool = False) -> Sanic: | ||||
|         """Retrieve an instantiated Sanic instance by name. | ||||
|  | ||||
|         This method is best used when needing to get access to an already | ||||
| @@ -2277,9 +2210,7 @@ class Sanic( | ||||
|         self.finalize() | ||||
|  | ||||
|         route_names = [route.extra.ident for route in self.router.routes] | ||||
|         duplicates = { | ||||
|             name for name in route_names if route_names.count(name) > 1 | ||||
|         } | ||||
|         duplicates = {name for name in route_names if route_names.count(name) > 1} | ||||
|         if duplicates: | ||||
|             names = ", ".join(duplicates) | ||||
|             message = ( | ||||
| @@ -2316,7 +2247,7 @@ class Sanic( | ||||
|         self, | ||||
|         concern: str, | ||||
|         action: str, | ||||
|         loop: Optional[AbstractEventLoop] = None, | ||||
|         loop: AbstractEventLoop | None = None, | ||||
|     ) -> None: | ||||
|         event = f"server.{concern}.{action}" | ||||
|         if action not in ("before", "after") or concern not in ( | ||||
| @@ -2324,9 +2255,7 @@ class Sanic( | ||||
|             "shutdown", | ||||
|         ): | ||||
|             raise SanicException(f"Invalid server event: {event}") | ||||
|         logger.debug( | ||||
|             f"Triggering server events: {event}", extra={"verbosity": 1} | ||||
|         ) | ||||
|         logger.debug(f"Triggering server events: {event}", extra={"verbosity": 1}) | ||||
|         reverse = concern == "shutdown" | ||||
|         if loop is None: | ||||
|             loop = self.loop | ||||
| @@ -2347,7 +2276,7 @@ class Sanic( | ||||
|  | ||||
|     def refresh( | ||||
|         self, | ||||
|         passthru: Optional[Dict[str, Any]] = None, | ||||
|         passthru: dict[str, Any] | None = None, | ||||
|     ) -> Sanic: | ||||
|         """Refresh the application instance. **This is used internally by Sanic**. | ||||
|  | ||||
| @@ -2392,9 +2321,7 @@ class Sanic( | ||||
|             Inspector: An instance of Inspector. | ||||
|         """ | ||||
|         if environ.get("SANIC_WORKER_PROCESS") or not self._inspector: | ||||
|             raise SanicException( | ||||
|                 "Can only access the inspector from the main process" | ||||
|             ) | ||||
|             raise SanicException("Can only access the inspector from the main process") | ||||
|         return self._inspector | ||||
|  | ||||
|     @property | ||||
| @@ -2427,7 +2354,5 @@ class Sanic( | ||||
|         """ | ||||
|  | ||||
|         if environ.get("SANIC_WORKER_PROCESS") or not self._manager: | ||||
|             raise SanicException( | ||||
|                 "Can only access the manager from the main process" | ||||
|             ) | ||||
|             raise SanicException("Can only access the manager from the main process") | ||||
|         return self._manager | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from contextlib import suppress | ||||
| from importlib import import_module | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from os import environ | ||||
|  | ||||
| from sanic.helpers import is_atty | ||||
|  | ||||
|  | ||||
| BASE_LOGO = """ | ||||
|  | ||||
|                  Sanic | ||||
| @@ -63,10 +61,7 @@ def get_logo(full: bool = False, coffee: bool = False) -> str: | ||||
|         else BASE_LOGO | ||||
|     ) | ||||
|  | ||||
|     if ( | ||||
|         sys.platform == "darwin" | ||||
|         and environ.get("TERM_PROGRAM") == "Apple_Terminal" | ||||
|     ): | ||||
|     if sys.platform == "darwin" and environ.get("TERM_PROGRAM") == "Apple_Terminal": | ||||
|         logo = ansi_pattern.sub("", logo) | ||||
|  | ||||
|     return logo | ||||
|   | ||||
| @@ -79,9 +79,7 @@ class MOTDTTY(MOTD): | ||||
|     def set_variables(self):  # no  cov | ||||
|         """Set the variables used for display.""" | ||||
|         fallback = (108, 24) | ||||
|         terminal_width = max( | ||||
|             get_terminal_size(fallback=fallback).columns, fallback[0] | ||||
|         ) | ||||
|         terminal_width = max(get_terminal_size(fallback=fallback).columns, fallback[0]) | ||||
|         self.max_value_width = terminal_width - fallback[0] + 36 | ||||
|  | ||||
|         self.key_width = 4 | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| import os | ||||
| import sys | ||||
| import time | ||||
|  | ||||
| from contextlib import contextmanager | ||||
| from queue import Queue | ||||
| from threading import Thread | ||||
|  | ||||
|  | ||||
| if os.name == "nt":  # noqa | ||||
|     import ctypes  # noqa | ||||
|  | ||||
| @@ -47,21 +45,16 @@ class Spinner:  # noqa | ||||
|     @staticmethod | ||||
|     def cursor(): | ||||
|         while True: | ||||
|             for cursor in "|/-\\": | ||||
|                 yield cursor | ||||
|             yield from "|/-\\" | ||||
|  | ||||
|     @staticmethod | ||||
|     def hide(): | ||||
|         if os.name == "nt": | ||||
|             ci = _CursorInfo() | ||||
|             handle = ctypes.windll.kernel32.GetStdHandle(-11) | ||||
|             ctypes.windll.kernel32.GetConsoleCursorInfo( | ||||
|                 handle, ctypes.byref(ci) | ||||
|             ) | ||||
|             ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) | ||||
|             ci.visible = False | ||||
|             ctypes.windll.kernel32.SetConsoleCursorInfo( | ||||
|                 handle, ctypes.byref(ci) | ||||
|             ) | ||||
|             ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) | ||||
|         elif os.name == "posix": | ||||
|             sys.stdout.write("\033[?25l") | ||||
|             sys.stdout.flush() | ||||
| @@ -71,13 +64,9 @@ class Spinner:  # noqa | ||||
|         if os.name == "nt": | ||||
|             ci = _CursorInfo() | ||||
|             handle = ctypes.windll.kernel32.GetStdHandle(-11) | ||||
|             ctypes.windll.kernel32.GetConsoleCursorInfo( | ||||
|                 handle, ctypes.byref(ci) | ||||
|             ) | ||||
|             ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) | ||||
|             ci.visible = True | ||||
|             ctypes.windll.kernel32.SetConsoleCursorInfo( | ||||
|                 handle, ctypes.byref(ci) | ||||
|             ) | ||||
|             ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) | ||||
|         elif os.name == "posix": | ||||
|             sys.stdout.write("\033[?25h") | ||||
|             sys.stdout.flush() | ||||
|   | ||||
| @@ -1,18 +1,16 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from dataclasses import dataclass, field | ||||
| from pathlib import Path | ||||
| from socket import socket | ||||
| from ssl import SSLContext | ||||
| from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union | ||||
| from typing import TYPE_CHECKING, Any | ||||
|  | ||||
| from sanic.application.constants import Mode, Server, ServerStage | ||||
| from sanic.log import VerbosityFilter, logger | ||||
| from sanic.server.async_server import AsyncioServer | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
| @@ -21,9 +19,9 @@ if TYPE_CHECKING: | ||||
| class ApplicationServerInfo: | ||||
|     """Information about a server instance.""" | ||||
|  | ||||
|     settings: Dict[str, Any] | ||||
|     settings: dict[str, Any] | ||||
|     stage: ServerStage = field(default=ServerStage.STOPPED) | ||||
|     server: Optional[AsyncioServer] = field(default=None) | ||||
|     server: AsyncioServer | None = field(default=None) | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| @@ -40,11 +38,11 @@ class ApplicationState: | ||||
|     fast: bool = field(default=False) | ||||
|     host: str = field(default="") | ||||
|     port: int = field(default=0) | ||||
|     ssl: Optional[SSLContext] = field(default=None) | ||||
|     sock: Optional[socket] = field(default=None) | ||||
|     unix: Optional[str] = field(default=None) | ||||
|     ssl: SSLContext | None = field(default=None) | ||||
|     sock: socket | None = field(default=None) | ||||
|     unix: str | None = field(default=None) | ||||
|     mode: Mode = field(default=Mode.PRODUCTION) | ||||
|     reload_dirs: Set[Path] = field(default_factory=set) | ||||
|     reload_dirs: set[Path] = field(default_factory=set) | ||||
|     auto_reload: bool = field(default=False) | ||||
|     server: Server = field(default=Server.SANIC) | ||||
|     is_running: bool = field(default=False) | ||||
| @@ -53,7 +51,7 @@ class ApplicationState: | ||||
|     verbosity: int = field(default=0) | ||||
|     workers: int = field(default=0) | ||||
|     primary: bool = field(default=True) | ||||
|     server_info: List[ApplicationServerInfo] = field(default_factory=list) | ||||
|     server_info: list[ApplicationServerInfo] = field(default_factory=list) | ||||
|  | ||||
|     # This property relates to the ApplicationState instance and should | ||||
|     # not be changed except in the __post_init__ method | ||||
| @@ -64,14 +62,12 @@ class ApplicationState: | ||||
|  | ||||
|     def __setattr__(self, name: str, value: Any) -> None: | ||||
|         if self._init and name == "_init": | ||||
|             raise RuntimeError( | ||||
|                 "Cannot change the value of _init after instantiation" | ||||
|             ) | ||||
|             raise RuntimeError("Cannot change the value of _init after instantiation") | ||||
|         super().__setattr__(name, value) | ||||
|         if self._init and hasattr(self, f"set_{name}"): | ||||
|             getattr(self, f"set_{name}")(value) | ||||
|  | ||||
|     def set_mode(self, value: Union[str, Mode]): | ||||
|     def set_mode(self, value: str | Mode): | ||||
|         if hasattr(self.app, "error_handler"): | ||||
|             self.app.error_handler.debug = self.app.debug | ||||
|         if getattr(self.app, "configure_logging", False) and self.app.debug: | ||||
| @@ -107,9 +103,7 @@ class ApplicationState: | ||||
|  | ||||
|         if all(info.stage is ServerStage.SERVING for info in self.server_info): | ||||
|             return ServerStage.SERVING | ||||
|         elif any( | ||||
|             info.stage is ServerStage.SERVING for info in self.server_info | ||||
|         ): | ||||
|         elif any(info.stage is ServerStage.SERVING for info in self.server_info): | ||||
|             return ServerStage.PARTIAL | ||||
|  | ||||
|         return ServerStage.STOPPED | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import warnings | ||||
|  | ||||
| from typing import TYPE_CHECKING, Optional | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from sanic.compat import Header | ||||
| from sanic.exceptions import BadRequest, ServerError | ||||
| @@ -15,7 +14,6 @@ from sanic.response import BaseHTTPResponse | ||||
| from sanic.server import ConnInfo | ||||
| from sanic.server.websockets.connection import WebSocketConnection | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
| @@ -109,9 +107,9 @@ class ASGIApp: | ||||
|     request: Request | ||||
|     transport: MockTransport | ||||
|     lifespan: Lifespan | ||||
|     ws: Optional[WebSocketConnection] | ||||
|     ws: WebSocketConnection | None | ||||
|     stage: Stage | ||||
|     response: Optional[BaseHTTPResponse] | ||||
|     response: BaseHTTPResponse | None | ||||
|  | ||||
|     @classmethod | ||||
|     async def create( | ||||
| @@ -142,9 +140,7 @@ class ASGIApp: | ||||
|                 ] | ||||
|             ) | ||||
|         except UnicodeDecodeError: | ||||
|             raise BadRequest( | ||||
|                 "Header names can only contain US-ASCII characters" | ||||
|             ) | ||||
|             raise BadRequest("Header names can only contain US-ASCII characters") | ||||
|  | ||||
|         if scope["type"] == "http": | ||||
|             version = scope["http_version"] | ||||
| @@ -153,9 +149,7 @@ class ASGIApp: | ||||
|             version = "1.1" | ||||
|             method = "GET" | ||||
|  | ||||
|             instance.ws = instance.transport.create_websocket_connection( | ||||
|                 send, receive | ||||
|             ) | ||||
|             instance.ws = instance.transport.create_websocket_connection(send, receive) | ||||
|         else: | ||||
|             raise ServerError("Received unknown ASGI scope") | ||||
|  | ||||
| @@ -189,7 +183,7 @@ class ASGIApp: | ||||
|  | ||||
|         return instance | ||||
|  | ||||
|     async def read(self) -> Optional[bytes]: | ||||
|     async def read(self) -> bytes | None: | ||||
|         """ | ||||
|         Read and stream the body in chunks from an incoming ASGI message. | ||||
|         """ | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import re | ||||
|  | ||||
| from typing import Any, Optional | ||||
|  | ||||
| from sanic.base.meta import SanicMeta | ||||
| @@ -11,7 +10,6 @@ from sanic.mixins.routes import RouteMixin | ||||
| from sanic.mixins.signals import SignalMixin | ||||
| from sanic.mixins.static import StaticMixin | ||||
|  | ||||
|  | ||||
| VALID_NAME = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_\-]*$") | ||||
|  | ||||
|  | ||||
| @@ -26,9 +24,7 @@ class BaseSanic( | ||||
| ): | ||||
|     __slots__ = ("name",) | ||||
|  | ||||
|     def __init__( | ||||
|         self, name: Optional[str] = None, *args: Any, **kwargs: Any | ||||
|     ) -> None: | ||||
|     def __init__(self, name: Optional[str] = None, *args: Any, **kwargs: Any) -> None: | ||||
|         class_name = self.__class__.__name__ | ||||
|  | ||||
|         if name is None: | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from .blueprints import BlueprintGroup | ||||
|  | ||||
|  | ||||
| __all__ = ["BlueprintGroup"]  # noqa: F405 | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
| import sys | ||||
|  | ||||
| from collections import defaultdict | ||||
| from collections.abc import MutableSequence | ||||
| from copy import deepcopy | ||||
| @@ -14,15 +13,9 @@ from typing import ( | ||||
|     TYPE_CHECKING, | ||||
|     Any, | ||||
|     Callable, | ||||
|     Dict, | ||||
|     Iterable, | ||||
|     Iterator, | ||||
|     List, | ||||
|     Optional, | ||||
|     Sequence, | ||||
|     Set, | ||||
|     Tuple, | ||||
|     Union, | ||||
|     overload, | ||||
| ) | ||||
|  | ||||
| @@ -39,7 +32,6 @@ from sanic.models.handler_types import ( | ||||
|     RouteHandler, | ||||
| ) | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
| @@ -122,10 +114,10 @@ class Blueprint(BaseSanic): | ||||
|     def __init__( | ||||
|         self, | ||||
|         name: str, | ||||
|         url_prefix: Optional[str] = None, | ||||
|         host: Optional[Union[List[str], str]] = None, | ||||
|         version: Optional[Union[int, str, float]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         url_prefix: str | None = None, | ||||
|         host: list[str] | str | None = None, | ||||
|         version: int | str | float | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         version_prefix: str = "/v", | ||||
|     ): | ||||
|         super().__init__(name=name) | ||||
| @@ -136,9 +128,7 @@ class Blueprint(BaseSanic): | ||||
|         self.host = host | ||||
|         self.strict_slashes = strict_slashes | ||||
|         self.url_prefix = ( | ||||
|             url_prefix[:-1] | ||||
|             if url_prefix and url_prefix.endswith("/") | ||||
|             else url_prefix | ||||
|             url_prefix[:-1] if url_prefix and url_prefix.endswith("/") else url_prefix | ||||
|         ) | ||||
|         self.version = version | ||||
|         self.version_prefix = version_prefix | ||||
| @@ -161,7 +151,7 @@ class Blueprint(BaseSanic): | ||||
|         return f"Blueprint({args})" | ||||
|  | ||||
|     @property | ||||
|     def apps(self) -> Set[Sanic]: | ||||
|     def apps(self) -> set[Sanic]: | ||||
|         """Get the set of apps that this blueprint is registered to. | ||||
|  | ||||
|         Returns: | ||||
| @@ -172,9 +162,7 @@ class Blueprint(BaseSanic): | ||||
|                 an app. | ||||
|         """ | ||||
|         if not self._apps: | ||||
|             raise SanicException( | ||||
|                 f"{self} has not yet been registered to an app" | ||||
|             ) | ||||
|             raise SanicException(f"{self} has not yet been registered to an app") | ||||
|         return self._apps | ||||
|  | ||||
|     @property | ||||
| @@ -196,23 +184,23 @@ class Blueprint(BaseSanic): | ||||
|  | ||||
|     def reset(self) -> None: | ||||
|         """Reset the blueprint to its initial state.""" | ||||
|         self._apps: Set[Sanic] = set() | ||||
|         self._apps: set[Sanic] = set() | ||||
|         self._allow_route_overwrite = False | ||||
|         self.exceptions: List[RouteHandler] = [] | ||||
|         self.listeners: Dict[str, List[ListenerType[Any]]] = {} | ||||
|         self.middlewares: List[MiddlewareType] = [] | ||||
|         self.routes: List[Route] = [] | ||||
|         self.statics: List[RouteHandler] = [] | ||||
|         self.websocket_routes: List[Route] = [] | ||||
|         self.exceptions: list[RouteHandler] = [] | ||||
|         self.listeners: dict[str, list[ListenerType[Any]]] = {} | ||||
|         self.middlewares: list[MiddlewareType] = [] | ||||
|         self.routes: list[Route] = [] | ||||
|         self.statics: list[RouteHandler] = [] | ||||
|         self.websocket_routes: list[Route] = [] | ||||
|  | ||||
|     def copy( | ||||
|         self, | ||||
|         name: str, | ||||
|         url_prefix: Optional[Union[str, Default]] = _default, | ||||
|         version: Optional[Union[int, str, float, Default]] = _default, | ||||
|         version_prefix: Union[str, Default] = _default, | ||||
|         allow_route_overwrite: Union[bool, Default] = _default, | ||||
|         strict_slashes: Optional[Union[bool, Default]] = _default, | ||||
|         url_prefix: str | Default | None = _default, | ||||
|         version: int | str | float | Default | None = _default, | ||||
|         version_prefix: str | Default = _default, | ||||
|         allow_route_overwrite: bool | Default = _default, | ||||
|         strict_slashes: bool | Default | None = _default, | ||||
|         with_registration: bool = True, | ||||
|         with_ctx: bool = False, | ||||
|     ): | ||||
| @@ -277,12 +265,12 @@ class Blueprint(BaseSanic): | ||||
|  | ||||
|     @staticmethod | ||||
|     def group( | ||||
|         *blueprints: Union[Blueprint, BlueprintGroup], | ||||
|         url_prefix: Optional[str] = None, | ||||
|         version: Optional[Union[int, str, float]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         *blueprints: Blueprint | BlueprintGroup, | ||||
|         url_prefix: str | None = None, | ||||
|         version: int | str | float | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         version_prefix: str = "/v", | ||||
|         name_prefix: Optional[str] = "", | ||||
|         name_prefix: str | None = "", | ||||
|     ) -> BlueprintGroup: | ||||
|         """Group multiple blueprints (or other blueprint groups) together. | ||||
|  | ||||
| @@ -353,9 +341,7 @@ class Blueprint(BaseSanic): | ||||
|         opt_strict_slashes = options.get("strict_slashes", None) | ||||
|         opt_version_prefix = options.get("version_prefix", self.version_prefix) | ||||
|         opt_name_prefix = options.get("name_prefix", None) | ||||
|         error_format = options.get( | ||||
|             "error_format", app.config.FALLBACK_ERROR_FORMAT | ||||
|         ) | ||||
|         error_format = options.get("error_format", app.config.FALLBACK_ERROR_FORMAT) | ||||
|  | ||||
|         routes = [] | ||||
|         middleware = [] | ||||
| @@ -381,9 +367,7 @@ class Blueprint(BaseSanic): | ||||
|                     version_prefix = prefix | ||||
|                     break | ||||
|  | ||||
|             version = self._extract_value( | ||||
|                 future.version, opt_version, self.version | ||||
|             ) | ||||
|             version = self._extract_value(future.version, opt_version, self.version) | ||||
|             strict_slashes = self._extract_value( | ||||
|                 future.strict_slashes, opt_strict_slashes, self.strict_slashes | ||||
|             ) | ||||
| @@ -419,22 +403,16 @@ class Blueprint(BaseSanic): | ||||
|                 continue | ||||
|  | ||||
|             registered.add(apply_route) | ||||
|             route = app._apply_route( | ||||
|                 apply_route, overwrite=self._allow_route_overwrite | ||||
|             ) | ||||
|             route = app._apply_route(apply_route, overwrite=self._allow_route_overwrite) | ||||
|  | ||||
|             # If it is a copied BP, then make sure all of the names of routes | ||||
|             # matchup with the new BP name | ||||
|             if self.copied_from: | ||||
|                 for r in route: | ||||
|                     r.name = r.name.replace(self.copied_from, self.name) | ||||
|                     r.extra.ident = r.extra.ident.replace( | ||||
|                         self.copied_from, self.name | ||||
|                     ) | ||||
|                     r.extra.ident = r.extra.ident.replace(self.copied_from, self.name) | ||||
|  | ||||
|             operation = ( | ||||
|                 routes.extend if isinstance(route, list) else routes.append | ||||
|             ) | ||||
|             operation = routes.extend if isinstance(route, list) else routes.append | ||||
|             operation(route) | ||||
|  | ||||
|         # Static Files | ||||
| @@ -479,7 +457,7 @@ class Blueprint(BaseSanic): | ||||
|                 continue | ||||
|             future.condition.update({"__blueprint__": self.name}) | ||||
|             # Force exclusive to be False | ||||
|             app._apply_signal(tuple((*future[:-1], False))) | ||||
|             app._apply_signal((*future[:-1], False)) | ||||
|  | ||||
|         self.routes += [route for route in routes if isinstance(route, Route)] | ||||
|         self.websocket_routes += [ | ||||
| @@ -512,11 +490,9 @@ class Blueprint(BaseSanic): | ||||
|         condition = kwargs.pop("condition", {}) | ||||
|         condition.update({"__blueprint__": self.name}) | ||||
|         kwargs["condition"] = condition | ||||
|         await asyncio.gather( | ||||
|             *[app.dispatch(*args, **kwargs) for app in self.apps] | ||||
|         ) | ||||
|         await asyncio.gather(*[app.dispatch(*args, **kwargs) for app in self.apps]) | ||||
|  | ||||
|     def event(self, event: str, timeout: Optional[Union[int, float]] = None): | ||||
|     def event(self, event: str, timeout: int | float | None = None): | ||||
|         """Wait for a signal event to be dispatched. | ||||
|  | ||||
|         Args: | ||||
| @@ -550,7 +526,7 @@ class Blueprint(BaseSanic): | ||||
|         return value | ||||
|  | ||||
|     @staticmethod | ||||
|     def _setup_uri(base: str, prefix: Optional[str]): | ||||
|     def _setup_uri(base: str, prefix: str | None): | ||||
|         uri = base | ||||
|         if prefix: | ||||
|             uri = prefix | ||||
| @@ -563,7 +539,7 @@ class Blueprint(BaseSanic): | ||||
|  | ||||
|     @staticmethod | ||||
|     def register_futures( | ||||
|         apps: Set[Sanic], bp: Blueprint, futures: Sequence[Tuple[Any, ...]] | ||||
|         apps: set[Sanic], bp: Blueprint, futures: Sequence[tuple[Any, ...]] | ||||
|     ): | ||||
|         """Register futures to the apps. | ||||
|  | ||||
| @@ -575,7 +551,7 @@ class Blueprint(BaseSanic): | ||||
|         """ | ||||
|  | ||||
|         for app in apps: | ||||
|             app._future_registry.update(set((bp, item) for item in futures)) | ||||
|             app._future_registry.update({(bp, item) for item in futures}) | ||||
|  | ||||
|  | ||||
| if sys.version_info < (3, 9): | ||||
| @@ -667,13 +643,13 @@ class BlueprintGroup(bpg_base): | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         url_prefix: Optional[str] = None, | ||||
|         version: Optional[Union[int, str, float]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         url_prefix: str | None = None, | ||||
|         version: int | str | float | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         version_prefix: str = "/v", | ||||
|         name_prefix: Optional[str] = "", | ||||
|         name_prefix: str | None = "", | ||||
|     ): | ||||
|         self._blueprints: List[Blueprint] = [] | ||||
|         self._blueprints: list[Blueprint] = [] | ||||
|         self._url_prefix = url_prefix | ||||
|         self._version = version | ||||
|         self._version_prefix = version_prefix | ||||
| @@ -681,7 +657,7 @@ class BlueprintGroup(bpg_base): | ||||
|         self._name_prefix = name_prefix | ||||
|  | ||||
|     @property | ||||
|     def url_prefix(self) -> Optional[Union[int, str, float]]: | ||||
|     def url_prefix(self) -> int | str | float | None: | ||||
|         """The URL prefix for the Blueprint Group. | ||||
|  | ||||
|         Returns: | ||||
| @@ -691,7 +667,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._url_prefix | ||||
|  | ||||
|     @property | ||||
|     def blueprints(self) -> List[Blueprint]: | ||||
|     def blueprints(self) -> list[Blueprint]: | ||||
|         """A list of all the available blueprints under this group. | ||||
|  | ||||
|         Returns: | ||||
| @@ -701,7 +677,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._blueprints | ||||
|  | ||||
|     @property | ||||
|     def version(self) -> Optional[Union[str, int, float]]: | ||||
|     def version(self) -> str | int | float | None: | ||||
|         """API Version for the Blueprint Group, if any. | ||||
|  | ||||
|         Returns: | ||||
| @@ -710,7 +686,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._version | ||||
|  | ||||
|     @property | ||||
|     def strict_slashes(self) -> Optional[bool]: | ||||
|     def strict_slashes(self) -> bool | None: | ||||
|         """Whether to enforce strict slashes for the Blueprint Group. | ||||
|  | ||||
|         Returns: | ||||
| @@ -728,7 +704,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._version_prefix | ||||
|  | ||||
|     @property | ||||
|     def name_prefix(self) -> Optional[str]: | ||||
|     def name_prefix(self) -> str | None: | ||||
|         """Name prefix for the Blueprint Group. | ||||
|  | ||||
|         This is mainly needed when blueprints are copied in order to | ||||
| @@ -755,9 +731,7 @@ class BlueprintGroup(bpg_base): | ||||
|     def __getitem__(self, item: slice) -> MutableSequence[Blueprint]: | ||||
|         ... | ||||
|  | ||||
|     def __getitem__( | ||||
|         self, item: Union[int, slice] | ||||
|     ) -> Union[Blueprint, MutableSequence[Blueprint]]: | ||||
|     def __getitem__(self, item: int | slice) -> Blueprint | MutableSequence[Blueprint]: | ||||
|         """Get the Blueprint object at the specified index. | ||||
|  | ||||
|         This method returns a blueprint inside the group specified by | ||||
| @@ -785,8 +759,8 @@ class BlueprintGroup(bpg_base): | ||||
|  | ||||
|     def __setitem__( | ||||
|         self, | ||||
|         index: Union[int, slice], | ||||
|         item: Union[Blueprint, Iterable[Blueprint]], | ||||
|         index: int | slice, | ||||
|         item: Blueprint | Iterable[Blueprint], | ||||
|     ) -> None: | ||||
|         """Set the Blueprint object at the specified index. | ||||
|  | ||||
| @@ -824,7 +798,7 @@ class BlueprintGroup(bpg_base): | ||||
|     def __delitem__(self, index: slice) -> None: | ||||
|         ... | ||||
|  | ||||
|     def __delitem__(self, index: Union[int, slice]) -> None: | ||||
|     def __delitem__(self, index: int | slice) -> None: | ||||
|         """Delete the Blueprint object at the specified index. | ||||
|  | ||||
|         Abstract method implemented to turn the `BlueprintGroup` class | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import os | ||||
| import shutil | ||||
| import sys | ||||
|  | ||||
| from argparse import Namespace | ||||
| from functools import partial | ||||
| from textwrap import indent | ||||
| @@ -57,9 +56,7 @@ Or, a path to a directory to run as a simple HTTP server: | ||||
|         ) | ||||
|         self.parser._positionals.title = "Required\n========\n  Positional" | ||||
|         self.parser._optionals.title = "Optional\n========\n  General" | ||||
|         self.main_process = ( | ||||
|             os.environ.get("SANIC_RELOADER_PROCESS", "") != "true" | ||||
|         ) | ||||
|         self.main_process = os.environ.get("SANIC_RELOADER_PROCESS", "") != "true" | ||||
|         self.args: Namespace = Namespace() | ||||
|         self.groups: List[Group] = [] | ||||
|         self.inspecting = False | ||||
| @@ -127,11 +124,7 @@ Or, a path to a directory to run as a simple HTTP server: | ||||
|                         key = key.lstrip("-") | ||||
|                     except ValueError: | ||||
|                         value = False if arg.startswith("--no-") else True | ||||
|                         key = ( | ||||
|                             arg.replace("--no-", "") | ||||
|                             .lstrip("-") | ||||
|                             .replace("-", "_") | ||||
|                         ) | ||||
|                         key = arg.replace("--no-", "").lstrip("-").replace("-", "_") | ||||
|                     setattr(self.args, key, value) | ||||
|  | ||||
|         kwargs = {**self.args.__dict__} | ||||
| @@ -181,8 +174,7 @@ Or, a path to a directory to run as a simple HTTP server: | ||||
|                     "  Example Module: project.sanic_server.app" | ||||
|                 ) | ||||
|                 error_logger.error( | ||||
|                     "\nThe error below might have caused the above one:\n" | ||||
|                     f"{e.msg}" | ||||
|                     "\nThe error below might have caused the above one:\n" f"{e.msg}" | ||||
|                 ) | ||||
|                 sys.exit(1) | ||||
|             else: | ||||
| @@ -196,7 +188,7 @@ Or, a path to a directory to run as a simple HTTP server: | ||||
|         if self.args.tlshost: | ||||
|             ssl.append(None) | ||||
|         if self.args.cert is not None or self.args.key is not None: | ||||
|             ssl.append(dict(cert=self.args.cert, key=self.args.key)) | ||||
|             ssl.append({"cert": self.args.cert, "key": self.args.key}) | ||||
|         if self.args.tls: | ||||
|             ssl += self.args.tls | ||||
|         if not ssl: | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from argparse import ArgumentParser, _ArgumentGroup | ||||
| from typing import List, Optional, Type, Union | ||||
|  | ||||
| from sanic_routing import __version__ as __routing_version__ | ||||
|  | ||||
| @@ -10,14 +9,14 @@ from sanic.http.constants import HTTP | ||||
|  | ||||
|  | ||||
| class Group: | ||||
|     name: Optional[str] | ||||
|     container: Union[ArgumentParser, _ArgumentGroup] | ||||
|     _registry: List[Type[Group]] = [] | ||||
|     name: str | None | ||||
|     container: ArgumentParser | _ArgumentGroup | ||||
|     _registry: list[type[Group]] = [] | ||||
|  | ||||
|     def __init_subclass__(cls) -> None: | ||||
|         Group._registry.append(cls) | ||||
|  | ||||
|     def __init__(self, parser: ArgumentParser, title: Optional[str]): | ||||
|     def __init__(self, parser: ArgumentParser, title: str | None): | ||||
|         self.parser = parser | ||||
|  | ||||
|         if title: | ||||
| @@ -245,10 +244,7 @@ class DevelopmentGroup(Group): | ||||
|             "--auto-reload", | ||||
|             dest="auto_reload", | ||||
|             action="store_true", | ||||
|             help=( | ||||
|                 "Watch source directory for file changes and reload on " | ||||
|                 "changes" | ||||
|             ), | ||||
|             help=("Watch source directory for file changes and reload on " "changes"), | ||||
|         ) | ||||
|         self.container.add_argument( | ||||
|             "-R", | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from http.client import RemoteDisconnected | ||||
| from textwrap import indent | ||||
| from typing import Any, Dict, Optional | ||||
| from typing import Any | ||||
| from urllib.error import URLError | ||||
| from urllib.request import Request as URequest | ||||
| from urllib.request import urlopen | ||||
| @@ -13,7 +12,6 @@ from sanic.application.logo import get_logo | ||||
| from sanic.application.motd import MOTDTTY | ||||
| from sanic.log import Colors | ||||
|  | ||||
|  | ||||
| try:  # no cov | ||||
|     from ujson import dumps, loads | ||||
| except ModuleNotFoundError:  # no cov | ||||
| @@ -27,7 +25,7 @@ class InspectorClient: | ||||
|         port: int, | ||||
|         secure: bool, | ||||
|         raw: bool, | ||||
|         api_key: Optional[str], | ||||
|         api_key: str | None, | ||||
|     ) -> None: | ||||
|         self.scheme = "https" if secure else "http" | ||||
|         self.host = host | ||||
| @@ -47,11 +45,7 @@ class InspectorClient: | ||||
|             return | ||||
|         result = self.request(action, **kwargs).get("result") | ||||
|         if result: | ||||
|             out = ( | ||||
|                 dumps(result) | ||||
|                 if isinstance(result, (list, dict)) | ||||
|                 else str(result) | ||||
|             ) | ||||
|             out = dumps(result) if isinstance(result, (list, dict)) else str(result) | ||||
|             sys.stdout.write(out + "\n") | ||||
|  | ||||
|     def info(self) -> None: | ||||
| @@ -89,7 +83,7 @@ class InspectorClient: | ||||
|  | ||||
|     def request(self, action: str, method: str = "POST", **kwargs: Any) -> Any: | ||||
|         url = f"{self.base_url}/{action}" | ||||
|         params: Dict[str, Any] = {"method": method, "headers": {}} | ||||
|         params: dict[str, Any] = {"method": method, "headers": {}} | ||||
|         if kwargs: | ||||
|             params["data"] = dumps(kwargs).encode() | ||||
|             params["headers"]["content-type"] = "application/json" | ||||
|   | ||||
| @@ -3,25 +3,16 @@ import os | ||||
| import platform | ||||
| import signal | ||||
| import sys | ||||
|  | ||||
| from contextlib import contextmanager | ||||
| from enum import Enum | ||||
| from typing import Awaitable, Union | ||||
| from typing import Awaitable, Literal, Union | ||||
|  | ||||
| from multidict import CIMultiDict  # type: ignore | ||||
|  | ||||
| from sanic.helpers import Default | ||||
| from sanic.log import error_logger | ||||
|  | ||||
|  | ||||
| if sys.version_info < (3, 8):  # no cov | ||||
|     StartMethod = Union[Default, str] | ||||
| else:  # no cov | ||||
|     from typing import Literal | ||||
|  | ||||
|     StartMethod = Union[ | ||||
|         Default, Literal["fork"], Literal["forkserver"], Literal["spawn"] | ||||
|     ] | ||||
| StartMethod = Union[Default, Literal["fork"], Literal["forkserver"], Literal["spawn"]] | ||||
|  | ||||
| OS_IS_WINDOWS = os.name == "nt" | ||||
| PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy" | ||||
| @@ -142,7 +133,10 @@ if use_trio:  # pragma: no cover | ||||
|         return trio.Path(path).stat() | ||||
|  | ||||
|     open_async = trio.open_file | ||||
|     CancelledErrors = tuple([asyncio.CancelledError, trio.Cancelled]) | ||||
|     CancelledErrors: tuple[type[BaseException], ...] = ( | ||||
|         asyncio.CancelledError, | ||||
|         trio.Cancelled, | ||||
|     ) | ||||
| else: | ||||
|     if PYPY_IMPLEMENTATION: | ||||
|         pypy_os_module_patch() | ||||
| @@ -156,7 +150,7 @@ else: | ||||
|     async def open_async(file, mode="r", **kwargs): | ||||
|         return aio_open(file, mode, **kwargs) | ||||
|  | ||||
|     CancelledErrors = tuple([asyncio.CancelledError]) | ||||
|     CancelledErrors = (asyncio.CancelledError,) | ||||
|  | ||||
|  | ||||
| def ctrlc_workaround_for_windows(app): | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from abc import ABCMeta | ||||
| from inspect import getmembers, isclass, isdatadescriptor | ||||
| from os import environ | ||||
| from pathlib import Path | ||||
| from typing import Any, Callable, Dict, Optional, Sequence, Union | ||||
| from typing import Any, Callable, Literal, Sequence, Union | ||||
| from warnings import filterwarnings | ||||
|  | ||||
| from sanic.constants import LocalCertCreator | ||||
| @@ -16,20 +14,14 @@ from sanic.http import Http | ||||
| from sanic.log import error_logger | ||||
| from sanic.utils import load_module_from_file_location, str_to_bool | ||||
|  | ||||
|  | ||||
| if sys.version_info >= (3, 8): | ||||
|     from typing import Literal | ||||
|  | ||||
|     FilterWarningType = Union[ | ||||
|         Literal["default"], | ||||
|         Literal["error"], | ||||
|         Literal["ignore"], | ||||
|         Literal["always"], | ||||
|         Literal["module"], | ||||
|         Literal["once"], | ||||
|     ] | ||||
| else: | ||||
|     FilterWarningType = str | ||||
| FilterWarningType = Union[ | ||||
|     Literal["default"], | ||||
|     Literal["error"], | ||||
|     Literal["ignore"], | ||||
|     Literal["always"], | ||||
|     Literal["module"], | ||||
|     Literal["once"], | ||||
| ] | ||||
|  | ||||
| SANIC_PREFIX = "SANIC_" | ||||
|  | ||||
| @@ -100,25 +92,25 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|     EVENT_AUTOREGISTER: bool | ||||
|     DEPRECATION_FILTER: FilterWarningType | ||||
|     FORWARDED_FOR_HEADER: str | ||||
|     FORWARDED_SECRET: Optional[str] | ||||
|     FORWARDED_SECRET: str | None | ||||
|     GRACEFUL_SHUTDOWN_TIMEOUT: float | ||||
|     INSPECTOR: bool | ||||
|     INSPECTOR_HOST: str | ||||
|     INSPECTOR_PORT: int | ||||
|     INSPECTOR_TLS_KEY: Union[Path, str, Default] | ||||
|     INSPECTOR_TLS_CERT: Union[Path, str, Default] | ||||
|     INSPECTOR_TLS_KEY: Path | str | Default | ||||
|     INSPECTOR_TLS_CERT: Path | str | Default | ||||
|     INSPECTOR_API_KEY: str | ||||
|     KEEP_ALIVE_TIMEOUT: int | ||||
|     KEEP_ALIVE: bool | ||||
|     LOCAL_CERT_CREATOR: Union[str, LocalCertCreator] | ||||
|     LOCAL_TLS_KEY: Union[Path, str, Default] | ||||
|     LOCAL_TLS_CERT: Union[Path, str, Default] | ||||
|     LOCAL_CERT_CREATOR: str | LocalCertCreator | ||||
|     LOCAL_TLS_KEY: Path | str | Default | ||||
|     LOCAL_TLS_CERT: Path | str | Default | ||||
|     LOCALHOST: str | ||||
|     MOTD: bool | ||||
|     MOTD_DISPLAY: Dict[str, str] | ||||
|     MOTD_DISPLAY: dict[str, str] | ||||
|     NOISY_EXCEPTIONS: bool | ||||
|     PROXIES_COUNT: Optional[int] | ||||
|     REAL_IP_HEADER: Optional[str] | ||||
|     PROXIES_COUNT: int | None | ||||
|     REAL_IP_HEADER: str | None | ||||
|     REQUEST_BUFFER_SIZE: int | ||||
|     REQUEST_MAX_HEADER_SIZE: int | ||||
|     REQUEST_ID_HEADER: str | ||||
| @@ -127,21 +119,19 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|     RESPONSE_TIMEOUT: int | ||||
|     SERVER_NAME: str | ||||
|     TLS_CERT_PASSWORD: str | ||||
|     TOUCHUP: Union[Default, bool] | ||||
|     USE_UVLOOP: Union[Default, bool] | ||||
|     TOUCHUP: Default | bool | ||||
|     USE_UVLOOP: Default | bool | ||||
|     WEBSOCKET_MAX_SIZE: int | ||||
|     WEBSOCKET_PING_INTERVAL: int | ||||
|     WEBSOCKET_PING_TIMEOUT: int | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         defaults: Optional[ | ||||
|             Dict[str, Union[str, bool, int, float, None]] | ||||
|         ] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         keep_alive: Optional[bool] = None, | ||||
|         defaults: dict[str, str | bool | int | float | None] | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         keep_alive: bool | None = None, | ||||
|         *, | ||||
|         converters: Optional[Sequence[Callable[[str], Any]]] = None, | ||||
|         converters: Sequence[Callable[[str], Any]] | None = None, | ||||
|     ): | ||||
|         defaults = defaults or {} | ||||
|         super().__init__({**DEFAULT_CONFIG, **defaults}) | ||||
| @@ -209,7 +199,7 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|             ``` | ||||
|         """ | ||||
|         kwargs.update({k: v for item in other for k, v in dict(item).items()}) | ||||
|         setters: Dict[str, Any] = { | ||||
|         setters: dict[str, Any] = { | ||||
|             k: kwargs.pop(k) | ||||
|             for k in {**kwargs}.keys() | ||||
|             if k in self.__class__.__setters__ | ||||
| @@ -237,9 +227,7 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|         if attr == "LOCAL_CERT_CREATOR" and not isinstance( | ||||
|             self.LOCAL_CERT_CREATOR, LocalCertCreator | ||||
|         ): | ||||
|             self.LOCAL_CERT_CREATOR = LocalCertCreator[ | ||||
|                 self.LOCAL_CERT_CREATOR.upper() | ||||
|             ] | ||||
|             self.LOCAL_CERT_CREATOR = LocalCertCreator[self.LOCAL_CERT_CREATOR.upper()] | ||||
|         elif attr == "DEPRECATION_FILTER": | ||||
|             self._configure_warnings() | ||||
|  | ||||
| @@ -276,7 +264,7 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|             module=r"sanic.*", | ||||
|         ) | ||||
|  | ||||
|     def _check_error_format(self, format: Optional[str] = None): | ||||
|     def _check_error_format(self, format: str | None = None): | ||||
|         check_error_format(format or self.FALLBACK_ERROR_FORMAT) | ||||
|  | ||||
|     def load_environment_vars(self, prefix=SANIC_PREFIX): | ||||
| @@ -332,7 +320,7 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|                 except ValueError: | ||||
|                     pass | ||||
|  | ||||
|     def update_config(self, config: Union[bytes, str, dict, Any]): | ||||
|     def update_config(self, config: bytes | str | dict | Any): | ||||
|         """Update app.config. | ||||
|  | ||||
|         .. note:: | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from .response import Cookie, CookieJar | ||||
|  | ||||
|  | ||||
| __all__ = ("Cookie", "CookieJar") | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| import re | ||||
|  | ||||
| from typing import Any, Dict, List, Optional | ||||
|  | ||||
| from sanic.cookies.response import Cookie | ||||
| from sanic.log import deprecation | ||||
| from sanic.request.parameters import RequestParameters | ||||
|  | ||||
|  | ||||
| COOKIE_NAME_RESERVED_CHARS = re.compile( | ||||
|     '[\x00-\x1F\x7F-\xFF()<>@,;:\\\\"/[\\]?={} \x09]' | ||||
| ) | ||||
| @@ -149,9 +147,7 @@ class CookieRequestParameters(RequestParameters): | ||||
|         except KeyError: | ||||
|             return super().get(name, default) | ||||
|  | ||||
|     def getlist( | ||||
|         self, name: str, default: Optional[Any] = None | ||||
|     ) -> Optional[Any]: | ||||
|     def getlist(self, name: str, default: Optional[Any] = None) -> Optional[Any]: | ||||
|         try: | ||||
|             return self._get_prefixed_cookie(name) | ||||
|         except KeyError: | ||||
|   | ||||
| @@ -2,31 +2,25 @@ from __future__ import annotations | ||||
|  | ||||
| import re | ||||
| import string | ||||
| import sys | ||||
|  | ||||
| from datetime import datetime | ||||
| from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union | ||||
| from typing import TYPE_CHECKING, Any, Union | ||||
|  | ||||
| from sanic.exceptions import ServerError | ||||
| from sanic.log import deprecation | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic.compat import Header | ||||
|  | ||||
| if sys.version_info < (3, 8):  # no cov | ||||
|     SameSite = str | ||||
| else:  # no cov | ||||
|     from typing import Literal | ||||
| from typing import Literal | ||||
|  | ||||
|     SameSite = Union[ | ||||
|         Literal["Strict"], | ||||
|         Literal["Lax"], | ||||
|         Literal["None"], | ||||
|         Literal["strict"], | ||||
|         Literal["lax"], | ||||
|         Literal["none"], | ||||
|     ] | ||||
| SameSite = Union[ | ||||
|     Literal["Strict"], | ||||
|     Literal["Lax"], | ||||
|     Literal["None"], | ||||
|     Literal["strict"], | ||||
|     Literal["lax"], | ||||
|     Literal["none"], | ||||
| ] | ||||
|  | ||||
| DEFAULT_MAX_AGE = 0 | ||||
| SAMESITE_VALUES = ("strict", "lax", "none") | ||||
| @@ -180,7 +174,7 @@ class CookieJar(dict): | ||||
|         return CookieJar.HEADER_KEY | ||||
|  | ||||
|     @property | ||||
|     def cookie_headers(self) -> Dict[str, str]:  # no cov | ||||
|     def cookie_headers(self) -> dict[str, str]:  # no cov | ||||
|         """Deprecated in v24.3""" | ||||
|         deprecation( | ||||
|             "The CookieJar.coookie_headers property has been deprecated " | ||||
| @@ -191,7 +185,7 @@ class CookieJar(dict): | ||||
|         return {key: self.header_key for key in self} | ||||
|  | ||||
|     @property | ||||
|     def cookies(self) -> List[Cookie]: | ||||
|     def cookies(self) -> list[Cookie]: | ||||
|         """A list of cookies in the CookieJar. | ||||
|  | ||||
|         Returns: | ||||
| @@ -203,10 +197,10 @@ class CookieJar(dict): | ||||
|         self, | ||||
|         key: str, | ||||
|         path: str = "/", | ||||
|         domain: Optional[str] = None, | ||||
|         domain: str | None = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> Optional[Cookie]: | ||||
|     ) -> Cookie | None: | ||||
|         """Fetch a cookie from the CookieJar. | ||||
|  | ||||
|         Args: | ||||
| @@ -236,7 +230,7 @@ class CookieJar(dict): | ||||
|         self, | ||||
|         key: str, | ||||
|         path: str = "/", | ||||
|         domain: Optional[str] = None, | ||||
|         domain: str | None = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> bool: | ||||
| @@ -271,14 +265,14 @@ class CookieJar(dict): | ||||
|         value: str, | ||||
|         *, | ||||
|         path: str = "/", | ||||
|         domain: Optional[str] = None, | ||||
|         domain: str | None = None, | ||||
|         secure: bool = True, | ||||
|         max_age: Optional[int] = None, | ||||
|         expires: Optional[datetime] = None, | ||||
|         max_age: int | None = None, | ||||
|         expires: datetime | None = None, | ||||
|         httponly: bool = False, | ||||
|         samesite: Optional[SameSite] = "Lax", | ||||
|         samesite: SameSite | None = "Lax", | ||||
|         partitioned: bool = False, | ||||
|         comment: Optional[str] = None, | ||||
|         comment: str | None = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> Cookie: | ||||
| @@ -362,7 +356,7 @@ class CookieJar(dict): | ||||
|         key: str, | ||||
|         *, | ||||
|         path: str = "/", | ||||
|         domain: Optional[str] = None, | ||||
|         domain: str | None = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> None: | ||||
| @@ -390,7 +384,7 @@ class CookieJar(dict): | ||||
|         :type secure_prefix: bool | ||||
|         """ | ||||
|         # remove it from header | ||||
|         cookies: List[Cookie] = self.headers.popall(self.HEADER_KEY, []) | ||||
|         cookies: list[Cookie] = self.headers.popall(self.HEADER_KEY, []) | ||||
|         for cookie in cookies: | ||||
|             if ( | ||||
|                 cookie.key != Cookie.make_key(key, host_prefix, secure_prefix) | ||||
| @@ -481,14 +475,14 @@ class Cookie(dict): | ||||
|         value: str, | ||||
|         *, | ||||
|         path: str = "/", | ||||
|         domain: Optional[str] = None, | ||||
|         domain: str | None = None, | ||||
|         secure: bool = True, | ||||
|         max_age: Optional[int] = None, | ||||
|         expires: Optional[datetime] = None, | ||||
|         max_age: int | None = None, | ||||
|         expires: datetime | None = None, | ||||
|         httponly: bool = False, | ||||
|         samesite: Optional[SameSite] = "Lax", | ||||
|         samesite: SameSite | None = "Lax", | ||||
|         partitioned: bool = False, | ||||
|         comment: Optional[str] = None, | ||||
|         comment: str | None = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ): | ||||
| @@ -502,9 +496,7 @@ class Cookie(dict): | ||||
|                     "Cannot set host_prefix on a cookie without secure=True" | ||||
|                 ) | ||||
|             if path != "/": | ||||
|                 raise ServerError( | ||||
|                     "Cannot set host_prefix on a cookie unless path='/'" | ||||
|                 ) | ||||
|                 raise ServerError("Cannot set host_prefix on a cookie unless path='/'") | ||||
|             if domain: | ||||
|                 raise ServerError( | ||||
|                     "Cannot set host_prefix on a cookie with a defined domain" | ||||
| @@ -561,7 +553,7 @@ class Cookie(dict): | ||||
|     # in v24.3 when this is no longer a dict | ||||
|     def _set_value(self, key: str, value: Any) -> None: | ||||
|         if key not in self._keys: | ||||
|             raise KeyError("Unknown cookie property: %s=%s" % (key, value)) | ||||
|             raise KeyError(f"Unknown cookie property: {key}={value}") | ||||
|  | ||||
|         if value is not None: | ||||
|             if key.lower() == "max-age" and not str(value).isdigit(): | ||||
| @@ -604,21 +596,18 @@ class Cookie(dict): | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Format as a Set-Cookie header value.""" | ||||
|         output = ["%s=%s" % (self.key, _quote(self.value))] | ||||
|         output = [f"{self.key}={_quote(self.value)}"] | ||||
|         key_index = list(self._keys) | ||||
|         for key, value in sorted( | ||||
|             self.items(), key=lambda x: key_index.index(x[0]) | ||||
|         ): | ||||
|         for key, value in sorted(self.items(), key=lambda x: key_index.index(x[0])): | ||||
|             if value is not None and value is not False: | ||||
|                 if key == "max-age": | ||||
|                     try: | ||||
|                         output.append("%s=%d" % (self._keys[key], value)) | ||||
|                     except TypeError: | ||||
|                         output.append("%s=%s" % (self._keys[key], value)) | ||||
|                         output.append(f"{self._keys[key]}={value}") | ||||
|                 elif key == "expires": | ||||
|                     output.append( | ||||
|                         "%s=%s" | ||||
|                         % ( | ||||
|                         "{}={}".format( | ||||
|                             self._keys[key], | ||||
|                             value.strftime("%a, %d-%b-%Y %T GMT"), | ||||
|                         ) | ||||
| @@ -626,7 +615,7 @@ class Cookie(dict): | ||||
|                 elif key in self._flags: | ||||
|                     output.append(self._keys[key]) | ||||
|                 else: | ||||
|                     output.append("%s=%s" % (self._keys[key], value)) | ||||
|                     output.append(f"{self._keys[key]}={value}") | ||||
|  | ||||
|         return "; ".join(output) | ||||
|  | ||||
| @@ -640,7 +629,7 @@ class Cookie(dict): | ||||
|         self._set_value("path", value) | ||||
|  | ||||
|     @property | ||||
|     def expires(self) -> Optional[datetime]:  # no cov | ||||
|     def expires(self) -> datetime | None:  # no cov | ||||
|         """The expiration date of the cookie. Defaults to `None`.""" | ||||
|         return self.get("expires") | ||||
|  | ||||
| @@ -649,7 +638,7 @@ class Cookie(dict): | ||||
|         self._set_value("expires", value) | ||||
|  | ||||
|     @property | ||||
|     def comment(self) -> Optional[str]:  # no cov | ||||
|     def comment(self) -> str | None:  # no cov | ||||
|         """A comment for the cookie. Defaults to `None`.""" | ||||
|         return self.get("comment") | ||||
|  | ||||
| @@ -658,7 +647,7 @@ class Cookie(dict): | ||||
|         self._set_value("comment", value) | ||||
|  | ||||
|     @property | ||||
|     def domain(self) -> Optional[str]:  # no cov | ||||
|     def domain(self) -> str | None:  # no cov | ||||
|         """The domain of the cookie. Defaults to `None`.""" | ||||
|         return self.get("domain") | ||||
|  | ||||
| @@ -667,7 +656,7 @@ class Cookie(dict): | ||||
|         self._set_value("domain", value) | ||||
|  | ||||
|     @property | ||||
|     def max_age(self) -> Optional[int]:  # no cov | ||||
|     def max_age(self) -> int | None:  # no cov | ||||
|         """The maximum age of the cookie in seconds. Defaults to `None`.""" | ||||
|         return self.get("max-age") | ||||
|  | ||||
| @@ -694,7 +683,7 @@ class Cookie(dict): | ||||
|         self._set_value("httponly", value) | ||||
|  | ||||
|     @property | ||||
|     def samesite(self) -> Optional[SameSite]:  # no cov | ||||
|     def samesite(self) -> SameSite | None:  # no cov | ||||
|         """The SameSite attribute for the cookie. Defaults to `"Lax"`.""" | ||||
|         return self.get("samesite") | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,6 @@ from __future__ import annotations | ||||
|  | ||||
| import sys | ||||
| import typing as t | ||||
|  | ||||
| from functools import partial | ||||
| from traceback import extract_tb | ||||
|  | ||||
| @@ -26,7 +25,6 @@ from sanic.log import deprecation, logger | ||||
| from sanic.pages.error import ErrorPage | ||||
| from sanic.response import html, json, text | ||||
|  | ||||
|  | ||||
| dumps: t.Callable[..., str] | ||||
| try: | ||||
|     from ujson import dumps | ||||
| @@ -73,7 +71,7 @@ class BaseRenderer: | ||||
|         self.debug = debug | ||||
|  | ||||
|     @property | ||||
|     def headers(self) -> t.Dict[str, str]: | ||||
|     def headers(self) -> dict[str, str]: | ||||
|         """The headers to be used for the response.""" | ||||
|         if isinstance(self.exception, SanicException): | ||||
|             return getattr(self.exception, "headers", {}) | ||||
| @@ -192,8 +190,7 @@ class TextRenderer(BaseRenderer): | ||||
|             lines += [ | ||||
|                 f"{self.exception.__class__.__name__}: {self.exception} while " | ||||
|                 f"handling path {self.request.path}", | ||||
|                 f"Traceback of {self.request.app.name} " | ||||
|                 "(most recent call last):\n", | ||||
|                 f"Traceback of {self.request.app.name} " "(most recent call last):\n", | ||||
|             ] | ||||
|  | ||||
|             while exc_value: | ||||
| @@ -326,8 +323,8 @@ def exception_response( | ||||
|     exception: Exception, | ||||
|     debug: bool, | ||||
|     fallback: str, | ||||
|     base: t.Type[BaseRenderer], | ||||
|     renderer: t.Optional[t.Type[BaseRenderer]] = None, | ||||
|     base: type[BaseRenderer], | ||||
|     renderer: type[BaseRenderer] | None = None, | ||||
| ) -> HTTPResponse: | ||||
|     """Render a response for the default FALLBACK exception handler.""" | ||||
|     if not renderer: | ||||
| @@ -390,9 +387,7 @@ def guess_mime(req: Request, fallback: str) -> str: | ||||
|     if m: | ||||
|         format = CONFIG_BY_MIME[m.mime] | ||||
|         source = formats[format] | ||||
|         logger.debug( | ||||
|             f"The client accepts {m.header}, using '{format}' from {source}" | ||||
|         ) | ||||
|         logger.debug(f"The client accepts {m.header}, using '{format}' from {source}") | ||||
|     else: | ||||
|         logger.debug(f"No format found, the client accepts {req.accept!r}") | ||||
|     return m.mime | ||||
|   | ||||
| @@ -69,9 +69,7 @@ class SanicException(Exception): | ||||
|     ) -> None: | ||||
|         self.context = context | ||||
|         self.extra = extra | ||||
|         status_code = status_code or getattr( | ||||
|             self.__class__, "status_code", None | ||||
|         ) | ||||
|         status_code = status_code or getattr(self.__class__, "status_code", None) | ||||
|         quiet = quiet or getattr(self.__class__, "quiet", None) | ||||
|         headers = headers or getattr(self.__class__, "headers", {}) | ||||
|         if message is None: | ||||
| @@ -621,9 +619,7 @@ class Unauthorized(HTTPException): | ||||
|  | ||||
|         # if auth-scheme is specified, set "WWW-Authenticate" header | ||||
|         if scheme is not None: | ||||
|             values = [ | ||||
|                 '{!s}="{!s}"'.format(k, v) for k, v in challenges.items() | ||||
|             ] | ||||
|             values = [f'{k!s}="{v!s}"' for k, v in challenges.items()] | ||||
|             challenge = ", ".join(values) | ||||
|  | ||||
|             self.headers = { | ||||
|   | ||||
| @@ -2,7 +2,6 @@ from .content_range import ContentRangeHandler | ||||
| from .directory import DirectoryHandler | ||||
| from .error import ErrorHandler | ||||
|  | ||||
|  | ||||
| __all__ = ( | ||||
|     "ContentRangeHandler", | ||||
|     "DirectoryHandler", | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import os | ||||
|  | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from sanic.exceptions import ( | ||||
| @@ -11,7 +10,6 @@ from sanic.exceptions import ( | ||||
| ) | ||||
| from sanic.models.protocol_types import Range | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Request | ||||
|  | ||||
| @@ -33,27 +31,19 @@ class ContentRangeHandler(Range): | ||||
|             raise HeaderNotFound("Range Header Not Found") | ||||
|         unit, _, value = tuple(map(str.strip, _range.partition("="))) | ||||
|         if unit != "bytes": | ||||
|             raise InvalidRangeType( | ||||
|                 "%s is not a valid Range Type" % (unit,), self | ||||
|             ) | ||||
|             raise InvalidRangeType(f"{unit} is not a valid Range Type", self) | ||||
|         start_b, _, end_b = tuple(map(str.strip, value.partition("-"))) | ||||
|         try: | ||||
|             self.start = int(start_b) if start_b else None | ||||
|         except ValueError: | ||||
|             raise RangeNotSatisfiable( | ||||
|                 "'%s' is invalid for Content Range" % (start_b,), self | ||||
|             ) | ||||
|             raise RangeNotSatisfiable(f"'{start_b}' is invalid for Content Range", self) | ||||
|         try: | ||||
|             self.end = int(end_b) if end_b else None | ||||
|         except ValueError: | ||||
|             raise RangeNotSatisfiable( | ||||
|                 "'%s' is invalid for Content Range" % (end_b,), self | ||||
|             ) | ||||
|             raise RangeNotSatisfiable(f"'{end_b}' is invalid for Content Range", self) | ||||
|         if self.end is None: | ||||
|             if self.start is None: | ||||
|                 raise RangeNotSatisfiable( | ||||
|                     "Invalid for Content Range parameters", self | ||||
|                 ) | ||||
|                 raise RangeNotSatisfiable("Invalid for Content Range parameters", self) | ||||
|             else: | ||||
|                 # this case represents `Content-Range: bytes 5-` | ||||
|                 self.end = self.total - 1 | ||||
| @@ -63,14 +53,9 @@ class ContentRangeHandler(Range): | ||||
|                 self.start = self.total - self.end | ||||
|                 self.end = self.total - 1 | ||||
|         if self.start >= self.end: | ||||
|             raise RangeNotSatisfiable( | ||||
|                 "Invalid for Content Range parameters", self | ||||
|             ) | ||||
|             raise RangeNotSatisfiable("Invalid for Content Range parameters", self) | ||||
|         self.size = self.end - self.start + 1 | ||||
|         self.headers = { | ||||
|             "Content-Range": "bytes %s-%s/%s" | ||||
|             % (self.start, self.end, self.total) | ||||
|         } | ||||
|         self.headers = {"Content-Range": f"bytes {self.start}-{self.end}/{self.total}"} | ||||
|  | ||||
|     def __bool__(self): | ||||
|         return hasattr(self, "size") and self.size > 0 | ||||
|   | ||||
| @@ -4,7 +4,7 @@ from datetime import datetime | ||||
| from operator import itemgetter | ||||
| from pathlib import Path | ||||
| from stat import S_ISDIR | ||||
| from typing import Dict, Iterable, Optional, Sequence, Union, cast | ||||
| from typing import Iterable, Sequence, cast | ||||
|  | ||||
| from sanic.exceptions import NotFound | ||||
| from sanic.pages.directory_page import DirectoryPage, FileInfo | ||||
| @@ -28,7 +28,7 @@ class DirectoryHandler: | ||||
|         uri: str, | ||||
|         directory: Path, | ||||
|         directory_view: bool = False, | ||||
|         index: Optional[Union[str, Sequence[str]]] = None, | ||||
|         index: str | Sequence[str] | None = None, | ||||
|     ) -> None: | ||||
|         if isinstance(index, str): | ||||
|             index = [index] | ||||
| @@ -60,9 +60,7 @@ class DirectoryHandler: | ||||
|                 return await file(index_file) | ||||
|  | ||||
|         if self.directory_view: | ||||
|             return self._index( | ||||
|                 self.directory / current, path, request.app.debug | ||||
|             ) | ||||
|             return self._index(self.directory / current, path, request.app.debug) | ||||
|  | ||||
|         if self.index: | ||||
|             raise NotFound("File not found") | ||||
| @@ -72,20 +70,16 @@ class DirectoryHandler: | ||||
|     def _index(self, location: Path, path: str, debug: bool): | ||||
|         # Remove empty path elements, append slash | ||||
|         if "//" in path or not path.endswith("/"): | ||||
|             return redirect( | ||||
|                 "/" + "".join([f"{p}/" for p in path.split("/") if p]) | ||||
|             ) | ||||
|             return redirect("/" + "".join([f"{p}/" for p in path.split("/") if p])) | ||||
|  | ||||
|         # Render file browser | ||||
|         page = DirectoryPage(self._iter_files(location), path, debug) | ||||
|         return html(page.render()) | ||||
|  | ||||
|     def _prepare_file(self, path: Path) -> Dict[str, Union[int, str]]: | ||||
|     def _prepare_file(self, path: Path) -> dict[str, int | str]: | ||||
|         stat = path.stat() | ||||
|         modified = ( | ||||
|             datetime.fromtimestamp(stat.st_mtime) | ||||
|             .isoformat()[:19] | ||||
|             .replace("T", " ") | ||||
|             datetime.fromtimestamp(stat.st_mtime).isoformat()[:19].replace("T", " ") | ||||
|         ) | ||||
|         is_dir = S_ISDIR(stat.st_mode) | ||||
|         icon = "📁" if is_dir else "📄" | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from typing import Dict, List, Optional, Tuple, Type | ||||
|  | ||||
| from sanic.errorpages import BaseRenderer, TextRenderer, exception_response | ||||
| from sanic.exceptions import ServerError | ||||
| from sanic.log import error_logger | ||||
| @@ -25,20 +23,20 @@ class ErrorHandler: | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         base: Type[BaseRenderer] = TextRenderer, | ||||
|         base: type[BaseRenderer] = TextRenderer, | ||||
|     ): | ||||
|         self.cached_handlers: Dict[ | ||||
|             Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler] | ||||
|         self.cached_handlers: dict[ | ||||
|             tuple[type[BaseException], str | None], RouteHandler | None | ||||
|         ] = {} | ||||
|         self.debug = False | ||||
|         self.base = base | ||||
|  | ||||
|     def _full_lookup(self, exception, route_name: Optional[str] = None): | ||||
|     def _full_lookup(self, exception, route_name: str | None = None): | ||||
|         return self.lookup(exception, route_name) | ||||
|  | ||||
|     def _add( | ||||
|         self, | ||||
|         key: Tuple[Type[BaseException], Optional[str]], | ||||
|         key: tuple[type[BaseException], str | None], | ||||
|         handler: RouteHandler, | ||||
|     ) -> None: | ||||
|         if key in self.cached_handlers: | ||||
| @@ -53,7 +51,7 @@ class ErrorHandler: | ||||
|             raise ServerError(message) | ||||
|         self.cached_handlers[key] = handler | ||||
|  | ||||
|     def add(self, exception, handler, route_names: Optional[List[str]] = None): | ||||
|     def add(self, exception, handler, route_names: list[str] | None = None): | ||||
|         """Add a new exception handler to an already existing handler object. | ||||
|  | ||||
|         Args: | ||||
| @@ -72,7 +70,7 @@ class ErrorHandler: | ||||
|         else: | ||||
|             self._add((exception, None), handler) | ||||
|  | ||||
|     def lookup(self, exception, route_name: Optional[str] = None): | ||||
|     def lookup(self, exception, route_name: str | None = None): | ||||
|         """Lookup the existing instance of `ErrorHandler` and fetch the registered handler for a specific type of exception. | ||||
|  | ||||
|         This method leverages a dict lookup to speedup the retrieval process. | ||||
| @@ -98,9 +96,7 @@ class ErrorHandler: | ||||
|                 exception_key = (ancestor, name) | ||||
|                 if exception_key in self.cached_handlers: | ||||
|                     handler = self.cached_handlers[exception_key] | ||||
|                     self.cached_handlers[ | ||||
|                         (exception_class, route_name) | ||||
|                     ] = handler | ||||
|                     self.cached_handlers[(exception_class, route_name)] = handler | ||||
|                     return handler | ||||
|  | ||||
|                 if ancestor is BaseException: | ||||
| @@ -135,13 +131,11 @@ class ErrorHandler: | ||||
|                 url = repr(request.url) | ||||
|             except AttributeError:  # no cov | ||||
|                 url = "unknown" | ||||
|             response_message = ( | ||||
|                 "Exception raised in exception handler " '"%s" for uri: %s' | ||||
|             ) | ||||
|             error_logger.exception(response_message, handler.__name__, url) | ||||
|             response_message = f'Exception raised in exception handler "{handler.__name__}" for uri: {url}' | ||||
|             error_logger.exception(response_message) | ||||
|  | ||||
|             if self.debug: | ||||
|                 return text(response_message % (handler.__name__, url), 500) | ||||
|                 return text(response_message, 500) | ||||
|             else: | ||||
|                 return text("An error occurred while handling an error", 500) | ||||
|         return response | ||||
| @@ -200,6 +194,4 @@ class ErrorHandler: | ||||
|             except AttributeError:  # no cov | ||||
|                 url = "unknown" | ||||
|  | ||||
|             error_logger.exception( | ||||
|                 "Exception occurred while handling uri: %s", url | ||||
|             ) | ||||
|             error_logger.exception("Exception occurred while handling uri: %s", url) | ||||
|   | ||||
| @@ -1,14 +1,12 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import re | ||||
|  | ||||
| from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union | ||||
| from typing import Any, Dict, Iterable, Tuple, Union | ||||
| from urllib.parse import unquote | ||||
|  | ||||
| from sanic.exceptions import InvalidHeader | ||||
| from sanic.helpers import STATUS_CODES | ||||
|  | ||||
|  | ||||
| # TODO: | ||||
| # - the Options object should be a typed object to allow for less casting | ||||
| #   across the application (in request.py for example) | ||||
| @@ -21,9 +19,7 @@ _token, _quoted = r"([\w!#$%&'*+\-.^_`|~]+)", r'"([^"]*)"' | ||||
| _param = re.compile(rf";\s*{_token}=(?:{_token}|{_quoted})", re.ASCII) | ||||
| _ipv6 = "(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}" | ||||
| _ipv6_re = re.compile(_ipv6) | ||||
| _host_re = re.compile( | ||||
|     r"((?:\[" + _ipv6 + r"\])|[a-zA-Z0-9.\-]{1,253})(?::(\d{1,5}))?" | ||||
| ) | ||||
| _host_re = re.compile(r"((?:\[" + _ipv6 + r"\])|[a-zA-Z0-9.\-]{1,253})(?::(\d{1,5}))?") | ||||
|  | ||||
| # RFC's quoted-pair escapes are mostly ignored by browsers. Chrome, Firefox and | ||||
| # curl all have different escaping, that we try to handle as well as possible, | ||||
| @@ -85,8 +81,8 @@ class MediaType: | ||||
|  | ||||
|     def match( | ||||
|         self, | ||||
|         mime_with_params: Union[str, MediaType], | ||||
|     ) -> Optional[MediaType]: | ||||
|         mime_with_params: str | MediaType, | ||||
|     ) -> MediaType | None: | ||||
|         """Match this media type against another media type. | ||||
|  | ||||
|         Check if this media type matches the given mime type/subtype. | ||||
| @@ -124,9 +120,7 @@ class MediaType: | ||||
|                     or mt.subtype == "*" | ||||
|                 ) | ||||
|                 # Type match | ||||
|                 and ( | ||||
|                     self.type == mt.type or self.type == "*" or mt.type == "*" | ||||
|                 ) | ||||
|                 and (self.type == mt.type or self.type == "*" or mt.type == "*") | ||||
|             ) | ||||
|             else None | ||||
|         ) | ||||
| @@ -141,7 +135,7 @@ class MediaType: | ||||
|         return any(part == "*" for part in (self.subtype, self.type)) | ||||
|  | ||||
|     @classmethod | ||||
|     def _parse(cls, mime_with_params: str) -> Optional[MediaType]: | ||||
|     def _parse(cls, mime_with_params: str) -> MediaType | None: | ||||
|         mtype = mime_with_params.strip() | ||||
|         if "/" not in mime_with_params: | ||||
|             return None | ||||
| @@ -151,12 +145,10 @@ class MediaType: | ||||
|         if not type_ or not subtype: | ||||
|             raise ValueError(f"Invalid media type: {mtype}") | ||||
|  | ||||
|         params = dict( | ||||
|             [ | ||||
|                 (key.strip(), value.strip()) | ||||
|                 for key, value in (param.split("=", 1) for param in raw_params) | ||||
|             ] | ||||
|         ) | ||||
|         params = { | ||||
|             key.strip(): value.strip() | ||||
|             for key, value in (param.split("=", 1) for param in raw_params) | ||||
|         } | ||||
|  | ||||
|         return cls(type_.lstrip(), subtype.rstrip(), **params) | ||||
|  | ||||
| @@ -173,7 +165,7 @@ class Matched: | ||||
|         header (MediaType): The header to match against, if any. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, mime: str, header: Optional[MediaType]): | ||||
|     def __init__(self, mime: str, header: MediaType | None): | ||||
|         self.mime = mime | ||||
|         self.header = header | ||||
|  | ||||
| @@ -200,7 +192,7 @@ class Matched: | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def _compare(self, other) -> Tuple[bool, Matched]: | ||||
|     def _compare(self, other) -> tuple[bool, Matched]: | ||||
|         if isinstance(other, str): | ||||
|             parsed = Matched.parse(other) | ||||
|             if self.mime == other: | ||||
| @@ -215,7 +207,7 @@ class Matched: | ||||
|             f"mime types of '{self.mime}' and '{other}'" | ||||
|         ) | ||||
|  | ||||
|     def match(self, other: Union[str, Matched]) -> Optional[Matched]: | ||||
|     def match(self, other: str | Matched) -> Matched | None: | ||||
|         """Match this MIME string against another MIME string. | ||||
|  | ||||
|         Check if this MIME string matches the given MIME string. Wildcards are supported both ways on both type and subtype. | ||||
| @@ -296,7 +288,7 @@ class AcceptList(list): | ||||
|         return ", ".join(str(m) for m in self) | ||||
|  | ||||
|  | ||||
| def parse_accept(accept: Optional[str]) -> AcceptList: | ||||
| def parse_accept(accept: str | None) -> AcceptList: | ||||
|     """Parse an Accept header and order the acceptable media types according to RFC 7231, s. 5.3.2 | ||||
|  | ||||
|     https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 | ||||
| @@ -316,9 +308,7 @@ def parse_accept(accept: Optional[str]) -> AcceptList: | ||||
|         accept = "*/*"  # No header means that all types are accepted | ||||
|     try: | ||||
|         a = [ | ||||
|             mt | ||||
|             for mt in [MediaType._parse(mtype) for mtype in accept.split(",")] | ||||
|             if mt | ||||
|             mt for mt in [MediaType._parse(mtype) for mtype in accept.split(",")] if mt | ||||
|         ] | ||||
|         if not a: | ||||
|             raise ValueError | ||||
| @@ -327,7 +317,7 @@ def parse_accept(accept: Optional[str]) -> AcceptList: | ||||
|         raise InvalidHeader(f"Invalid header value in Accept: {accept}") | ||||
|  | ||||
|  | ||||
| def parse_content_header(value: str) -> Tuple[str, Options]: | ||||
| def parse_content_header(value: str) -> tuple[str, Options]: | ||||
|     """Parse content-type and content-disposition header values. | ||||
|  | ||||
|     E.g. `form-data; name=upload; filename="file.txt"` to | ||||
| @@ -346,11 +336,10 @@ def parse_content_header(value: str) -> Tuple[str, Options]: | ||||
|     """ | ||||
|     pos = value.find(";") | ||||
|     if pos == -1: | ||||
|         options: Dict[str, Union[int, str]] = {} | ||||
|         options: dict[str, int | str] = {} | ||||
|     else: | ||||
|         options = { | ||||
|             m.group(1) | ||||
|             .lower(): (m.group(2) or m.group(3)) | ||||
|             m.group(1).lower(): (m.group(2) or m.group(3)) | ||||
|             .replace("%22", '"') | ||||
|             .replace("%0D%0A", "\n") | ||||
|             for m in _param.finditer(value[pos:]) | ||||
| @@ -367,7 +356,7 @@ def parse_content_header(value: str) -> Tuple[str, Options]: | ||||
| _rparam = re.compile(f"(?:{_token}|{_quoted})={_token}\\s*($|[;,])", re.ASCII) | ||||
|  | ||||
|  | ||||
| def parse_forwarded(headers, config) -> Optional[Options]: | ||||
| def parse_forwarded(headers, config) -> Options | None: | ||||
|     """Parse RFC 7239 Forwarded headers. | ||||
|     The value of `by` or `secret` must match `config.FORWARDED_SECRET` | ||||
|     :return: dict with keys and values, or None if nothing matched | ||||
| @@ -381,7 +370,7 @@ def parse_forwarded(headers, config) -> Optional[Options]: | ||||
|         return None | ||||
|     # Loop over <separator><key>=<value> elements from right to left | ||||
|     sep = pos = None | ||||
|     options: List[Tuple[str, str]] = [] | ||||
|     options: list[tuple[str, str]] = [] | ||||
|     found = False | ||||
|     for m in _rparam.finditer(header[::-1]): | ||||
|         # Start of new element? (on parser skips and non-semicolon right sep) | ||||
| @@ -405,7 +394,7 @@ def parse_forwarded(headers, config) -> Optional[Options]: | ||||
|     return fwd_normalize(reversed(options)) if found else None | ||||
|  | ||||
|  | ||||
| def parse_xforwarded(headers, config) -> Optional[Options]: | ||||
| def parse_xforwarded(headers, config) -> Options | None: | ||||
|     """Parse traditional proxy headers.""" | ||||
|     real_ip_header = config.REAL_IP_HEADER | ||||
|     proxies_count = config.PROXIES_COUNT | ||||
| @@ -416,11 +405,7 @@ def parse_xforwarded(headers, config) -> Optional[Options]: | ||||
|             # Combine, split and filter multiple headers' entries | ||||
|             forwarded_for = headers.getall(config.FORWARDED_FOR_HEADER) | ||||
|             proxies = [ | ||||
|                 p | ||||
|                 for p in ( | ||||
|                     p.strip() for h in forwarded_for for p in h.split(",") | ||||
|                 ) | ||||
|                 if p | ||||
|                 p for p in (p.strip() for h in forwarded_for for p in h.split(",")) if p | ||||
|             ] | ||||
|             addr = proxies[-proxies_count] | ||||
|         except (KeyError, IndexError): | ||||
| @@ -452,7 +437,7 @@ def fwd_normalize(fwd: OptionsIterable) -> Options: | ||||
|     Returns: | ||||
|         Options: A dict of normalized key-value pairs. | ||||
|     """ | ||||
|     ret: Dict[str, Union[int, str]] = {} | ||||
|     ret: dict[str, int | str] = {} | ||||
|     for key, val in fwd: | ||||
|         if val is not None: | ||||
|             try: | ||||
| @@ -489,7 +474,7 @@ def fwd_normalize_address(addr: str) -> str: | ||||
|     return addr.lower() | ||||
|  | ||||
|  | ||||
| def parse_host(host: str) -> Tuple[Optional[str], Optional[int]]: | ||||
| def parse_host(host: str) -> tuple[str | None, int | None]: | ||||
|     """Split host:port into hostname and port. | ||||
|  | ||||
|     Args: | ||||
| @@ -531,9 +516,9 @@ def format_http1_response(status: int, headers: HeaderBytesIterable) -> bytes: | ||||
|  | ||||
|  | ||||
| def parse_credentials( | ||||
|     header: Optional[str], | ||||
|     prefixes: Optional[Union[List, Tuple, Set]] = None, | ||||
| ) -> Tuple[Optional[str], Optional[str]]: | ||||
|     header: str | None, | ||||
|     prefixes: list | tuple | set | None = None, | ||||
| ) -> tuple[str | None, str | None]: | ||||
|     """Parses any header with the aim to retrieve any credentials from it. | ||||
|  | ||||
|     Args: | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| """Defines basics of HTTP standard.""" | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from importlib import import_module | ||||
| from inspect import ismodule | ||||
| from typing import Dict | ||||
|  | ||||
|  | ||||
| STATUS_CODES: Dict[int, bytes] = { | ||||
|     100: b"Continue", | ||||
|     101: b"Switching Protocols", | ||||
| @@ -132,7 +130,7 @@ def remove_entity_headers(headers, allowed=("content-location", "expires")): | ||||
|  | ||||
|     returns the headers without the entity headers | ||||
|     """ | ||||
|     allowed = set([h.lower() for h in allowed]) | ||||
|     allowed = {h.lower() for h in allowed} | ||||
|     headers = { | ||||
|         header: value | ||||
|         for header, value in headers.items() | ||||
|   | ||||
| @@ -2,5 +2,4 @@ from .constants import Stage | ||||
| from .http1 import Http | ||||
| from .http3 import Http3 | ||||
|  | ||||
|  | ||||
| __all__ = ("Http", "Stage", "Http3") | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from typing import TYPE_CHECKING, Optional | ||||
|  | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic.request import Request | ||||
| @@ -25,7 +24,6 @@ from sanic.http.stream import Stream | ||||
| from sanic.log import access_logger, error_logger, logger | ||||
| from sanic.touchup import TouchUpMeta | ||||
|  | ||||
|  | ||||
| HTTP_CONTINUE = b"HTTP/1.1 100 Continue\r\n\r\n" | ||||
|  | ||||
|  | ||||
| @@ -363,26 +361,20 @@ class Http(Stream, metaclass=TouchUpMeta): | ||||
|             self.response_func = None | ||||
|             self.stage = Stage.IDLE | ||||
|  | ||||
|     async def http1_response_chunked( | ||||
|         self, data: bytes, end_stream: bool | ||||
|     ) -> None: | ||||
|     async def http1_response_chunked(self, data: bytes, end_stream: bool) -> None: | ||||
|         """Format a part of response body in chunked encoding.""" | ||||
|         # Chunked encoding | ||||
|         size = len(data) | ||||
|         if end_stream: | ||||
|             await self._send( | ||||
|                 b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) | ||||
|                 if size | ||||
|                 else b"0\r\n\r\n" | ||||
|                 b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) if size else b"0\r\n\r\n" | ||||
|             ) | ||||
|             self.response_func = None | ||||
|             self.stage = Stage.IDLE | ||||
|         elif size: | ||||
|             await self._send(b"%x\r\n%b\r\n" % (size, data)) | ||||
|  | ||||
|     async def http1_response_normal( | ||||
|         self, data: bytes, end_stream: bool | ||||
|     ) -> None: | ||||
|     async def http1_response_normal(self, data: bytes, end_stream: bool) -> None: | ||||
|         """Format / keep track of non-chunked response.""" | ||||
|         bytes_left = self.response_bytes_left - len(data) | ||||
|         if bytes_left <= 0: | ||||
| @@ -420,9 +412,7 @@ class Http(Stream, metaclass=TouchUpMeta): | ||||
|                 exception, (ServiceUnavailable, RequestCancelled) | ||||
|             ) | ||||
|             try: | ||||
|                 await app.handle_exception( | ||||
|                     self.request, exception, request_middleware | ||||
|                 ) | ||||
|                 await app.handle_exception(self.request, exception, request_middleware) | ||||
|             except Exception as e: | ||||
|                 await app.handle_exception(self.request, e, False) | ||||
|  | ||||
| @@ -481,7 +471,7 @@ class Http(Stream, metaclass=TouchUpMeta): | ||||
|             if data: | ||||
|                 yield data | ||||
|  | ||||
|     async def read(self) -> Optional[bytes]:  # no cov | ||||
|     async def read(self) -> bytes | None:  # no cov | ||||
|         """Read some bytes of request body.""" | ||||
|  | ||||
|         # Send a 100-continue if needed | ||||
|   | ||||
| @@ -1,17 +1,12 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| from abc import ABC, abstractmethod | ||||
| from ssl import SSLContext | ||||
| from typing import ( | ||||
|     TYPE_CHECKING, | ||||
|     Any, | ||||
|     Callable, | ||||
|     Dict, | ||||
|     List, | ||||
|     Optional, | ||||
|     Tuple, | ||||
|     Union, | ||||
|     cast, | ||||
| ) | ||||
| @@ -32,7 +27,6 @@ from sanic.log import Colors, logger | ||||
| from sanic.models.protocol_types import TransportProtocol | ||||
| from sanic.models.server_types import ConnInfo | ||||
|  | ||||
|  | ||||
| try: | ||||
|     from aioquic.h0.connection import H0_ALPN, H0Connection | ||||
|     from aioquic.h3.connection import H3_ALPN, H3Connection | ||||
| @@ -71,10 +65,7 @@ class HTTP3Transport(TransportProtocol): | ||||
|         return self._protocol | ||||
|  | ||||
|     def get_extra_info(self, info: str, default: Any = None) -> Any: | ||||
|         if ( | ||||
|             info in ("socket", "sockname", "peername") | ||||
|             and self._protocol._transport | ||||
|         ): | ||||
|         if info in ("socket", "sockname", "peername") and self._protocol._transport: | ||||
|             return self._protocol._transport.get_extra_info(info, default) | ||||
|         elif info == "network_paths": | ||||
|             return self._protocol._quic._network_paths | ||||
| @@ -109,19 +100,18 @@ class HTTPReceiver(Receiver, Stream): | ||||
|         self.request_body = None | ||||
|         self.stage = Stage.IDLE | ||||
|         self.headers_sent = False | ||||
|         self.response: Optional[BaseHTTPResponse] = None | ||||
|         self.response: BaseHTTPResponse | None = None | ||||
|         self.request_max_size = self.protocol.request_max_size | ||||
|         self.request_bytes = 0 | ||||
|  | ||||
|     async def run(self, exception: Optional[Exception] = None): | ||||
|     async def run(self, exception: Exception | None = None): | ||||
|         """Handle the request and response cycle.""" | ||||
|         self.stage = Stage.HANDLER | ||||
|         self.head_only = self.request.method.upper() == "HEAD" | ||||
|  | ||||
|         if exception: | ||||
|             logger.info(  # no cov | ||||
|                 f"{Colors.BLUE}[exception]: " | ||||
|                 f"{Colors.RED}{exception}{Colors.END}", | ||||
|                 f"{Colors.BLUE}[exception]: " f"{Colors.RED}{exception}{Colors.END}", | ||||
|                 exc_info=True, | ||||
|                 extra={"verbosity": 1}, | ||||
|             ) | ||||
| @@ -146,17 +136,13 @@ class HTTPReceiver(Receiver, Stream): | ||||
|  | ||||
|         await app.handle_exception(self.request, exception) | ||||
|  | ||||
|     def _prepare_headers( | ||||
|         self, response: BaseHTTPResponse | ||||
|     ) -> List[Tuple[bytes, bytes]]: | ||||
|     def _prepare_headers(self, response: BaseHTTPResponse) -> list[tuple[bytes, bytes]]: | ||||
|         size = len(response.body) if response.body else 0 | ||||
|         headers = response.headers | ||||
|         status = response.status | ||||
|  | ||||
|         if not has_message_body(status) and ( | ||||
|             size | ||||
|             or "content-length" in headers | ||||
|             or "transfer-encoding" in headers | ||||
|             size or "content-length" in headers or "transfer-encoding" in headers | ||||
|         ): | ||||
|             headers.pop("content-length", None) | ||||
|             headers.pop("transfer-encoding", None) | ||||
| @@ -249,11 +235,7 @@ class HTTPReceiver(Receiver, Stream): | ||||
|         ): | ||||
|             size = len(data) | ||||
|             if end_stream: | ||||
|                 data = ( | ||||
|                     b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) | ||||
|                     if size | ||||
|                     else b"0\r\n\r\n" | ||||
|                 ) | ||||
|                 data = b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) if size else b"0\r\n\r\n" | ||||
|             elif size: | ||||
|                 data = b"%x\r\n%b\r\n" % (size, data) | ||||
|  | ||||
| @@ -304,7 +286,7 @@ class Http3: | ||||
|     ) -> None: | ||||
|         self.protocol = protocol | ||||
|         self.transmit = transmit | ||||
|         self.receivers: Dict[int, Receiver] = {} | ||||
|         self.receivers: dict[int, Receiver] = {} | ||||
|  | ||||
|     def http_event_received(self, event: H3Event) -> None: | ||||
|         logger.debug(  # no cov | ||||
| @@ -330,11 +312,8 @@ class Http3: | ||||
|                 extra={"verbosity": 2}, | ||||
|             ) | ||||
|  | ||||
|     def get_or_make_receiver(self, event: H3Event) -> Tuple[Receiver, bool]: | ||||
|         if ( | ||||
|             isinstance(event, HeadersReceived) | ||||
|             and event.stream_id not in self.receivers | ||||
|         ): | ||||
|     def get_or_make_receiver(self, event: H3Event) -> tuple[Receiver, bool]: | ||||
|         if isinstance(event, HeadersReceived) and event.stream_id not in self.receivers: | ||||
|             request = self._make_request(event) | ||||
|             receiver = HTTPReceiver(self.transmit, self.protocol, request) | ||||
|             request.stream = receiver | ||||
| @@ -357,9 +336,7 @@ class Http3: | ||||
|                 ) | ||||
|             ) | ||||
|         except UnicodeDecodeError: | ||||
|             raise BadRequest( | ||||
|                 "Header names may only contain US-ASCII characters." | ||||
|             ) | ||||
|             raise BadRequest("Header names may only contain US-ASCII characters.") | ||||
|         method = headers[":method"] | ||||
|         path = headers[":path"] | ||||
|         scheme = headers.pop(":scheme", "") | ||||
| @@ -396,18 +373,16 @@ class SessionTicketStore: | ||||
|     """ | ||||
|  | ||||
|     def __init__(self) -> None: | ||||
|         self.tickets: Dict[bytes, SessionTicket] = {} | ||||
|         self.tickets: dict[bytes, SessionTicket] = {} | ||||
|  | ||||
|     def add(self, ticket: SessionTicket) -> None: | ||||
|         self.tickets[ticket.ticket] = ticket | ||||
|  | ||||
|     def pop(self, label: bytes) -> Optional[SessionTicket]: | ||||
|     def pop(self, label: bytes) -> SessionTicket | None: | ||||
|         return self.tickets.pop(label, None) | ||||
|  | ||||
|  | ||||
| def get_config( | ||||
|     app: Sanic, ssl: Union[SanicSSLContext, CertSelector, SSLContext] | ||||
| ): | ||||
| def get_config(app: Sanic, ssl: SanicSSLContext | CertSelector | SSLContext): | ||||
|     # TODO: | ||||
|     # - proper selection needed if service with multiple certs insted of | ||||
|     #   just taking the first | ||||
| @@ -430,8 +405,6 @@ def get_config( | ||||
|     ) | ||||
|     password = app.config.TLS_CERT_PASSWORD or None | ||||
|  | ||||
|     config.load_cert_chain( | ||||
|         ssl.sanic["cert"], ssl.sanic["key"], password=password | ||||
|     ) | ||||
|     config.load_cert_chain(ssl.sanic["cert"], ssl.sanic["key"], password=password) | ||||
|  | ||||
|     return config | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from typing import TYPE_CHECKING, Optional, Tuple, Union | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from sanic.http.constants import Stage | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic.response import BaseHTTPResponse | ||||
|     from sanic.server.protocols.http_protocol import HttpProtocol | ||||
| @@ -12,16 +11,14 @@ if TYPE_CHECKING: | ||||
|  | ||||
| class Stream: | ||||
|     stage: Stage | ||||
|     response: Optional[BaseHTTPResponse] | ||||
|     response: BaseHTTPResponse | None | ||||
|     protocol: HttpProtocol | ||||
|     url: Optional[str] | ||||
|     request_body: Optional[bytes] | ||||
|     request_max_size: Union[int, float] | ||||
|     url: str | None | ||||
|     request_body: bytes | None | ||||
|     request_max_size: int | float | ||||
|  | ||||
|     __touchup__: Tuple[str, ...] = tuple() | ||||
|     __touchup__: tuple[str, ...] = () | ||||
|     __slots__ = ("request_max_size",) | ||||
|  | ||||
|     def respond( | ||||
|         self, response: BaseHTTPResponse | ||||
|     ) -> BaseHTTPResponse:  # no cov | ||||
|     def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse:  # no cov | ||||
|         raise NotImplementedError("Not implemented") | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| from .context import process_to_context | ||||
| from .creators import get_ssl_context | ||||
|  | ||||
|  | ||||
| __all__ = ("get_ssl_context", "process_to_context") | ||||
|   | ||||
| @@ -2,12 +2,10 @@ from __future__ import annotations | ||||
|  | ||||
| import os | ||||
| import ssl | ||||
|  | ||||
| from typing import Any, Dict, Iterable, Optional, Union | ||||
| from typing import Any, Iterable | ||||
|  | ||||
| from sanic.log import logger | ||||
|  | ||||
|  | ||||
| # Only allow secure ciphers, notably leaving out AES-CBC mode | ||||
| # OpenSSL chooses ECDSA or RSA depending on the cert in use | ||||
| CIPHERS_TLS12 = [ | ||||
| @@ -19,11 +17,14 @@ CIPHERS_TLS12 = [ | ||||
|     "ECDHE-RSA-AES128-GCM-SHA256", | ||||
| ] | ||||
|  | ||||
| TlsDef = None | ssl.SSLContext | dict[str, Any] | str | ||||
| TlsDefs = TlsDef | list[TlsDef] | tuple[TlsDef, ...] | ||||
|  | ||||
|  | ||||
| def create_context( | ||||
|     certfile: Optional[str] = None, | ||||
|     keyfile: Optional[str] = None, | ||||
|     password: Optional[str] = None, | ||||
|     certfile: str | None = None, | ||||
|     keyfile: str | None = None, | ||||
|     password: str | None = None, | ||||
|     purpose: ssl.Purpose = ssl.Purpose.CLIENT_AUTH, | ||||
| ) -> ssl.SSLContext: | ||||
|     """Create a context with secure crypto and HTTP/1.1 in protocols.""" | ||||
| @@ -38,9 +39,7 @@ def create_context( | ||||
|     return context | ||||
|  | ||||
|  | ||||
| def shorthand_to_ctx( | ||||
|     ctxdef: Union[None, ssl.SSLContext, dict, str] | ||||
| ) -> Optional[ssl.SSLContext]: | ||||
| def shorthand_to_ctx(ctxdef: TlsDef) -> ssl.SSLContext | None: | ||||
|     """Convert an ssl argument shorthand to an SSLContext object.""" | ||||
|     if ctxdef is None or isinstance(ctxdef, ssl.SSLContext): | ||||
|         return ctxdef | ||||
| @@ -54,9 +53,7 @@ def shorthand_to_ctx( | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def process_to_context( | ||||
|     ssldef: Union[None, ssl.SSLContext, dict, str, list, tuple] | ||||
| ) -> Optional[ssl.SSLContext]: | ||||
| def process_to_context(ssldef: TlsDefs) -> ssl.SSLContext | None: | ||||
|     """Process app.run ssl argument from easy formats to full SSLContext.""" | ||||
|     return ( | ||||
|         CertSelector(map(shorthand_to_ctx, ssldef)) | ||||
| @@ -71,13 +68,9 @@ def load_cert_dir(p: str) -> ssl.SSLContext: | ||||
|     keyfile = os.path.join(p, "privkey.pem") | ||||
|     certfile = os.path.join(p, "fullchain.pem") | ||||
|     if not os.access(keyfile, os.R_OK): | ||||
|         raise ValueError( | ||||
|             f"Certificate not found or permission denied {keyfile}" | ||||
|         ) | ||||
|         raise ValueError(f"Certificate not found or permission denied {keyfile}") | ||||
|     if not os.access(certfile, os.R_OK): | ||||
|         raise ValueError( | ||||
|             f"Certificate not found or permission denied {certfile}" | ||||
|         ) | ||||
|         raise ValueError(f"Certificate not found or permission denied {certfile}") | ||||
|     return CertSimple(certfile, keyfile) | ||||
|  | ||||
|  | ||||
| @@ -89,9 +82,7 @@ def find_cert(self: CertSelector, server_name: str): | ||||
|     if not server_name: | ||||
|         if self.sanic_fallback: | ||||
|             return self.sanic_fallback | ||||
|         raise ValueError( | ||||
|             "The client provided no SNI to match for certificate." | ||||
|         ) | ||||
|         raise ValueError("The client provided no SNI to match for certificate.") | ||||
|     for ctx in self.sanic_select: | ||||
|         if match_hostname(ctx, server_name): | ||||
|             return ctx | ||||
| @@ -100,9 +91,7 @@ def find_cert(self: CertSelector, server_name: str): | ||||
|     raise ValueError(f"No certificate found matching hostname {server_name!r}") | ||||
|  | ||||
|  | ||||
| def match_hostname( | ||||
|     ctx: Union[ssl.SSLContext, CertSelector], hostname: str | ||||
| ) -> bool: | ||||
| def match_hostname(ctx: ssl.SSLContext | CertSelector, hostname: str) -> bool: | ||||
|     """Match names from CertSelector against a received hostname.""" | ||||
|     # Local certs are considered trusted, so this can be less pedantic | ||||
|     # and thus faster than the deprecated ssl.match_hostname function is. | ||||
| @@ -119,7 +108,7 @@ def match_hostname( | ||||
|  | ||||
| def selector_sni_callback( | ||||
|     sslobj: ssl.SSLObject, server_name: str, ctx: CertSelector | ||||
| ) -> Optional[int]: | ||||
| ) -> int | None: | ||||
|     """Select a certificate matching the SNI.""" | ||||
|     # Call server_name_callback to store the SNI on sslobj | ||||
|     server_name_callback(sslobj, server_name, ctx) | ||||
| @@ -142,7 +131,7 @@ def server_name_callback( | ||||
|  | ||||
|  | ||||
| class SanicSSLContext(ssl.SSLContext): | ||||
|     sanic: Dict[str, os.PathLike] | ||||
|     sanic: dict[str, os.PathLike] | ||||
|  | ||||
|     @classmethod | ||||
|     def create_from_ssl_context(cls, context: ssl.SSLContext): | ||||
| @@ -153,7 +142,7 @@ class SanicSSLContext(ssl.SSLContext): | ||||
| class CertSimple(SanicSSLContext): | ||||
|     """A wrapper for creating SSLContext with a sanic attribute.""" | ||||
|  | ||||
|     sanic: Dict[str, Any] | ||||
|     sanic: dict[str, Any] | ||||
|  | ||||
|     def __new__(cls, cert, key, **kw): | ||||
|         # try common aliases, rename to cert/key | ||||
| @@ -166,9 +155,7 @@ class CertSimple(SanicSSLContext): | ||||
|         if "names" not in kw: | ||||
|             cert = ssl._ssl._test_decode_cert(certfile)  # type: ignore | ||||
|             kw["names"] = [ | ||||
|                 name | ||||
|                 for t, name in cert["subjectAltName"] | ||||
|                 if t in ["DNS", "IP Address"] | ||||
|                 name for t, name in cert["subjectAltName"] if t in ["DNS", "IP Address"] | ||||
|             ] | ||||
|             subject = {k: v for item in cert["subject"] for k, v in item} | ||||
|         self = create_context(certfile, keyfile, password) | ||||
| @@ -190,7 +177,7 @@ class CertSelector(ssl.SSLContext): | ||||
|     def __new__(cls, ctxs): | ||||
|         return super().__new__(cls) | ||||
|  | ||||
|     def __init__(self, ctxs: Iterable[Optional[ssl.SSLContext]]): | ||||
|     def __init__(self, ctxs: Iterable[ssl.SSLContext | None]): | ||||
|         super().__init__() | ||||
|         self.sni_callback = selector_sni_callback  # type: ignore | ||||
|         self.sanic_select = [] | ||||
| @@ -205,7 +192,5 @@ class CertSelector(ssl.SSLContext): | ||||
|             if i == 0: | ||||
|                 self.sanic_fallback = ctx | ||||
|         if not all_names: | ||||
|             raise ValueError( | ||||
|                 "No certificates with SubjectAlternativeNames found." | ||||
|             ) | ||||
|             raise ValueError("No certificates with SubjectAlternativeNames found.") | ||||
|         logger.info(f"Certificate vhosts: {', '.join(all_names)}") | ||||
|   | ||||
| @@ -3,13 +3,12 @@ from __future__ import annotations | ||||
| import ssl | ||||
| import subprocess | ||||
| import sys | ||||
|  | ||||
| from abc import ABC, abstractmethod | ||||
| from contextlib import suppress | ||||
| from pathlib import Path | ||||
| from tempfile import mkdtemp | ||||
| from types import ModuleType | ||||
| from typing import TYPE_CHECKING, Optional, Tuple, Type, Union, cast | ||||
| from typing import TYPE_CHECKING, cast | ||||
|  | ||||
| from sanic.application.constants import Mode | ||||
| from sanic.application.spinner import loading | ||||
| @@ -22,7 +21,6 @@ from sanic.exceptions import SanicException | ||||
| from sanic.helpers import Default | ||||
| from sanic.http.tls.context import CertSimple, SanicSSLContext | ||||
|  | ||||
|  | ||||
| try: | ||||
|     import trustme | ||||
|  | ||||
| @@ -47,7 +45,7 @@ CIPHERS_TLS12 = [ | ||||
| ] | ||||
|  | ||||
|  | ||||
| def _make_path(maybe_path: Union[Path, str], tmpdir: Optional[Path]) -> Path: | ||||
| def _make_path(maybe_path: Path | str, tmpdir: Path | None) -> Path: | ||||
|     if isinstance(maybe_path, Path): | ||||
|         return maybe_path | ||||
|     else: | ||||
| @@ -60,9 +58,7 @@ def _make_path(maybe_path: Union[Path, str], tmpdir: Optional[Path]) -> Path: | ||||
|     return path | ||||
|  | ||||
|  | ||||
| def get_ssl_context( | ||||
|     app: Sanic, ssl: Optional[ssl.SSLContext] | ||||
| ) -> ssl.SSLContext: | ||||
| def get_ssl_context(app: Sanic, ssl: ssl.SSLContext | None) -> ssl.SSLContext: | ||||
|     if ssl: | ||||
|         return ssl | ||||
|  | ||||
| @@ -96,16 +92,8 @@ class CertCreator(ABC): | ||||
|         if isinstance(self.key, Default) or isinstance(self.cert, Default): | ||||
|             self.tmpdir = Path(mkdtemp()) | ||||
|  | ||||
|         key = ( | ||||
|             DEFAULT_LOCAL_TLS_KEY | ||||
|             if isinstance(self.key, Default) | ||||
|             else self.key | ||||
|         ) | ||||
|         cert = ( | ||||
|             DEFAULT_LOCAL_TLS_CERT | ||||
|             if isinstance(self.cert, Default) | ||||
|             else self.cert | ||||
|         ) | ||||
|         key = DEFAULT_LOCAL_TLS_KEY if isinstance(self.key, Default) else self.key | ||||
|         cert = DEFAULT_LOCAL_TLS_CERT if isinstance(self.cert, Default) else self.cert | ||||
|  | ||||
|         self.key_path = _make_path(key, self.tmpdir) | ||||
|         self.cert_path = _make_path(cert, self.tmpdir) | ||||
| @@ -126,11 +114,9 @@ class CertCreator(ABC): | ||||
|         local_tls_key, | ||||
|         local_tls_cert, | ||||
|     ) -> CertCreator: | ||||
|         creator: Optional[CertCreator] = None | ||||
|         creator: CertCreator | None = None | ||||
|  | ||||
|         cert_creator_options: Tuple[ | ||||
|             Tuple[Type[CertCreator], LocalCertCreator], ... | ||||
|         ] = ( | ||||
|         cert_creator_options: tuple[tuple[type[CertCreator], LocalCertCreator], ...] = ( | ||||
|             (MkcertCreator, LocalCertCreator.MKCERT), | ||||
|             (TrustmeCreator, LocalCertCreator.TRUSTME), | ||||
|         ) | ||||
| @@ -160,8 +146,8 @@ class CertCreator(ABC): | ||||
|     @staticmethod | ||||
|     def _try_select( | ||||
|         app: Sanic, | ||||
|         creator: Optional[CertCreator], | ||||
|         creator_class: Type[CertCreator], | ||||
|         creator: CertCreator | None, | ||||
|         creator_class: type[CertCreator], | ||||
|         creator_requirement: LocalCertCreator, | ||||
|         creator_requested: LocalCertCreator, | ||||
|         local_tls_key, | ||||
|   | ||||
							
								
								
									
										18
									
								
								sanic/log.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								sanic/log.py
									
									
									
									
									
								
							| @@ -1,13 +1,11 @@ | ||||
| import logging | ||||
| import sys | ||||
|  | ||||
| from enum import Enum | ||||
| from typing import TYPE_CHECKING, Any, Dict | ||||
| from warnings import warn | ||||
|  | ||||
| from sanic.helpers import is_atty | ||||
|  | ||||
|  | ||||
| # Python 3.11 changed the way Enum formatting works for mixed-in types. | ||||
| if sys.version_info < (3, 11, 0): | ||||
|  | ||||
| @@ -19,10 +17,10 @@ else: | ||||
|         from enum import StrEnum | ||||
|  | ||||
|  | ||||
| LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(  # no cov | ||||
|     version=1, | ||||
|     disable_existing_loggers=False, | ||||
|     loggers={ | ||||
| LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = {  # no cov | ||||
|     "version": 1, | ||||
|     "disable_existing_loggers": False, | ||||
|     "loggers": { | ||||
|         "sanic.root": {"level": "INFO", "handlers": ["console"]}, | ||||
|         "sanic.error": { | ||||
|             "level": "INFO", | ||||
| @@ -43,7 +41,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(  # no cov | ||||
|             "qualname": "sanic.server", | ||||
|         }, | ||||
|     }, | ||||
|     handlers={ | ||||
|     "handlers": { | ||||
|         "console": { | ||||
|             "class": "logging.StreamHandler", | ||||
|             "formatter": "generic", | ||||
| @@ -60,7 +58,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(  # no cov | ||||
|             "stream": sys.stdout, | ||||
|         }, | ||||
|     }, | ||||
|     formatters={ | ||||
|     "formatters": { | ||||
|         "generic": { | ||||
|             "format": "%(asctime)s [%(process)s] [%(levelname)s] %(message)s", | ||||
|             "datefmt": "[%Y-%m-%d %H:%M:%S %z]", | ||||
| @@ -68,12 +66,12 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(  # no cov | ||||
|         }, | ||||
|         "access": { | ||||
|             "format": "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " | ||||
|             + "%(request)s %(message)s %(status)s %(byte)s", | ||||
|             "%(request)s %(message)s %(status)s %(byte)s", | ||||
|             "datefmt": "[%Y-%m-%d %H:%M:%S %z]", | ||||
|             "class": "logging.Formatter", | ||||
|         }, | ||||
|     }, | ||||
| ) | ||||
| } | ||||
| """ | ||||
| Defult logging configuration | ||||
| """ | ||||
|   | ||||
| @@ -3,7 +3,7 @@ from __future__ import annotations | ||||
| from collections import deque | ||||
| from enum import IntEnum, auto | ||||
| from itertools import count | ||||
| from typing import Deque, Sequence, Union | ||||
| from typing import Sequence | ||||
|  | ||||
| from sanic.models.handler_types import MiddlewareType | ||||
|  | ||||
| @@ -69,9 +69,9 @@ class Middleware: | ||||
|     @classmethod | ||||
|     def convert( | ||||
|         cls, | ||||
|         *middleware_collections: Sequence[Union[Middleware, MiddlewareType]], | ||||
|         *middleware_collections: Sequence[Middleware | MiddlewareType], | ||||
|         location: MiddlewareLocation, | ||||
|     ) -> Deque[Middleware]: | ||||
|     ) -> deque[Middleware]: | ||||
|         """Convert middleware collections to a deque of Middleware objects. | ||||
|  | ||||
|         Args: | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class ExceptionMixin(metaclass=SanicMeta): | ||||
|     def exception( | ||||
|         self, | ||||
|         *exceptions: Union[Type[Exception], List[Type[Exception]]], | ||||
|         apply: bool = True | ||||
|         apply: bool = True, | ||||
|     ) -> Callable: | ||||
|         """Decorator used to register an exception handler for the current application or blueprint instance. | ||||
|  | ||||
| @@ -79,9 +79,7 @@ class ExceptionMixin(metaclass=SanicMeta): | ||||
|  | ||||
|         return decorator | ||||
|  | ||||
|     def all_exceptions( | ||||
|         self, handler: Callable[..., Any] | ||||
|     ) -> Callable[..., Any]: | ||||
|     def all_exceptions(self, handler: Callable[..., Any]) -> Callable[..., Any]: | ||||
|         """Enables the process of creating a global exception handler as a convenience. | ||||
|  | ||||
|         This following two examples are equivalent: | ||||
|   | ||||
| @@ -120,16 +120,12 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|  | ||||
|         if callable(listener_or_event): | ||||
|             if event_or_none is None: | ||||
|                 raise BadRequest( | ||||
|                     "Invalid event registration: Missing event name." | ||||
|                 ) | ||||
|                 raise BadRequest("Invalid event registration: Missing event name.") | ||||
|             return register_listener(listener_or_event, event_or_none) | ||||
|         else: | ||||
|             return partial(register_listener, event=listener_or_event) | ||||
|  | ||||
|     def main_process_start( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def main_process_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the main_process_start event. | ||||
|  | ||||
|         This event is fired only on the main process and **NOT** on any | ||||
| @@ -151,9 +147,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "main_process_start") | ||||
|  | ||||
|     def main_process_ready( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def main_process_ready(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the main_process_ready event. | ||||
|  | ||||
|         This event is fired only on the main process and **NOT** on any | ||||
| @@ -176,9 +170,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "main_process_ready") | ||||
|  | ||||
|     def main_process_stop( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def main_process_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the main_process_stop event. | ||||
|  | ||||
|         This event is fired only on the main process and **NOT** on any | ||||
| @@ -222,9 +214,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "reload_process_start") | ||||
|  | ||||
|     def reload_process_stop( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def reload_process_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the reload_process_stop event. | ||||
|  | ||||
|         This event is fired only on the reload process and **NOT** on any | ||||
| @@ -293,9 +283,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "after_reload_trigger") | ||||
|  | ||||
|     def before_server_start( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def before_server_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the before_server_start event. | ||||
|  | ||||
|         This event is fired on all worker processes. You should typically | ||||
| @@ -319,9 +307,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "before_server_start") | ||||
|  | ||||
|     def after_server_start( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def after_server_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the after_server_start event. | ||||
|  | ||||
|         This event is fired on all worker processes. You should typically | ||||
| @@ -349,9 +335,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "after_server_start") | ||||
|  | ||||
|     def before_server_stop( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def before_server_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the before_server_stop event. | ||||
|  | ||||
|         This event is fired on all worker processes. This event is fired | ||||
| @@ -376,9 +360,7 @@ class ListenerMixin(metaclass=SanicMeta): | ||||
|         """  # noqa: E501 | ||||
|         return self.listener(listener, "before_server_stop") | ||||
|  | ||||
|     def after_server_stop( | ||||
|         self, listener: ListenerType[Sanic] | ||||
|     ) -> ListenerType[Sanic]: | ||||
|     def after_server_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: | ||||
|         """Decorator for registering a listener for the after_server_stop event. | ||||
|  | ||||
|         This event is fired on all worker processes. This event is fired | ||||
|   | ||||
| @@ -25,7 +25,7 @@ class MiddlewareMixin(metaclass=SanicMeta): | ||||
|         attach_to: str = "request", | ||||
|         apply: bool = True, | ||||
|         *, | ||||
|         priority: int = 0 | ||||
|         priority: int = 0, | ||||
|     ) -> MiddlewareType: | ||||
|         ... | ||||
|  | ||||
| @@ -36,7 +36,7 @@ class MiddlewareMixin(metaclass=SanicMeta): | ||||
|         attach_to: str = "request", | ||||
|         apply: bool = True, | ||||
|         *, | ||||
|         priority: int = 0 | ||||
|         priority: int = 0, | ||||
|     ) -> Callable[[MiddlewareType], MiddlewareType]: | ||||
|         ... | ||||
|  | ||||
| @@ -46,7 +46,7 @@ class MiddlewareMixin(metaclass=SanicMeta): | ||||
|         attach_to: str = "request", | ||||
|         apply: bool = True, | ||||
|         *, | ||||
|         priority: int = 0 | ||||
|         priority: int = 0, | ||||
|     ) -> Union[MiddlewareType, Callable[[MiddlewareType], MiddlewareType]]: | ||||
|         """Decorator for registering middleware. | ||||
|  | ||||
| @@ -99,13 +99,9 @@ class MiddlewareMixin(metaclass=SanicMeta): | ||||
|  | ||||
|         # Detect which way this was called, @middleware or @middleware('AT') | ||||
|         if callable(middleware_or_request): | ||||
|             return register_middleware( | ||||
|                 middleware_or_request, attach_to=attach_to | ||||
|             ) | ||||
|             return register_middleware(middleware_or_request, attach_to=attach_to) | ||||
|         else: | ||||
|             return partial( | ||||
|                 register_middleware, attach_to=middleware_or_request | ||||
|             ) | ||||
|             return partial(register_middleware, attach_to=middleware_or_request) | ||||
|  | ||||
|     def on_request(self, middleware=None, *, priority=0) -> MiddlewareType: | ||||
|         """Register a middleware to be called before a request is handled. | ||||
| @@ -157,9 +153,7 @@ class MiddlewareMixin(metaclass=SanicMeta): | ||||
|         if callable(middleware): | ||||
|             return self.middleware(middleware, "response", priority=priority) | ||||
|         else: | ||||
|             return partial( | ||||
|                 self.middleware, attach_to="response", priority=priority | ||||
|             ) | ||||
|             return partial(self.middleware, attach_to="response", priority=priority) | ||||
|  | ||||
|     def finalize_middleware(self) -> None: | ||||
|         """Finalize the middleware configuration for the Sanic application. | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user