Compare commits
	
		
			3 Commits
		
	
	
		
			ruff-only
			...
			response-e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8027563144 | ||
|   | 875e921bda | ||
|   | 11c841ab4e | 
| @@ -1,4 +1,5 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Sanic documentation build configuration file, created by | ||||
| # sphinx-quickstart on Sun Dec 25 18:07:21 2016. | ||||
| @@ -9,6 +10,7 @@ | ||||
| import os | ||||
| import sys | ||||
|  | ||||
|  | ||||
| # Add support for auto-doc | ||||
|  | ||||
|  | ||||
| @@ -17,7 +19,8 @@ import sys | ||||
| root_directory = os.path.dirname(os.getcwd()) | ||||
| sys.path.insert(0, root_directory) | ||||
|  | ||||
| import sanic  # noqa: E402 | ||||
| import sanic | ||||
|  | ||||
|  | ||||
| # -- General configuration ------------------------------------------------ | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ from random import randint | ||||
| from sanic import Sanic | ||||
| from sanic.response import text | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -24,6 +25,5 @@ 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,8 +1,11 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| from functools import wraps | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| 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,6 +1,7 @@ | ||||
| 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,6 +2,7 @@ from asyncio import sleep | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("DelayedResponseApp", strict_slashes=True) | ||||
| app.config.AUTO_EXTEND = False | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ 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) | ||||
| @@ -38,6 +39,7 @@ server's error_handler to an instance of our CustomHandler | ||||
|  | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| handler = CustomHandler() | ||||
| app = Sanic("Example", error_handler=handler) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ from sanic import Sanic, response, text | ||||
| from sanic.handlers import ErrorHandler | ||||
| from sanic.server.async_server import AsyncioServer | ||||
|  | ||||
|  | ||||
| HTTP_PORT = 9999 | ||||
| HTTPS_PORT = 8888 | ||||
|  | ||||
| @@ -35,7 +36,9 @@ 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 | ||||
| @@ -66,6 +69,5 @@ 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,6 +5,7 @@ import httpx | ||||
| from sanic import Sanic | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
| sem = None | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| import logging | ||||
|  | ||||
| from contextvars import ContextVar | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import logging | ||||
| import socket | ||||
|  | ||||
| from os import getenv | ||||
| from platform import node | ||||
| from uuid import getnode as get_mac | ||||
| @@ -10,6 +11,7 @@ from sanic import Sanic | ||||
| from sanic.request import Request | ||||
| from sanic.response import json | ||||
|  | ||||
|  | ||||
| log = logging.getLogger("logdna") | ||||
| log.setLevel(logging.INFO) | ||||
|  | ||||
| @@ -33,7 +35,9 @@ 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) | ||||
| @@ -44,7 +48,7 @@ app = Sanic("Example") | ||||
|  | ||||
| @app.middleware | ||||
| def log_request(request: Request): | ||||
|     logdna.info(f"I was Here with a new Request to URL: {request.url}") | ||||
|     logdna.info("I was Here with a new Request to URL: {}".format(request.url)) | ||||
|  | ||||
|  | ||||
| @app.route("/") | ||||
|   | ||||
| @@ -4,6 +4,7 @@ Modify header or status in response | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ 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,6 +11,7 @@ Run with xdist params: | ||||
| import re | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from sanic_testing import SanicTestClient | ||||
| from sanic_testing.testing import PORT as PORT_BASE | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ 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,6 +4,7 @@ from sanic import Sanic, response | ||||
| from sanic.config import Config | ||||
| from sanic.exceptions import RequestTimeout | ||||
|  | ||||
|  | ||||
| Config.REQUEST_TIMEOUT = 1 | ||||
| app = Sanic("Example") | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from sanic import Sanic | ||||
| from sanic.exceptions import SanicException | ||||
| from sanic.handlers import ErrorHandler | ||||
|  | ||||
|  | ||||
| rollbar.init(getenv("ROLLBAR_API_KEY")) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ from pathlib import Path | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -42,7 +43,9 @@ 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,6 +4,7 @@ import uvloop | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import asyncio | ||||
|  | ||||
| from signal import SIGINT, signal | ||||
|  | ||||
| import uvloop | ||||
| @@ -6,6 +7,7 @@ import uvloop | ||||
| from sanic import Sanic, response | ||||
| from sanic.server import AsyncioServer | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -33,10 +35,11 @@ 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,6 +6,7 @@ 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,6 +2,7 @@ from sanic import Sanic | ||||
| from sanic.response import text | ||||
| from sanic.views import HTTPMethodView | ||||
|  | ||||
|  | ||||
| app = Sanic("some_name") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
| app.static("/", "./static") | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from sanic import Sanic | ||||
| from sanic import response as res | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from sanic import Sanic, response | ||||
| from sanic.exceptions import ServerError | ||||
| from sanic.log import logger as log | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -19,7 +20,7 @@ def test_sync(request): | ||||
|  | ||||
| @app.route("/dynamic/<name>/<i:int>") | ||||
| def test_params(request, name, i): | ||||
|     return response.text(f"yeehaww {name} {i}") | ||||
|     return response.text("yeehaww {} {}".format(name, i)) | ||||
|  | ||||
|  | ||||
| @app.route("/exception") | ||||
| @@ -42,7 +43,9 @@ 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,5 +1,6 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
| @@ -13,7 +14,7 @@ async def index(request): | ||||
|  | ||||
| @app.route("/posts/<post_id>") | ||||
| async def post_handler(request, post_id): | ||||
|     return response.text(f"Post - {post_id}") | ||||
|     return response.text("Post - {}".format(post_id)) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   | ||||
| @@ -2,6 +2,7 @@ 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,6 +1,7 @@ | ||||
| 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 | ||||
| @@ -11,7 +12,9 @@ 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,6 +1,7 @@ | ||||
| from sanic import Sanic | ||||
| from sanic.response import redirect | ||||
|  | ||||
|  | ||||
| app = Sanic("Example") | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -7,10 +7,14 @@ 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): | ||||
|   | ||||
| @@ -1,5 +0,0 @@ | ||||
| [tool.ruff] | ||||
| extend = "../pyproject.toml" | ||||
|  | ||||
| [tool.ruff.isort] | ||||
| known-first-party = ["webapp"] | ||||
| @@ -13,7 +13,9 @@ 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: | ||||
| @@ -62,7 +64,9 @@ 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,14 +1,17 @@ | ||||
| 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"), | ||||
| @@ -43,7 +46,10 @@ 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 html5tagger import Builder, E  # type: ignore | ||||
| from sanic import Request | ||||
|  | ||||
| from webapp.display.layouts.models import MenuItem | ||||
| from webapp.display.text import slugify | ||||
|  | ||||
| from html5tagger import Builder, E  # type: ignore | ||||
| from sanic import Request | ||||
|  | ||||
|  | ||||
| def do_sidebar(builder: Builder, request: Request) -> None: | ||||
|     builder.a(class_="burger")(E.span().span().span().span()) | ||||
| @@ -15,7 +15,9 @@ 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" | ||||
| @@ -71,7 +73,9 @@ 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,7 +1,6 @@ | ||||
| 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 | ||||
| @@ -9,6 +8,8 @@ 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 | ||||
| @@ -36,9 +37,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) | ||||
| @@ -46,7 +47,9 @@ 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 | ||||
|         ) | ||||
| @@ -90,7 +93,9 @@ 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,6 +10,7 @@ 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 | ||||
| @@ -119,7 +120,9 @@ 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 ( | ||||
| @@ -153,7 +156,9 @@ 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) | ||||
| @@ -207,7 +212,9 @@ 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: | ||||
| @@ -232,7 +239,9 @@ 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>(" | ||||
| @@ -246,7 +255,9 @@ 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": | ||||
| @@ -257,7 +268,9 @@ 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) | ||||
|  | ||||
| @@ -305,7 +318,10 @@ 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 "" | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
| @@ -318,7 +334,11 @@ 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 "" | ||||
|                         ) | ||||
|                     ) | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| @@ -334,7 +354,11 @@ 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") | ||||
| @@ -349,11 +373,17 @@ 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,6 +2,7 @@ from __future__ import annotations | ||||
|  | ||||
| from dataclasses import dataclass, field | ||||
| from pathlib import Path | ||||
| from typing import Type | ||||
|  | ||||
| from frontmatter import parse | ||||
|  | ||||
| @@ -11,8 +12,10 @@ 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, | ||||
| } | ||||
| @@ -40,7 +43,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,12 +1,13 @@ | ||||
| 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 | ||||
|  | ||||
| @@ -20,9 +21,13 @@ 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 | ||||
| @@ -34,7 +39,9 @@ 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,11 +2,12 @@ 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): | ||||
| @@ -15,7 +16,9 @@ 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,7 +10,9 @@ 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() | ||||
| @@ -34,7 +36,9 @@ 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,12 +16,16 @@ 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,16 +3,19 @@ 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,7 @@ | ||||
| from html5tagger import HTML, E | ||||
| from mistune.directives import Admonition | ||||
|  | ||||
| from html5tagger import HTML, E | ||||
|  | ||||
|  | ||||
| class Notification(Admonition): | ||||
|     SUPPORTED_NAMES = { | ||||
| @@ -19,8 +20,12 @@ 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,7 +10,9 @@ 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() | ||||
| @@ -39,7 +41,9 @@ 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,7 +5,6 @@ from pathlib import Path | ||||
| from typing import ClassVar | ||||
|  | ||||
| from msgspec import Struct | ||||
|  | ||||
| from webapp.display.page import Page | ||||
|  | ||||
|  | ||||
| @@ -92,7 +91,9 @@ 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] | ||||
| @@ -101,7 +102,9 @@ def _tf_idf_vector(document: Document, idf: dict[str, float]) -> dict[str, float | ||||
|     } | ||||
|  | ||||
|  | ||||
| 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 | ||||
| @@ -123,7 +126,9 @@ 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( | ||||
| @@ -150,13 +155,16 @@ 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,7 +1,6 @@ | ||||
| from pathlib import Path | ||||
|  | ||||
| from msgspec import yaml | ||||
|  | ||||
| from webapp.display.layouts.models import GeneralConfig, MenuItem | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| 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 | ||||
| @@ -9,6 +7,8 @@ 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,9 +28,13 @@ 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) | ||||
| @@ -62,6 +66,8 @@ 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,6 +5,7 @@ from queue import Empty, Queue | ||||
| from typing import Any | ||||
|  | ||||
| import ujson | ||||
|  | ||||
| from sanic import Request, Sanic, Websocket | ||||
|  | ||||
|  | ||||
| @@ -53,12 +54,16 @@ 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" | ||||
|         ) | ||||
| @@ -104,5 +109,7 @@ 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,11 +1,11 @@ | ||||
| # from scss.compiler import compile_string | ||||
|  | ||||
| from pygments.formatters import html | ||||
| from sanic import Sanic | ||||
| from sass import compile as compile_scss | ||||
|  | ||||
| from webapp.display.code_style import SanicCodeStyle | ||||
|  | ||||
| from sanic import Sanic | ||||
|  | ||||
|  | ||||
| def setup_style(app: Sanic) -> None: | ||||
|     index = app.config.STYLE_DIR / "index.scss" | ||||
|   | ||||
| @@ -2,28 +2,20 @@ | ||||
| requires = ["setuptools", "wheel"] | ||||
| build-backend = "setuptools.build_meta" | ||||
|  | ||||
| [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.black] | ||||
| line-length = 79 | ||||
|  | ||||
| [tool.ruff.isort] | ||||
| known-first-party = ["sanic"] | ||||
| known-third-party = ["pytest"] | ||||
| [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.mypy.overrides]] | ||||
| module = [ | ||||
|   | ||||
| @@ -36,6 +36,7 @@ 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,6 +1,7 @@ | ||||
| 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,6 +5,7 @@ import logging | ||||
| import logging.config | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from asyncio import ( | ||||
|     AbstractEventLoop, | ||||
|     CancelledError, | ||||
| @@ -31,12 +32,19 @@ from typing import ( | ||||
|     Callable, | ||||
|     ClassVar, | ||||
|     Coroutine, | ||||
|     Deque, | ||||
|     Dict, | ||||
|     Generic, | ||||
|     Iterable, | ||||
|     Iterator, | ||||
|     List, | ||||
|     Literal, | ||||
|     Optional, | ||||
|     Set, | ||||
|     Tuple, | ||||
|     Type, | ||||
|     TypeVar, | ||||
|     Union, | ||||
|     cast, | ||||
|     overload, | ||||
| ) | ||||
| @@ -88,6 +96,7 @@ 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 | ||||
| @@ -164,7 +173,7 @@ class Sanic( | ||||
|         "websocket_tasks", | ||||
|     ) | ||||
|  | ||||
|     _app_registry: ClassVar[dict[str, Sanic]] = {} | ||||
|     _app_registry: ClassVar[Dict[str, "Sanic"]] = {} | ||||
|     test_mode: ClassVar[bool] = False | ||||
|  | ||||
|     @overload | ||||
| @@ -173,19 +182,19 @@ class Sanic( | ||||
|         name: str, | ||||
|         config: None = None, | ||||
|         ctx: 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, | ||||
|         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, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
| @@ -193,21 +202,21 @@ class Sanic( | ||||
|     def __init__( | ||||
|         self: Sanic[config_type, SimpleNamespace], | ||||
|         name: str, | ||||
|         config: config_type | None = None, | ||||
|         config: Optional[config_type] = None, | ||||
|         ctx: 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, | ||||
|         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, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
| @@ -216,20 +225,20 @@ class Sanic( | ||||
|         self: Sanic[Config, ctx_type], | ||||
|         name: str, | ||||
|         config: 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, | ||||
|         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, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
| @@ -237,42 +246,42 @@ class Sanic( | ||||
|     def __init__( | ||||
|         self: Sanic[config_type, ctx_type], | ||||
|         name: str, | ||||
|         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, | ||||
|         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, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|     ) -> None: | ||||
|         ... | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         name: str, | ||||
|         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, | ||||
|         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, | ||||
|         strict_slashes: bool = False, | ||||
|         log_config: dict[str, Any] | None = None, | ||||
|         log_config: Optional[Dict[str, Any]] = None, | ||||
|         configure_logging: bool = True, | ||||
|         dumps: Callable[..., AnyStr] | None = None, | ||||
|         loads: Callable[..., Any] | None = None, | ||||
|         dumps: Optional[Callable[..., AnyStr]] = None, | ||||
|         loads: Optional[Callable[..., Any]] = None, | ||||
|         inspector: bool = False, | ||||
|         inspector_class: type[Inspector] | None = None, | ||||
|         certloader_class: type[CertLoader] | None = None, | ||||
|         inspector_class: Optional[Type[Inspector]] = None, | ||||
|         certloader_class: Optional[Type[CertLoader]] = None, | ||||
|     ) -> None: | ||||
|         super().__init__(name=name) | ||||
|         # logging | ||||
| @@ -294,39 +303,41 @@ class Sanic( | ||||
|             self.config.INSPECTOR = inspector | ||||
|  | ||||
|         # Then we can do the rest | ||||
|         self._asgi_app: ASGIApp | None = None | ||||
|         self._asgi_lifespan: Lifespan | None = None | ||||
|         self._asgi_app: Optional[ASGIApp] = None | ||||
|         self._asgi_lifespan: Optional[Lifespan] = 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: Inspector | None = None | ||||
|         self._manager: WorkerManager | None = None | ||||
|         self._inspector: Optional[Inspector] = None | ||||
|         self._manager: Optional[WorkerManager] = None | ||||
|         self._state: ApplicationState = ApplicationState(app=self) | ||||
|         self._task_registry: dict[str, Task | None] = {} | ||||
|         self._task_registry: Dict[str, Union[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: socket | None = None | ||||
|         self.sock: Optional[socket] = 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 | ||||
| @@ -385,11 +396,15 @@ class Sanic( | ||||
|         try: | ||||
|             _event = ListenerEvent[event.upper()] | ||||
|         except (ValueError, AttributeError): | ||||
|             valid = ", ".join(x.lower() for x in ListenerEvent.__members__.keys()) | ||||
|             valid = ", ".join( | ||||
|                 map(lambda x: x.lower(), 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) | ||||
|  | ||||
| @@ -397,11 +412,11 @@ class Sanic( | ||||
|  | ||||
|     def register_middleware( | ||||
|         self, | ||||
|         middleware: MiddlewareType | Middleware, | ||||
|         middleware: Union[MiddlewareType, Middleware], | ||||
|         attach_to: str = "request", | ||||
|         *, | ||||
|         priority: Default | int = _default, | ||||
|     ) -> MiddlewareType | Middleware: | ||||
|         priority: Union[Default, int] = _default, | ||||
|     ) -> Union[MiddlewareType, Middleware]: | ||||
|         """Register a middleware to be called before a request is handled. | ||||
|  | ||||
|         Args: | ||||
| @@ -446,7 +461,7 @@ class Sanic( | ||||
|         route_names: Iterable[str], | ||||
|         attach_to: str = "request", | ||||
|         *, | ||||
|         priority: Default | int = _default, | ||||
|         priority: Union[Default, int] = _default, | ||||
|     ): | ||||
|         """Used to register named middleqare (middleware typically on blueprints) | ||||
|  | ||||
| @@ -497,7 +512,7 @@ class Sanic( | ||||
|     def _apply_exception_handler( | ||||
|         self, | ||||
|         handler: FutureException, | ||||
|         route_names: list[str] | None = None, | ||||
|         route_names: Optional[List[str]] = None, | ||||
|     ): | ||||
|         """Decorate a function to be registered as a handler for exceptions | ||||
|  | ||||
| @@ -516,7 +531,9 @@ 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) | ||||
| @@ -550,7 +567,7 @@ class Sanic( | ||||
|     def _apply_middleware( | ||||
|         self, | ||||
|         middleware: FutureMiddleware, | ||||
|         route_names: list[str] | None = None, | ||||
|         route_names: Optional[List[str]] = None, | ||||
|     ): | ||||
|         with self.amend(): | ||||
|             if route_names: | ||||
| @@ -571,8 +588,8 @@ class Sanic( | ||||
|         self, | ||||
|         event: str, | ||||
|         *, | ||||
|         condition: dict[str, str] | None = None, | ||||
|         context: dict[str, Any] | None = None, | ||||
|         condition: Optional[Dict[str, str]] = None, | ||||
|         context: Optional[Dict[str, Any]] = None, | ||||
|         fail_not_found: bool = True, | ||||
|         inline: Literal[True], | ||||
|         reverse: bool = False, | ||||
| @@ -584,8 +601,8 @@ class Sanic( | ||||
|         self, | ||||
|         event: str, | ||||
|         *, | ||||
|         condition: dict[str, str] | None = None, | ||||
|         context: dict[str, Any] | None = None, | ||||
|         condition: Optional[Dict[str, str]] = None, | ||||
|         context: Optional[Dict[str, Any]] = None, | ||||
|         fail_not_found: bool = True, | ||||
|         inline: Literal[False] = False, | ||||
|         reverse: bool = False, | ||||
| @@ -596,12 +613,12 @@ class Sanic( | ||||
|         self, | ||||
|         event: str, | ||||
|         *, | ||||
|         condition: dict[str, str] | None = None, | ||||
|         context: dict[str, Any] | None = None, | ||||
|         condition: Optional[Dict[str, str]] = None, | ||||
|         context: Optional[Dict[str, Any]] = None, | ||||
|         fail_not_found: bool = True, | ||||
|         inline: bool = False, | ||||
|         reverse: bool = False, | ||||
|     ) -> Coroutine[Any, Any, Awaitable[Task | Any]]: | ||||
|     ) -> Coroutine[Any, Any, Awaitable[Union[Task, Any]]]: | ||||
|         """Dispatches an event to the signal router. | ||||
|  | ||||
|         Args: | ||||
| @@ -645,7 +662,9 @@ class Sanic( | ||||
|             fail_not_found=fail_not_found, | ||||
|         ) | ||||
|  | ||||
|     async def event(self, event: str, timeout: int | float | None = None) -> None: | ||||
|     async def event( | ||||
|         self, event: str, timeout: Optional[Union[int, float]] = None | ||||
|     ) -> None: | ||||
|         """Wait for a specific event to be triggered. | ||||
|  | ||||
|         This method waits for a named event to be triggered and can be used | ||||
| @@ -730,7 +749,9 @@ 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 | ||||
|  | ||||
| @@ -759,13 +780,13 @@ class Sanic( | ||||
|  | ||||
|     def blueprint( | ||||
|         self, | ||||
|         blueprint: Blueprint | (Iterable[Blueprint] | BlueprintGroup), | ||||
|         blueprint: Union[Blueprint, Iterable[Blueprint], BlueprintGroup], | ||||
|         *, | ||||
|         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, | ||||
|         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, | ||||
|     ) -> None: | ||||
|         """Register a blueprint on the application. | ||||
|  | ||||
| @@ -791,7 +812,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: | ||||
| @@ -804,7 +825,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", ""), | ||||
| @@ -819,12 +840,14 @@ 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) | ||||
| @@ -834,14 +857,17 @@ class Sanic( | ||||
|             return | ||||
|         if blueprint.name in self.blueprints: | ||||
|             assert self.blueprints[blueprint.name] is blueprint, ( | ||||
|                 f'A blueprint with the name "{blueprint.name}" is already registered.  ' | ||||
|                 "Blueprint names must be unique." | ||||
|                 'A blueprint with the name "%s" is already registered.  ' | ||||
|                 "Blueprint names must be unique." % (blueprint.name,) | ||||
|             ) | ||||
|         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) | ||||
|  | ||||
| @@ -897,7 +923,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: | ||||
| @@ -911,7 +937,9 @@ 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 | ||||
|  | ||||
| @@ -950,7 +978,9 @@ 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 " | ||||
| @@ -1066,7 +1096,10 @@ 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 " | ||||
| @@ -1113,7 +1146,10 @@ 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: | ||||
| @@ -1158,7 +1194,9 @@ 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. | ||||
| @@ -1183,11 +1221,13 @@ class Sanic( | ||||
|  | ||||
|         # Define `response` var here to remove warnings about | ||||
|         # allocation before assignment below. | ||||
|         response: ( | ||||
|             BaseHTTPResponse | ||||
|             | (Coroutine[Any, Any, BaseHTTPResponse | None] | ResponseStream) | ||||
|             | None | ||||
|         ) = None | ||||
|         response: Optional[ | ||||
|             Union[ | ||||
|                 BaseHTTPResponse, | ||||
|                 Coroutine[Any, Any, Optional[BaseHTTPResponse]], | ||||
|                 ResponseStream, | ||||
|             ] | ||||
|         ] = None | ||||
|         run_middleware = True | ||||
|         try: | ||||
|             await self.dispatch( | ||||
| @@ -1245,8 +1285,10 @@ 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 | ||||
| @@ -1305,14 +1347,17 @@ 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 | ||||
| @@ -1391,7 +1436,9 @@ 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: | ||||
| @@ -1468,7 +1515,9 @@ 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: | ||||
| @@ -1497,7 +1546,9 @@ 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( | ||||
| @@ -1515,7 +1566,7 @@ class Sanic( | ||||
|         app, | ||||
|         loop, | ||||
|         *, | ||||
|         name: str | None = None, | ||||
|         name: Optional[str] = None, | ||||
|         register: bool = True, | ||||
|     ) -> Task: | ||||
|         if not isinstance(task, Future): | ||||
| @@ -1577,11 +1628,11 @@ class Sanic( | ||||
|  | ||||
|     def add_task( | ||||
|         self, | ||||
|         task: Future[Any] | (Coroutine[Any, Any, Any] | Awaitable[Any]), | ||||
|         task: Union[Future[Any], Coroutine[Any, Any, Any], Awaitable[Any]], | ||||
|         *, | ||||
|         name: str | None = None, | ||||
|         name: Optional[str] = None, | ||||
|         register: bool = True, | ||||
|     ) -> Task[Any] | None: | ||||
|     ) -> Optional[Task[Any]]: | ||||
|         """Schedule a task to run later, after the loop has started. | ||||
|  | ||||
|         While this is somewhat similar to `asyncio.create_task`, it can be | ||||
| @@ -1606,14 +1657,18 @@ 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) | ||||
| @@ -1624,14 +1679,18 @@ class Sanic( | ||||
|         ... | ||||
|  | ||||
|     @overload | ||||
|     def get_task(self, name: str, *, raise_exception: Literal[False]) -> Task | None: | ||||
|     def get_task( | ||||
|         self, name: str, *, raise_exception: Literal[False] | ||||
|     ) -> Optional[Task]: | ||||
|         ... | ||||
|  | ||||
|     @overload | ||||
|     def get_task(self, name: str, *, raise_exception: bool) -> Task | None: | ||||
|     def get_task(self, name: str, *, raise_exception: bool) -> Optional[Task]: | ||||
|         ... | ||||
|  | ||||
|     def get_task(self, name: str, *, raise_exception: bool = True) -> Task | None: | ||||
|     def get_task( | ||||
|         self, name: str, *, raise_exception: bool = True | ||||
|     ) -> Optional[Task]: | ||||
|         """Get a named task. | ||||
|  | ||||
|         This method is used to get a task by its name. Optionally, you can | ||||
| @@ -1649,13 +1708,15 @@ 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: str | None = None, | ||||
|         msg: Optional[str] = None, | ||||
|         *, | ||||
|         raise_exception: bool = True, | ||||
|     ) -> None: | ||||
| @@ -1690,7 +1751,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,) | ||||
| @@ -1723,7 +1784,7 @@ class Sanic( | ||||
|         } | ||||
|  | ||||
|     def shutdown_tasks( | ||||
|         self, timeout: float | None = None, increment: float = 0.1 | ||||
|         self, timeout: Optional[float] = None, increment: float = 0.1 | ||||
|     ) -> None: | ||||
|         """Cancel all tasks except the server task. | ||||
|  | ||||
| @@ -1761,7 +1822,11 @@ 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 | ||||
| @@ -1788,7 +1853,7 @@ class Sanic( | ||||
|     # Configuration | ||||
|     # -------------------------------------------------------------------- # | ||||
|  | ||||
|     def update_config(self, config: Any) -> None: | ||||
|     def update_config(self, config: Union[bytes, str, dict, Any]) -> None: | ||||
|         """Update the application configuration. | ||||
|  | ||||
|         This method is used to update the application configuration. It can | ||||
| @@ -1798,7 +1863,7 @@ class Sanic( | ||||
|         See [Configuration](/en/guide/deployment/configuration) for details. | ||||
|  | ||||
|         Args: | ||||
|             config (bytes | str | dict | Any): The configuration object, | ||||
|             config (Union[bytes, str, dict, Any]): The configuration object, | ||||
|                 dictionary, or path to a configuration file. | ||||
|         """ | ||||
|  | ||||
| @@ -1838,7 +1903,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: | ||||
| @@ -1883,9 +1948,9 @@ class Sanic( | ||||
|     def extend( | ||||
|         self, | ||||
|         *, | ||||
|         extensions: list[type[Extension]] | None = None, | ||||
|         extensions: Optional[List[Type[Extension]]] = None, | ||||
|         built_in_extensions: bool = True, | ||||
|         config: Config | dict[str, Any] | None = None, | ||||
|         config: Optional[Union[Config, Dict[str, Any]]] = None, | ||||
|         **kwargs, | ||||
|     ) -> Extend: | ||||
|         """Extend Sanic with additional functionality using Sanic Extensions. | ||||
| @@ -2003,7 +2068,9 @@ class Sanic( | ||||
|             del cls._app_registry[name] | ||||
|  | ||||
|     @classmethod | ||||
|     def get_app(cls, name: str | None = None, *, force_create: bool = False) -> Sanic: | ||||
|     def get_app( | ||||
|         cls, name: Optional[str] = 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 | ||||
| @@ -2210,7 +2277,9 @@ 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 = ( | ||||
| @@ -2247,7 +2316,7 @@ class Sanic( | ||||
|         self, | ||||
|         concern: str, | ||||
|         action: str, | ||||
|         loop: AbstractEventLoop | None = None, | ||||
|         loop: Optional[AbstractEventLoop] = None, | ||||
|     ) -> None: | ||||
|         event = f"server.{concern}.{action}" | ||||
|         if action not in ("before", "after") or concern not in ( | ||||
| @@ -2255,7 +2324,9 @@ 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 | ||||
| @@ -2276,7 +2347,7 @@ class Sanic( | ||||
|  | ||||
|     def refresh( | ||||
|         self, | ||||
|         passthru: dict[str, Any] | None = None, | ||||
|         passthru: Optional[Dict[str, Any]] = None, | ||||
|     ) -> Sanic: | ||||
|         """Refresh the application instance. **This is used internally by Sanic**. | ||||
|  | ||||
| @@ -2321,7 +2392,9 @@ 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 | ||||
| @@ -2354,5 +2427,7 @@ 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,6 +4,7 @@ from contextlib import suppress | ||||
| from importlib import import_module | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from os import environ | ||||
|  | ||||
| from sanic.helpers import is_atty | ||||
|  | ||||
|  | ||||
| BASE_LOGO = """ | ||||
|  | ||||
|                  Sanic | ||||
| @@ -61,7 +63,10 @@ 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,7 +79,9 @@ 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,10 +1,12 @@ | ||||
| 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 | ||||
|  | ||||
| @@ -45,16 +47,21 @@ class Spinner:  # noqa | ||||
|     @staticmethod | ||||
|     def cursor(): | ||||
|         while True: | ||||
|             yield from "|/-\\" | ||||
|             for cursor in "|/-\\": | ||||
|                 yield cursor | ||||
|  | ||||
|     @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() | ||||
| @@ -64,9 +71,13 @@ 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,16 +1,18 @@ | ||||
| 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 | ||||
| from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union | ||||
|  | ||||
| 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 | ||||
|  | ||||
| @@ -19,9 +21,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: AsyncioServer | None = field(default=None) | ||||
|     server: Optional[AsyncioServer] = field(default=None) | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| @@ -38,11 +40,11 @@ class ApplicationState: | ||||
|     fast: bool = field(default=False) | ||||
|     host: str = field(default="") | ||||
|     port: int = field(default=0) | ||||
|     ssl: SSLContext | None = field(default=None) | ||||
|     sock: socket | None = field(default=None) | ||||
|     unix: str | None = field(default=None) | ||||
|     ssl: Optional[SSLContext] = field(default=None) | ||||
|     sock: Optional[socket] = field(default=None) | ||||
|     unix: Optional[str] = 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) | ||||
| @@ -51,7 +53,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 | ||||
| @@ -62,12 +64,14 @@ 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: str | Mode): | ||||
|     def set_mode(self, value: Union[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: | ||||
| @@ -103,7 +107,9 @@ 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,7 +1,8 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import warnings | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from typing import TYPE_CHECKING, Optional | ||||
|  | ||||
| from sanic.compat import Header | ||||
| from sanic.exceptions import BadRequest, ServerError | ||||
| @@ -14,6 +15,7 @@ from sanic.response import BaseHTTPResponse | ||||
| from sanic.server import ConnInfo | ||||
| from sanic.server.websockets.connection import WebSocketConnection | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
| @@ -107,9 +109,9 @@ class ASGIApp: | ||||
|     request: Request | ||||
|     transport: MockTransport | ||||
|     lifespan: Lifespan | ||||
|     ws: WebSocketConnection | None | ||||
|     ws: Optional[WebSocketConnection] | ||||
|     stage: Stage | ||||
|     response: BaseHTTPResponse | None | ||||
|     response: Optional[BaseHTTPResponse] | ||||
|  | ||||
|     @classmethod | ||||
|     async def create( | ||||
| @@ -140,7 +142,9 @@ 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"] | ||||
| @@ -149,7 +153,9 @@ 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") | ||||
|  | ||||
| @@ -183,7 +189,7 @@ class ASGIApp: | ||||
|  | ||||
|         return instance | ||||
|  | ||||
|     async def read(self) -> bytes | None: | ||||
|     async def read(self) -> Optional[bytes]: | ||||
|         """ | ||||
|         Read and stream the body in chunks from an incoming ASGI message. | ||||
|         """ | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import re | ||||
|  | ||||
| from typing import Any, Optional | ||||
|  | ||||
| from sanic.base.meta import SanicMeta | ||||
| @@ -10,6 +11,7 @@ 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_\-]*$") | ||||
|  | ||||
|  | ||||
| @@ -24,7 +26,9 @@ 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,3 +1,4 @@ | ||||
| from .blueprints import BlueprintGroup | ||||
|  | ||||
|  | ||||
| __all__ = ["BlueprintGroup"]  # noqa: F405 | ||||
|   | ||||
| @@ -2,6 +2,7 @@ from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
| import sys | ||||
|  | ||||
| from collections import defaultdict | ||||
| from collections.abc import MutableSequence | ||||
| from copy import deepcopy | ||||
| @@ -13,9 +14,15 @@ from typing import ( | ||||
|     TYPE_CHECKING, | ||||
|     Any, | ||||
|     Callable, | ||||
|     Dict, | ||||
|     Iterable, | ||||
|     Iterator, | ||||
|     List, | ||||
|     Optional, | ||||
|     Sequence, | ||||
|     Set, | ||||
|     Tuple, | ||||
|     Union, | ||||
|     overload, | ||||
| ) | ||||
|  | ||||
| @@ -32,6 +39,7 @@ from sanic.models.handler_types import ( | ||||
|     RouteHandler, | ||||
| ) | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|  | ||||
| @@ -114,10 +122,10 @@ class Blueprint(BaseSanic): | ||||
|     def __init__( | ||||
|         self, | ||||
|         name: str, | ||||
|         url_prefix: str | None = None, | ||||
|         host: list[str] | str | None = None, | ||||
|         version: int | str | float | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         url_prefix: Optional[str] = None, | ||||
|         host: Optional[Union[List[str], str]] = None, | ||||
|         version: Optional[Union[int, str, float]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         version_prefix: str = "/v", | ||||
|     ): | ||||
|         super().__init__(name=name) | ||||
| @@ -128,7 +136,9 @@ 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 | ||||
| @@ -151,7 +161,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: | ||||
| @@ -162,7 +172,9 @@ 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 | ||||
| @@ -184,23 +196,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: 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, | ||||
|         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, | ||||
|         with_registration: bool = True, | ||||
|         with_ctx: bool = False, | ||||
|     ): | ||||
| @@ -265,12 +277,12 @@ class Blueprint(BaseSanic): | ||||
|  | ||||
|     @staticmethod | ||||
|     def group( | ||||
|         *blueprints: Blueprint | BlueprintGroup, | ||||
|         url_prefix: str | None = None, | ||||
|         version: int | str | float | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         *blueprints: Union[Blueprint, BlueprintGroup], | ||||
|         url_prefix: Optional[str] = None, | ||||
|         version: Optional[Union[int, str, float]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         version_prefix: str = "/v", | ||||
|         name_prefix: str | None = "", | ||||
|         name_prefix: Optional[str] = "", | ||||
|     ) -> BlueprintGroup: | ||||
|         """Group multiple blueprints (or other blueprint groups) together. | ||||
|  | ||||
| @@ -341,7 +353,9 @@ 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 = [] | ||||
| @@ -367,7 +381,9 @@ 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 | ||||
|             ) | ||||
| @@ -403,16 +419,22 @@ 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 | ||||
| @@ -457,7 +479,7 @@ class Blueprint(BaseSanic): | ||||
|                 continue | ||||
|             future.condition.update({"__blueprint__": self.name}) | ||||
|             # Force exclusive to be False | ||||
|             app._apply_signal((*future[:-1], False)) | ||||
|             app._apply_signal(tuple((*future[:-1], False))) | ||||
|  | ||||
|         self.routes += [route for route in routes if isinstance(route, Route)] | ||||
|         self.websocket_routes += [ | ||||
| @@ -490,9 +512,11 @@ 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: int | float | None = None): | ||||
|     def event(self, event: str, timeout: Optional[Union[int, float]] = None): | ||||
|         """Wait for a signal event to be dispatched. | ||||
|  | ||||
|         Args: | ||||
| @@ -526,7 +550,7 @@ class Blueprint(BaseSanic): | ||||
|         return value | ||||
|  | ||||
|     @staticmethod | ||||
|     def _setup_uri(base: str, prefix: str | None): | ||||
|     def _setup_uri(base: str, prefix: Optional[str]): | ||||
|         uri = base | ||||
|         if prefix: | ||||
|             uri = prefix | ||||
| @@ -539,7 +563,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. | ||||
|  | ||||
| @@ -551,7 +575,7 @@ class Blueprint(BaseSanic): | ||||
|         """ | ||||
|  | ||||
|         for app in apps: | ||||
|             app._future_registry.update({(bp, item) for item in futures}) | ||||
|             app._future_registry.update(set((bp, item) for item in futures)) | ||||
|  | ||||
|  | ||||
| if sys.version_info < (3, 9): | ||||
| @@ -643,13 +667,13 @@ class BlueprintGroup(bpg_base): | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         url_prefix: str | None = None, | ||||
|         version: int | str | float | None = None, | ||||
|         strict_slashes: bool | None = None, | ||||
|         url_prefix: Optional[str] = None, | ||||
|         version: Optional[Union[int, str, float]] = None, | ||||
|         strict_slashes: Optional[bool] = None, | ||||
|         version_prefix: str = "/v", | ||||
|         name_prefix: str | None = "", | ||||
|         name_prefix: Optional[str] = "", | ||||
|     ): | ||||
|         self._blueprints: list[Blueprint] = [] | ||||
|         self._blueprints: List[Blueprint] = [] | ||||
|         self._url_prefix = url_prefix | ||||
|         self._version = version | ||||
|         self._version_prefix = version_prefix | ||||
| @@ -657,7 +681,7 @@ class BlueprintGroup(bpg_base): | ||||
|         self._name_prefix = name_prefix | ||||
|  | ||||
|     @property | ||||
|     def url_prefix(self) -> int | str | float | None: | ||||
|     def url_prefix(self) -> Optional[Union[int, str, float]]: | ||||
|         """The URL prefix for the Blueprint Group. | ||||
|  | ||||
|         Returns: | ||||
| @@ -667,7 +691,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: | ||||
| @@ -677,7 +701,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._blueprints | ||||
|  | ||||
|     @property | ||||
|     def version(self) -> str | int | float | None: | ||||
|     def version(self) -> Optional[Union[str, int, float]]: | ||||
|         """API Version for the Blueprint Group, if any. | ||||
|  | ||||
|         Returns: | ||||
| @@ -686,7 +710,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._version | ||||
|  | ||||
|     @property | ||||
|     def strict_slashes(self) -> bool | None: | ||||
|     def strict_slashes(self) -> Optional[bool]: | ||||
|         """Whether to enforce strict slashes for the Blueprint Group. | ||||
|  | ||||
|         Returns: | ||||
| @@ -704,7 +728,7 @@ class BlueprintGroup(bpg_base): | ||||
|         return self._version_prefix | ||||
|  | ||||
|     @property | ||||
|     def name_prefix(self) -> str | None: | ||||
|     def name_prefix(self) -> Optional[str]: | ||||
|         """Name prefix for the Blueprint Group. | ||||
|  | ||||
|         This is mainly needed when blueprints are copied in order to | ||||
| @@ -731,7 +755,9 @@ class BlueprintGroup(bpg_base): | ||||
|     def __getitem__(self, item: slice) -> MutableSequence[Blueprint]: | ||||
|         ... | ||||
|  | ||||
|     def __getitem__(self, item: int | slice) -> Blueprint | MutableSequence[Blueprint]: | ||||
|     def __getitem__( | ||||
|         self, item: Union[int, slice] | ||||
|     ) -> Union[Blueprint, MutableSequence[Blueprint]]: | ||||
|         """Get the Blueprint object at the specified index. | ||||
|  | ||||
|         This method returns a blueprint inside the group specified by | ||||
| @@ -759,8 +785,8 @@ class BlueprintGroup(bpg_base): | ||||
|  | ||||
|     def __setitem__( | ||||
|         self, | ||||
|         index: int | slice, | ||||
|         item: Blueprint | Iterable[Blueprint], | ||||
|         index: Union[int, slice], | ||||
|         item: Union[Blueprint, Iterable[Blueprint]], | ||||
|     ) -> None: | ||||
|         """Set the Blueprint object at the specified index. | ||||
|  | ||||
| @@ -798,7 +824,7 @@ class BlueprintGroup(bpg_base): | ||||
|     def __delitem__(self, index: slice) -> None: | ||||
|         ... | ||||
|  | ||||
|     def __delitem__(self, index: int | slice) -> None: | ||||
|     def __delitem__(self, index: Union[int, slice]) -> None: | ||||
|         """Delete the Blueprint object at the specified index. | ||||
|  | ||||
|         Abstract method implemented to turn the `BlueprintGroup` class | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import os | ||||
| import shutil | ||||
| import sys | ||||
|  | ||||
| from argparse import Namespace | ||||
| from functools import partial | ||||
| from textwrap import indent | ||||
| @@ -56,7 +57,9 @@ 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 | ||||
| @@ -124,7 +127,11 @@ 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__} | ||||
| @@ -174,7 +181,8 @@ 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: | ||||
| @@ -188,7 +196,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({"cert": self.args.cert, "key": self.args.key}) | ||||
|             ssl.append(dict(cert=self.args.cert, key=self.args.key)) | ||||
|         if self.args.tls: | ||||
|             ssl += self.args.tls | ||||
|         if not ssl: | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from argparse import ArgumentParser, _ArgumentGroup | ||||
| from typing import List, Optional, Type, Union | ||||
|  | ||||
| from sanic_routing import __version__ as __routing_version__ | ||||
|  | ||||
| @@ -9,14 +10,14 @@ from sanic.http.constants import HTTP | ||||
|  | ||||
|  | ||||
| class Group: | ||||
|     name: str | None | ||||
|     container: ArgumentParser | _ArgumentGroup | ||||
|     _registry: list[type[Group]] = [] | ||||
|     name: Optional[str] | ||||
|     container: Union[ArgumentParser, _ArgumentGroup] | ||||
|     _registry: List[Type[Group]] = [] | ||||
|  | ||||
|     def __init_subclass__(cls) -> None: | ||||
|         Group._registry.append(cls) | ||||
|  | ||||
|     def __init__(self, parser: ArgumentParser, title: str | None): | ||||
|     def __init__(self, parser: ArgumentParser, title: Optional[str]): | ||||
|         self.parser = parser | ||||
|  | ||||
|         if title: | ||||
| @@ -244,7 +245,10 @@ 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,9 +1,10 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import sys | ||||
|  | ||||
| from http.client import RemoteDisconnected | ||||
| from textwrap import indent | ||||
| from typing import Any | ||||
| from typing import Any, Dict, Optional | ||||
| from urllib.error import URLError | ||||
| from urllib.request import Request as URequest | ||||
| from urllib.request import urlopen | ||||
| @@ -12,6 +13,7 @@ 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 | ||||
| @@ -25,7 +27,7 @@ class InspectorClient: | ||||
|         port: int, | ||||
|         secure: bool, | ||||
|         raw: bool, | ||||
|         api_key: str | None, | ||||
|         api_key: Optional[str], | ||||
|     ) -> None: | ||||
|         self.scheme = "https" if secure else "http" | ||||
|         self.host = host | ||||
| @@ -45,7 +47,11 @@ 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: | ||||
| @@ -83,7 +89,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,16 +3,25 @@ import os | ||||
| import platform | ||||
| import signal | ||||
| import sys | ||||
|  | ||||
| from contextlib import contextmanager | ||||
| from enum import Enum | ||||
| from typing import Awaitable, Literal, Union | ||||
| from typing import Awaitable, Union | ||||
|  | ||||
| from multidict import CIMultiDict  # type: ignore | ||||
|  | ||||
| from sanic.helpers import Default | ||||
| from sanic.log import error_logger | ||||
|  | ||||
| StartMethod = Union[Default, Literal["fork"], Literal["forkserver"], Literal["spawn"]] | ||||
|  | ||||
| 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"] | ||||
|     ] | ||||
|  | ||||
| OS_IS_WINDOWS = os.name == "nt" | ||||
| PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy" | ||||
| @@ -133,10 +142,7 @@ if use_trio:  # pragma: no cover | ||||
|         return trio.Path(path).stat() | ||||
|  | ||||
|     open_async = trio.open_file | ||||
|     CancelledErrors: tuple[type[BaseException], ...] = ( | ||||
|         asyncio.CancelledError, | ||||
|         trio.Cancelled, | ||||
|     ) | ||||
|     CancelledErrors = tuple([asyncio.CancelledError, trio.Cancelled]) | ||||
| else: | ||||
|     if PYPY_IMPLEMENTATION: | ||||
|         pypy_os_module_patch() | ||||
| @@ -150,7 +156,7 @@ else: | ||||
|     async def open_async(file, mode="r", **kwargs): | ||||
|         return aio_open(file, mode, **kwargs) | ||||
|  | ||||
|     CancelledErrors = (asyncio.CancelledError,) | ||||
|     CancelledErrors = tuple([asyncio.CancelledError]) | ||||
|  | ||||
|  | ||||
| def ctrlc_workaround_for_windows(app): | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| 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, Literal, Sequence, Union | ||||
| from typing import Any, Callable, Dict, Optional, Sequence, Union | ||||
| from warnings import filterwarnings | ||||
|  | ||||
| from sanic.constants import LocalCertCreator | ||||
| @@ -14,14 +16,20 @@ from sanic.http import Http | ||||
| from sanic.log import error_logger | ||||
| from sanic.utils import load_module_from_file_location, str_to_bool | ||||
|  | ||||
| FilterWarningType = Union[ | ||||
|     Literal["default"], | ||||
|     Literal["error"], | ||||
|     Literal["ignore"], | ||||
|     Literal["always"], | ||||
|     Literal["module"], | ||||
|     Literal["once"], | ||||
| ] | ||||
|  | ||||
| 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 | ||||
|  | ||||
| SANIC_PREFIX = "SANIC_" | ||||
|  | ||||
| @@ -92,25 +100,25 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|     EVENT_AUTOREGISTER: bool | ||||
|     DEPRECATION_FILTER: FilterWarningType | ||||
|     FORWARDED_FOR_HEADER: str | ||||
|     FORWARDED_SECRET: str | None | ||||
|     FORWARDED_SECRET: Optional[str] | ||||
|     GRACEFUL_SHUTDOWN_TIMEOUT: float | ||||
|     INSPECTOR: bool | ||||
|     INSPECTOR_HOST: str | ||||
|     INSPECTOR_PORT: int | ||||
|     INSPECTOR_TLS_KEY: Path | str | Default | ||||
|     INSPECTOR_TLS_CERT: Path | str | Default | ||||
|     INSPECTOR_TLS_KEY: Union[Path, str, Default] | ||||
|     INSPECTOR_TLS_CERT: Union[Path, str, Default] | ||||
|     INSPECTOR_API_KEY: str | ||||
|     KEEP_ALIVE_TIMEOUT: int | ||||
|     KEEP_ALIVE: bool | ||||
|     LOCAL_CERT_CREATOR: str | LocalCertCreator | ||||
|     LOCAL_TLS_KEY: Path | str | Default | ||||
|     LOCAL_TLS_CERT: Path | str | Default | ||||
|     LOCAL_CERT_CREATOR: Union[str, LocalCertCreator] | ||||
|     LOCAL_TLS_KEY: Union[Path, str, Default] | ||||
|     LOCAL_TLS_CERT: Union[Path, str, Default] | ||||
|     LOCALHOST: str | ||||
|     MOTD: bool | ||||
|     MOTD_DISPLAY: dict[str, str] | ||||
|     MOTD_DISPLAY: Dict[str, str] | ||||
|     NOISY_EXCEPTIONS: bool | ||||
|     PROXIES_COUNT: int | None | ||||
|     REAL_IP_HEADER: str | None | ||||
|     PROXIES_COUNT: Optional[int] | ||||
|     REAL_IP_HEADER: Optional[str] | ||||
|     REQUEST_BUFFER_SIZE: int | ||||
|     REQUEST_MAX_HEADER_SIZE: int | ||||
|     REQUEST_ID_HEADER: str | ||||
| @@ -119,19 +127,21 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|     RESPONSE_TIMEOUT: int | ||||
|     SERVER_NAME: str | ||||
|     TLS_CERT_PASSWORD: str | ||||
|     TOUCHUP: Default | bool | ||||
|     USE_UVLOOP: Default | bool | ||||
|     TOUCHUP: Union[Default, bool] | ||||
|     USE_UVLOOP: Union[Default, bool] | ||||
|     WEBSOCKET_MAX_SIZE: int | ||||
|     WEBSOCKET_PING_INTERVAL: int | ||||
|     WEBSOCKET_PING_TIMEOUT: int | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         defaults: dict[str, str | bool | int | float | None] | None = None, | ||||
|         env_prefix: str | None = SANIC_PREFIX, | ||||
|         keep_alive: bool | None = None, | ||||
|         defaults: Optional[ | ||||
|             Dict[str, Union[str, bool, int, float, None]] | ||||
|         ] = None, | ||||
|         env_prefix: Optional[str] = SANIC_PREFIX, | ||||
|         keep_alive: Optional[bool] = None, | ||||
|         *, | ||||
|         converters: Sequence[Callable[[str], Any]] | None = None, | ||||
|         converters: Optional[Sequence[Callable[[str], Any]]] = None, | ||||
|     ): | ||||
|         defaults = defaults or {} | ||||
|         super().__init__({**DEFAULT_CONFIG, **defaults}) | ||||
| @@ -199,7 +209,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__ | ||||
| @@ -227,7 +237,9 @@ 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() | ||||
|  | ||||
| @@ -264,7 +276,7 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|             module=r"sanic.*", | ||||
|         ) | ||||
|  | ||||
|     def _check_error_format(self, format: str | None = None): | ||||
|     def _check_error_format(self, format: Optional[str] = None): | ||||
|         check_error_format(format or self.FALLBACK_ERROR_FORMAT) | ||||
|  | ||||
|     def load_environment_vars(self, prefix=SANIC_PREFIX): | ||||
| @@ -320,7 +332,7 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|                 except ValueError: | ||||
|                     pass | ||||
|  | ||||
|     def update_config(self, config: bytes | str | dict | Any): | ||||
|     def update_config(self, config: Union[bytes, str, dict, Any]): | ||||
|         """Update app.config. | ||||
|  | ||||
|         .. note:: | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| from .response import Cookie, CookieJar | ||||
|  | ||||
|  | ||||
| __all__ = ("Cookie", "CookieJar") | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| 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]' | ||||
| ) | ||||
| @@ -147,7 +149,9 @@ 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,25 +2,31 @@ from __future__ import annotations | ||||
|  | ||||
| import re | ||||
| import string | ||||
| import sys | ||||
|  | ||||
| from datetime import datetime | ||||
| from typing import TYPE_CHECKING, Any, Union | ||||
| from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union | ||||
|  | ||||
| from sanic.exceptions import ServerError | ||||
| from sanic.log import deprecation | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic.compat import Header | ||||
|  | ||||
| from typing import Literal | ||||
| if sys.version_info < (3, 8):  # no cov | ||||
|     SameSite = str | ||||
| else:  # no cov | ||||
|     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") | ||||
| @@ -174,7 +180,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 " | ||||
| @@ -185,7 +191,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: | ||||
| @@ -197,10 +203,10 @@ class CookieJar(dict): | ||||
|         self, | ||||
|         key: str, | ||||
|         path: str = "/", | ||||
|         domain: str | None = None, | ||||
|         domain: Optional[str] = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> Cookie | None: | ||||
|     ) -> Optional[Cookie]: | ||||
|         """Fetch a cookie from the CookieJar. | ||||
|  | ||||
|         Args: | ||||
| @@ -230,7 +236,7 @@ class CookieJar(dict): | ||||
|         self, | ||||
|         key: str, | ||||
|         path: str = "/", | ||||
|         domain: str | None = None, | ||||
|         domain: Optional[str] = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> bool: | ||||
| @@ -265,14 +271,14 @@ class CookieJar(dict): | ||||
|         value: str, | ||||
|         *, | ||||
|         path: str = "/", | ||||
|         domain: str | None = None, | ||||
|         domain: Optional[str] = None, | ||||
|         secure: bool = True, | ||||
|         max_age: int | None = None, | ||||
|         expires: datetime | None = None, | ||||
|         max_age: Optional[int] = None, | ||||
|         expires: Optional[datetime] = None, | ||||
|         httponly: bool = False, | ||||
|         samesite: SameSite | None = "Lax", | ||||
|         samesite: Optional[SameSite] = "Lax", | ||||
|         partitioned: bool = False, | ||||
|         comment: str | None = None, | ||||
|         comment: Optional[str] = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> Cookie: | ||||
| @@ -356,7 +362,7 @@ class CookieJar(dict): | ||||
|         key: str, | ||||
|         *, | ||||
|         path: str = "/", | ||||
|         domain: str | None = None, | ||||
|         domain: Optional[str] = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ) -> None: | ||||
| @@ -384,7 +390,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) | ||||
| @@ -475,14 +481,14 @@ class Cookie(dict): | ||||
|         value: str, | ||||
|         *, | ||||
|         path: str = "/", | ||||
|         domain: str | None = None, | ||||
|         domain: Optional[str] = None, | ||||
|         secure: bool = True, | ||||
|         max_age: int | None = None, | ||||
|         expires: datetime | None = None, | ||||
|         max_age: Optional[int] = None, | ||||
|         expires: Optional[datetime] = None, | ||||
|         httponly: bool = False, | ||||
|         samesite: SameSite | None = "Lax", | ||||
|         samesite: Optional[SameSite] = "Lax", | ||||
|         partitioned: bool = False, | ||||
|         comment: str | None = None, | ||||
|         comment: Optional[str] = None, | ||||
|         host_prefix: bool = False, | ||||
|         secure_prefix: bool = False, | ||||
|     ): | ||||
| @@ -496,7 +502,9 @@ 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" | ||||
| @@ -553,7 +561,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(f"Unknown cookie property: {key}={value}") | ||||
|             raise KeyError("Unknown cookie property: %s=%s" % (key, value)) | ||||
|  | ||||
|         if value is not None: | ||||
|             if key.lower() == "max-age" and not str(value).isdigit(): | ||||
| @@ -596,18 +604,21 @@ class Cookie(dict): | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Format as a Set-Cookie header value.""" | ||||
|         output = [f"{self.key}={_quote(self.value)}"] | ||||
|         output = ["%s=%s" % (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(f"{self._keys[key]}={value}") | ||||
|                         output.append("%s=%s" % (self._keys[key], value)) | ||||
|                 elif key == "expires": | ||||
|                     output.append( | ||||
|                         "{}={}".format( | ||||
|                         "%s=%s" | ||||
|                         % ( | ||||
|                             self._keys[key], | ||||
|                             value.strftime("%a, %d-%b-%Y %T GMT"), | ||||
|                         ) | ||||
| @@ -615,7 +626,7 @@ class Cookie(dict): | ||||
|                 elif key in self._flags: | ||||
|                     output.append(self._keys[key]) | ||||
|                 else: | ||||
|                     output.append(f"{self._keys[key]}={value}") | ||||
|                     output.append("%s=%s" % (self._keys[key], value)) | ||||
|  | ||||
|         return "; ".join(output) | ||||
|  | ||||
| @@ -629,7 +640,7 @@ class Cookie(dict): | ||||
|         self._set_value("path", value) | ||||
|  | ||||
|     @property | ||||
|     def expires(self) -> datetime | None:  # no cov | ||||
|     def expires(self) -> Optional[datetime]:  # no cov | ||||
|         """The expiration date of the cookie. Defaults to `None`.""" | ||||
|         return self.get("expires") | ||||
|  | ||||
| @@ -638,7 +649,7 @@ class Cookie(dict): | ||||
|         self._set_value("expires", value) | ||||
|  | ||||
|     @property | ||||
|     def comment(self) -> str | None:  # no cov | ||||
|     def comment(self) -> Optional[str]:  # no cov | ||||
|         """A comment for the cookie. Defaults to `None`.""" | ||||
|         return self.get("comment") | ||||
|  | ||||
| @@ -647,7 +658,7 @@ class Cookie(dict): | ||||
|         self._set_value("comment", value) | ||||
|  | ||||
|     @property | ||||
|     def domain(self) -> str | None:  # no cov | ||||
|     def domain(self) -> Optional[str]:  # no cov | ||||
|         """The domain of the cookie. Defaults to `None`.""" | ||||
|         return self.get("domain") | ||||
|  | ||||
| @@ -656,7 +667,7 @@ class Cookie(dict): | ||||
|         self._set_value("domain", value) | ||||
|  | ||||
|     @property | ||||
|     def max_age(self) -> int | None:  # no cov | ||||
|     def max_age(self) -> Optional[int]:  # no cov | ||||
|         """The maximum age of the cookie in seconds. Defaults to `None`.""" | ||||
|         return self.get("max-age") | ||||
|  | ||||
| @@ -683,7 +694,7 @@ class Cookie(dict): | ||||
|         self._set_value("httponly", value) | ||||
|  | ||||
|     @property | ||||
|     def samesite(self) -> SameSite | None:  # no cov | ||||
|     def samesite(self) -> Optional[SameSite]:  # no cov | ||||
|         """The SameSite attribute for the cookie. Defaults to `"Lax"`.""" | ||||
|         return self.get("samesite") | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ from __future__ import annotations | ||||
|  | ||||
| import sys | ||||
| import typing as t | ||||
|  | ||||
| from functools import partial | ||||
| from traceback import extract_tb | ||||
|  | ||||
| @@ -25,6 +26,7 @@ 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 | ||||
| @@ -71,7 +73,7 @@ class BaseRenderer: | ||||
|         self.debug = debug | ||||
|  | ||||
|     @property | ||||
|     def headers(self) -> dict[str, str]: | ||||
|     def headers(self) -> t.Dict[str, str]: | ||||
|         """The headers to be used for the response.""" | ||||
|         if isinstance(self.exception, SanicException): | ||||
|             return getattr(self.exception, "headers", {}) | ||||
| @@ -190,7 +192,8 @@ 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: | ||||
| @@ -323,8 +326,8 @@ def exception_response( | ||||
|     exception: Exception, | ||||
|     debug: bool, | ||||
|     fallback: str, | ||||
|     base: type[BaseRenderer], | ||||
|     renderer: type[BaseRenderer] | None = None, | ||||
|     base: t.Type[BaseRenderer], | ||||
|     renderer: t.Optional[t.Type[BaseRenderer]] = None, | ||||
| ) -> HTTPResponse: | ||||
|     """Render a response for the default FALLBACK exception handler.""" | ||||
|     if not renderer: | ||||
| @@ -387,7 +390,9 @@ 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,7 +69,9 @@ 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: | ||||
| @@ -619,7 +621,9 @@ class Unauthorized(HTTPException): | ||||
|  | ||||
|         # if auth-scheme is specified, set "WWW-Authenticate" header | ||||
|         if scheme is not None: | ||||
|             values = [f'{k!s}="{v!s}"' for k, v in challenges.items()] | ||||
|             values = [ | ||||
|                 '{!s}="{!s}"'.format(k, v) for k, v in challenges.items() | ||||
|             ] | ||||
|             challenge = ", ".join(values) | ||||
|  | ||||
|             self.headers = { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ from .content_range import ContentRangeHandler | ||||
| from .directory import DirectoryHandler | ||||
| from .error import ErrorHandler | ||||
|  | ||||
|  | ||||
| __all__ = ( | ||||
|     "ContentRangeHandler", | ||||
|     "DirectoryHandler", | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import os | ||||
|  | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from sanic.exceptions import ( | ||||
| @@ -10,6 +11,7 @@ from sanic.exceptions import ( | ||||
| ) | ||||
| from sanic.models.protocol_types import Range | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Request | ||||
|  | ||||
| @@ -31,19 +33,27 @@ class ContentRangeHandler(Range): | ||||
|             raise HeaderNotFound("Range Header Not Found") | ||||
|         unit, _, value = tuple(map(str.strip, _range.partition("="))) | ||||
|         if unit != "bytes": | ||||
|             raise InvalidRangeType(f"{unit} is not a valid Range Type", self) | ||||
|             raise InvalidRangeType( | ||||
|                 "%s is not a valid Range Type" % (unit,), 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(f"'{start_b}' is invalid for Content Range", self) | ||||
|             raise RangeNotSatisfiable( | ||||
|                 "'%s' is invalid for Content Range" % (start_b,), self | ||||
|             ) | ||||
|         try: | ||||
|             self.end = int(end_b) if end_b else None | ||||
|         except ValueError: | ||||
|             raise RangeNotSatisfiable(f"'{end_b}' is invalid for Content Range", self) | ||||
|             raise RangeNotSatisfiable( | ||||
|                 "'%s' is invalid for Content Range" % (end_b,), 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 | ||||
| @@ -53,9 +63,14 @@ 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": f"bytes {self.start}-{self.end}/{self.total}"} | ||||
|         self.headers = { | ||||
|             "Content-Range": "bytes %s-%s/%s" | ||||
|             % (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 Iterable, Sequence, cast | ||||
| from typing import Dict, Iterable, Optional, Sequence, Union, 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: str | Sequence[str] | None = None, | ||||
|         index: Optional[Union[str, Sequence[str]]] = None, | ||||
|     ) -> None: | ||||
|         if isinstance(index, str): | ||||
|             index = [index] | ||||
| @@ -60,7 +60,9 @@ 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") | ||||
| @@ -70,16 +72,20 @@ 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, int | str]: | ||||
|     def _prepare_file(self, path: Path) -> Dict[str, Union[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,5 +1,7 @@ | ||||
| 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 | ||||
| @@ -23,20 +25,20 @@ class ErrorHandler: | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         base: type[BaseRenderer] = TextRenderer, | ||||
|         base: Type[BaseRenderer] = TextRenderer, | ||||
|     ): | ||||
|         self.cached_handlers: dict[ | ||||
|             tuple[type[BaseException], str | None], RouteHandler | None | ||||
|         self.cached_handlers: Dict[ | ||||
|             Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler] | ||||
|         ] = {} | ||||
|         self.debug = False | ||||
|         self.base = base | ||||
|  | ||||
|     def _full_lookup(self, exception, route_name: str | None = None): | ||||
|     def _full_lookup(self, exception, route_name: Optional[str] = None): | ||||
|         return self.lookup(exception, route_name) | ||||
|  | ||||
|     def _add( | ||||
|         self, | ||||
|         key: tuple[type[BaseException], str | None], | ||||
|         key: Tuple[Type[BaseException], Optional[str]], | ||||
|         handler: RouteHandler, | ||||
|     ) -> None: | ||||
|         if key in self.cached_handlers: | ||||
| @@ -51,7 +53,7 @@ class ErrorHandler: | ||||
|             raise ServerError(message) | ||||
|         self.cached_handlers[key] = handler | ||||
|  | ||||
|     def add(self, exception, handler, route_names: list[str] | None = None): | ||||
|     def add(self, exception, handler, route_names: Optional[List[str]] = None): | ||||
|         """Add a new exception handler to an already existing handler object. | ||||
|  | ||||
|         Args: | ||||
| @@ -70,7 +72,7 @@ class ErrorHandler: | ||||
|         else: | ||||
|             self._add((exception, None), handler) | ||||
|  | ||||
|     def lookup(self, exception, route_name: str | None = None): | ||||
|     def lookup(self, exception, route_name: Optional[str] = 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. | ||||
| @@ -96,7 +98,9 @@ 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: | ||||
| @@ -131,11 +135,13 @@ class ErrorHandler: | ||||
|                 url = repr(request.url) | ||||
|             except AttributeError:  # no cov | ||||
|                 url = "unknown" | ||||
|             response_message = f'Exception raised in exception handler "{handler.__name__}" for uri: {url}' | ||||
|             error_logger.exception(response_message) | ||||
|             response_message = ( | ||||
|                 "Exception raised in exception handler " '"%s" for uri: %s' | ||||
|             ) | ||||
|             error_logger.exception(response_message, handler.__name__, url) | ||||
|  | ||||
|             if self.debug: | ||||
|                 return text(response_message, 500) | ||||
|                 return text(response_message % (handler.__name__, url), 500) | ||||
|             else: | ||||
|                 return text("An error occurred while handling an error", 500) | ||||
|         return response | ||||
| @@ -194,4 +200,6 @@ 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,12 +1,14 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import re | ||||
| from typing import Any, Dict, Iterable, Tuple, Union | ||||
|  | ||||
| from typing import Any, Dict, Iterable, List, Optional, Set, 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) | ||||
| @@ -19,7 +21,9 @@ _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, | ||||
| @@ -81,8 +85,8 @@ class MediaType: | ||||
|  | ||||
|     def match( | ||||
|         self, | ||||
|         mime_with_params: str | MediaType, | ||||
|     ) -> MediaType | None: | ||||
|         mime_with_params: Union[str, MediaType], | ||||
|     ) -> Optional[MediaType]: | ||||
|         """Match this media type against another media type. | ||||
|  | ||||
|         Check if this media type matches the given mime type/subtype. | ||||
| @@ -120,7 +124,9 @@ 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 | ||||
|         ) | ||||
| @@ -135,7 +141,7 @@ class MediaType: | ||||
|         return any(part == "*" for part in (self.subtype, self.type)) | ||||
|  | ||||
|     @classmethod | ||||
|     def _parse(cls, mime_with_params: str) -> MediaType | None: | ||||
|     def _parse(cls, mime_with_params: str) -> Optional[MediaType]: | ||||
|         mtype = mime_with_params.strip() | ||||
|         if "/" not in mime_with_params: | ||||
|             return None | ||||
| @@ -145,10 +151,12 @@ class MediaType: | ||||
|         if not type_ or not subtype: | ||||
|             raise ValueError(f"Invalid media type: {mtype}") | ||||
|  | ||||
|         params = { | ||||
|             key.strip(): value.strip() | ||||
|             for key, value in (param.split("=", 1) for param in raw_params) | ||||
|         } | ||||
|         params = dict( | ||||
|             [ | ||||
|                 (key.strip(), value.strip()) | ||||
|                 for key, value in (param.split("=", 1) for param in raw_params) | ||||
|             ] | ||||
|         ) | ||||
|  | ||||
|         return cls(type_.lstrip(), subtype.rstrip(), **params) | ||||
|  | ||||
| @@ -165,7 +173,7 @@ class Matched: | ||||
|         header (MediaType): The header to match against, if any. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, mime: str, header: MediaType | None): | ||||
|     def __init__(self, mime: str, header: Optional[MediaType]): | ||||
|         self.mime = mime | ||||
|         self.header = header | ||||
|  | ||||
| @@ -192,7 +200,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: | ||||
| @@ -207,7 +215,7 @@ class Matched: | ||||
|             f"mime types of '{self.mime}' and '{other}'" | ||||
|         ) | ||||
|  | ||||
|     def match(self, other: str | Matched) -> Matched | None: | ||||
|     def match(self, other: Union[str, Matched]) -> Optional[Matched]: | ||||
|         """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. | ||||
| @@ -288,7 +296,7 @@ class AcceptList(list): | ||||
|         return ", ".join(str(m) for m in self) | ||||
|  | ||||
|  | ||||
| def parse_accept(accept: str | None) -> AcceptList: | ||||
| def parse_accept(accept: Optional[str]) -> 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 | ||||
| @@ -308,7 +316,9 @@ def parse_accept(accept: str | None) -> 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 | ||||
| @@ -317,7 +327,7 @@ def parse_accept(accept: str | None) -> 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 | ||||
| @@ -336,10 +346,11 @@ def parse_content_header(value: str) -> tuple[str, Options]: | ||||
|     """ | ||||
|     pos = value.find(";") | ||||
|     if pos == -1: | ||||
|         options: dict[str, int | str] = {} | ||||
|         options: Dict[str, Union[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:]) | ||||
| @@ -356,7 +367,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) -> Options | None: | ||||
| def parse_forwarded(headers, config) -> Optional[Options]: | ||||
|     """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 | ||||
| @@ -370,7 +381,7 @@ def parse_forwarded(headers, config) -> Options | None: | ||||
|         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) | ||||
| @@ -394,7 +405,7 @@ def parse_forwarded(headers, config) -> Options | None: | ||||
|     return fwd_normalize(reversed(options)) if found else None | ||||
|  | ||||
|  | ||||
| def parse_xforwarded(headers, config) -> Options | None: | ||||
| def parse_xforwarded(headers, config) -> Optional[Options]: | ||||
|     """Parse traditional proxy headers.""" | ||||
|     real_ip_header = config.REAL_IP_HEADER | ||||
|     proxies_count = config.PROXIES_COUNT | ||||
| @@ -405,7 +416,11 @@ def parse_xforwarded(headers, config) -> Options | None: | ||||
|             # 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): | ||||
| @@ -437,7 +452,7 @@ def fwd_normalize(fwd: OptionsIterable) -> Options: | ||||
|     Returns: | ||||
|         Options: A dict of normalized key-value pairs. | ||||
|     """ | ||||
|     ret: dict[str, int | str] = {} | ||||
|     ret: Dict[str, Union[int, str]] = {} | ||||
|     for key, val in fwd: | ||||
|         if val is not None: | ||||
|             try: | ||||
| @@ -474,7 +489,7 @@ def fwd_normalize_address(addr: str) -> str: | ||||
|     return addr.lower() | ||||
|  | ||||
|  | ||||
| def parse_host(host: str) -> tuple[str | None, int | None]: | ||||
| def parse_host(host: str) -> Tuple[Optional[str], Optional[int]]: | ||||
|     """Split host:port into hostname and port. | ||||
|  | ||||
|     Args: | ||||
| @@ -516,9 +531,9 @@ def format_http1_response(status: int, headers: HeaderBytesIterable) -> bytes: | ||||
|  | ||||
|  | ||||
| def parse_credentials( | ||||
|     header: str | None, | ||||
|     prefixes: list | tuple | set | None = None, | ||||
| ) -> tuple[str | None, str | None]: | ||||
|     header: Optional[str], | ||||
|     prefixes: Optional[Union[List, Tuple, Set]] = None, | ||||
| ) -> Tuple[Optional[str], Optional[str]]: | ||||
|     """Parses any header with the aim to retrieve any credentials from it. | ||||
|  | ||||
|     Args: | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| """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", | ||||
| @@ -130,7 +132,7 @@ def remove_entity_headers(headers, allowed=("content-location", "expires")): | ||||
|  | ||||
|     returns the headers without the entity headers | ||||
|     """ | ||||
|     allowed = {h.lower() for h in allowed} | ||||
|     allowed = set([h.lower() for h in allowed]) | ||||
|     headers = { | ||||
|         header: value | ||||
|         for header, value in headers.items() | ||||
|   | ||||
| @@ -2,4 +2,5 @@ from .constants import Stage | ||||
| from .http1 import Http | ||||
| from .http3 import Http3 | ||||
|  | ||||
|  | ||||
| __all__ = ("Http", "Stage", "Http3") | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from typing import TYPE_CHECKING | ||||
| from typing import TYPE_CHECKING, Optional | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic.request import Request | ||||
| @@ -24,6 +25,7 @@ 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" | ||||
|  | ||||
|  | ||||
| @@ -361,20 +363,26 @@ 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: | ||||
| @@ -412,7 +420,9 @@ 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) | ||||
|  | ||||
| @@ -471,7 +481,7 @@ class Http(Stream, metaclass=TouchUpMeta): | ||||
|             if data: | ||||
|                 yield data | ||||
|  | ||||
|     async def read(self) -> bytes | None:  # no cov | ||||
|     async def read(self) -> Optional[bytes]:  # no cov | ||||
|         """Read some bytes of request body.""" | ||||
|  | ||||
|         # Send a 100-continue if needed | ||||
|   | ||||
| @@ -1,12 +1,17 @@ | ||||
| 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, | ||||
| ) | ||||
| @@ -27,6 +32,7 @@ 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 | ||||
| @@ -65,7 +71,10 @@ 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 | ||||
| @@ -100,18 +109,19 @@ class HTTPReceiver(Receiver, Stream): | ||||
|         self.request_body = None | ||||
|         self.stage = Stage.IDLE | ||||
|         self.headers_sent = False | ||||
|         self.response: BaseHTTPResponse | None = None | ||||
|         self.response: Optional[BaseHTTPResponse] = None | ||||
|         self.request_max_size = self.protocol.request_max_size | ||||
|         self.request_bytes = 0 | ||||
|  | ||||
|     async def run(self, exception: Exception | None = None): | ||||
|     async def run(self, exception: Optional[Exception] = 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}, | ||||
|             ) | ||||
| @@ -136,13 +146,17 @@ 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) | ||||
| @@ -235,7 +249,11 @@ 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) | ||||
|  | ||||
| @@ -286,7 +304,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 | ||||
| @@ -312,8 +330,11 @@ 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 | ||||
| @@ -336,7 +357,9 @@ 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", "") | ||||
| @@ -373,16 +396,18 @@ 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) -> SessionTicket | None: | ||||
|     def pop(self, label: bytes) -> Optional[SessionTicket]: | ||||
|         return self.tickets.pop(label, None) | ||||
|  | ||||
|  | ||||
| def get_config(app: Sanic, ssl: SanicSSLContext | CertSelector | SSLContext): | ||||
| def get_config( | ||||
|     app: Sanic, ssl: Union[SanicSSLContext, CertSelector, SSLContext] | ||||
| ): | ||||
|     # TODO: | ||||
|     # - proper selection needed if service with multiple certs insted of | ||||
|     #   just taking the first | ||||
| @@ -405,6 +430,8 @@ def get_config(app: Sanic, ssl: SanicSSLContext | CertSelector | SSLContext): | ||||
|     ) | ||||
|     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,9 +1,10 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from typing import TYPE_CHECKING | ||||
| from typing import TYPE_CHECKING, Optional, Tuple, Union | ||||
|  | ||||
| from sanic.http.constants import Stage | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic.response import BaseHTTPResponse | ||||
|     from sanic.server.protocols.http_protocol import HttpProtocol | ||||
| @@ -11,14 +12,16 @@ if TYPE_CHECKING: | ||||
|  | ||||
| class Stream: | ||||
|     stage: Stage | ||||
|     response: BaseHTTPResponse | None | ||||
|     response: Optional[BaseHTTPResponse] | ||||
|     protocol: HttpProtocol | ||||
|     url: str | None | ||||
|     request_body: bytes | None | ||||
|     request_max_size: int | float | ||||
|     url: Optional[str] | ||||
|     request_body: Optional[bytes] | ||||
|     request_max_size: Union[int, float] | ||||
|  | ||||
|     __touchup__: tuple[str, ...] = () | ||||
|     __touchup__: Tuple[str, ...] = tuple() | ||||
|     __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,4 +1,5 @@ | ||||
| from .context import process_to_context | ||||
| from .creators import get_ssl_context | ||||
|  | ||||
|  | ||||
| __all__ = ("get_ssl_context", "process_to_context") | ||||
|   | ||||
| @@ -2,10 +2,12 @@ from __future__ import annotations | ||||
|  | ||||
| import os | ||||
| import ssl | ||||
| from typing import Any, Iterable | ||||
|  | ||||
| from typing import Any, Dict, Iterable, Optional, Union | ||||
|  | ||||
| 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 = [ | ||||
| @@ -17,14 +19,11 @@ 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: str | None = None, | ||||
|     keyfile: str | None = None, | ||||
|     password: str | None = None, | ||||
|     certfile: Optional[str] = None, | ||||
|     keyfile: Optional[str] = None, | ||||
|     password: Optional[str] = None, | ||||
|     purpose: ssl.Purpose = ssl.Purpose.CLIENT_AUTH, | ||||
| ) -> ssl.SSLContext: | ||||
|     """Create a context with secure crypto and HTTP/1.1 in protocols.""" | ||||
| @@ -39,7 +38,9 @@ def create_context( | ||||
|     return context | ||||
|  | ||||
|  | ||||
| def shorthand_to_ctx(ctxdef: TlsDef) -> ssl.SSLContext | None: | ||||
| def shorthand_to_ctx( | ||||
|     ctxdef: Union[None, ssl.SSLContext, dict, str] | ||||
| ) -> Optional[ssl.SSLContext]: | ||||
|     """Convert an ssl argument shorthand to an SSLContext object.""" | ||||
|     if ctxdef is None or isinstance(ctxdef, ssl.SSLContext): | ||||
|         return ctxdef | ||||
| @@ -53,7 +54,9 @@ def shorthand_to_ctx(ctxdef: TlsDef) -> ssl.SSLContext | None: | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def process_to_context(ssldef: TlsDefs) -> ssl.SSLContext | None: | ||||
| def process_to_context( | ||||
|     ssldef: Union[None, ssl.SSLContext, dict, str, list, tuple] | ||||
| ) -> Optional[ssl.SSLContext]: | ||||
|     """Process app.run ssl argument from easy formats to full SSLContext.""" | ||||
|     return ( | ||||
|         CertSelector(map(shorthand_to_ctx, ssldef)) | ||||
| @@ -68,9 +71,13 @@ 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) | ||||
|  | ||||
|  | ||||
| @@ -82,7 +89,9 @@ 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 | ||||
| @@ -91,7 +100,9 @@ def find_cert(self: CertSelector, server_name: str): | ||||
|     raise ValueError(f"No certificate found matching hostname {server_name!r}") | ||||
|  | ||||
|  | ||||
| def match_hostname(ctx: ssl.SSLContext | CertSelector, hostname: str) -> bool: | ||||
| def match_hostname( | ||||
|     ctx: Union[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. | ||||
| @@ -108,7 +119,7 @@ def match_hostname(ctx: ssl.SSLContext | CertSelector, hostname: str) -> bool: | ||||
|  | ||||
| def selector_sni_callback( | ||||
|     sslobj: ssl.SSLObject, server_name: str, ctx: CertSelector | ||||
| ) -> int | None: | ||||
| ) -> Optional[int]: | ||||
|     """Select a certificate matching the SNI.""" | ||||
|     # Call server_name_callback to store the SNI on sslobj | ||||
|     server_name_callback(sslobj, server_name, ctx) | ||||
| @@ -131,7 +142,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): | ||||
| @@ -142,7 +153,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 | ||||
| @@ -155,7 +166,9 @@ 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) | ||||
| @@ -177,7 +190,7 @@ class CertSelector(ssl.SSLContext): | ||||
|     def __new__(cls, ctxs): | ||||
|         return super().__new__(cls) | ||||
|  | ||||
|     def __init__(self, ctxs: Iterable[ssl.SSLContext | None]): | ||||
|     def __init__(self, ctxs: Iterable[Optional[ssl.SSLContext]]): | ||||
|         super().__init__() | ||||
|         self.sni_callback = selector_sni_callback  # type: ignore | ||||
|         self.sanic_select = [] | ||||
| @@ -192,5 +205,7 @@ 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,12 +3,13 @@ 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, cast | ||||
| from typing import TYPE_CHECKING, Optional, Tuple, Type, Union, cast | ||||
|  | ||||
| from sanic.application.constants import Mode | ||||
| from sanic.application.spinner import loading | ||||
| @@ -21,6 +22,7 @@ from sanic.exceptions import SanicException | ||||
| from sanic.helpers import Default | ||||
| from sanic.http.tls.context import CertSimple, SanicSSLContext | ||||
|  | ||||
|  | ||||
| try: | ||||
|     import trustme | ||||
|  | ||||
| @@ -45,7 +47,7 @@ CIPHERS_TLS12 = [ | ||||
| ] | ||||
|  | ||||
|  | ||||
| def _make_path(maybe_path: Path | str, tmpdir: Path | None) -> Path: | ||||
| def _make_path(maybe_path: Union[Path, str], tmpdir: Optional[Path]) -> Path: | ||||
|     if isinstance(maybe_path, Path): | ||||
|         return maybe_path | ||||
|     else: | ||||
| @@ -58,7 +60,9 @@ def _make_path(maybe_path: Path | str, tmpdir: Path | None) -> Path: | ||||
|     return path | ||||
|  | ||||
|  | ||||
| def get_ssl_context(app: Sanic, ssl: ssl.SSLContext | None) -> ssl.SSLContext: | ||||
| def get_ssl_context( | ||||
|     app: Sanic, ssl: Optional[ssl.SSLContext] | ||||
| ) -> ssl.SSLContext: | ||||
|     if ssl: | ||||
|         return ssl | ||||
|  | ||||
| @@ -92,8 +96,16 @@ 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) | ||||
| @@ -114,9 +126,11 @@ class CertCreator(ABC): | ||||
|         local_tls_key, | ||||
|         local_tls_cert, | ||||
|     ) -> CertCreator: | ||||
|         creator: CertCreator | None = None | ||||
|         creator: Optional[CertCreator] = None | ||||
|  | ||||
|         cert_creator_options: tuple[tuple[type[CertCreator], LocalCertCreator], ...] = ( | ||||
|         cert_creator_options: Tuple[ | ||||
|             Tuple[Type[CertCreator], LocalCertCreator], ... | ||||
|         ] = ( | ||||
|             (MkcertCreator, LocalCertCreator.MKCERT), | ||||
|             (TrustmeCreator, LocalCertCreator.TRUSTME), | ||||
|         ) | ||||
| @@ -146,8 +160,8 @@ class CertCreator(ABC): | ||||
|     @staticmethod | ||||
|     def _try_select( | ||||
|         app: Sanic, | ||||
|         creator: CertCreator | None, | ||||
|         creator_class: type[CertCreator], | ||||
|         creator: Optional[CertCreator], | ||||
|         creator_class: Type[CertCreator], | ||||
|         creator_requirement: LocalCertCreator, | ||||
|         creator_requested: LocalCertCreator, | ||||
|         local_tls_key, | ||||
|   | ||||
							
								
								
									
										18
									
								
								sanic/log.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								sanic/log.py
									
									
									
									
									
								
							| @@ -1,11 +1,13 @@ | ||||
| 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): | ||||
|  | ||||
| @@ -17,10 +19,10 @@ else: | ||||
|         from enum import StrEnum | ||||
|  | ||||
|  | ||||
| LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = {  # no cov | ||||
|     "version": 1, | ||||
|     "disable_existing_loggers": False, | ||||
|     "loggers": { | ||||
| LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(  # no cov | ||||
|     version=1, | ||||
|     disable_existing_loggers=False, | ||||
|     loggers={ | ||||
|         "sanic.root": {"level": "INFO", "handlers": ["console"]}, | ||||
|         "sanic.error": { | ||||
|             "level": "INFO", | ||||
| @@ -41,7 +43,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = {  # no cov | ||||
|             "qualname": "sanic.server", | ||||
|         }, | ||||
|     }, | ||||
|     "handlers": { | ||||
|     handlers={ | ||||
|         "console": { | ||||
|             "class": "logging.StreamHandler", | ||||
|             "formatter": "generic", | ||||
| @@ -58,7 +60,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = {  # 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]", | ||||
| @@ -66,12 +68,12 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = {  # 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 Sequence | ||||
| from typing import Deque, Sequence, Union | ||||
|  | ||||
| from sanic.models.handler_types import MiddlewareType | ||||
|  | ||||
| @@ -69,9 +69,9 @@ class Middleware: | ||||
|     @classmethod | ||||
|     def convert( | ||||
|         cls, | ||||
|         *middleware_collections: Sequence[Middleware | MiddlewareType], | ||||
|         *middleware_collections: Sequence[Union[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,7 +79,9 @@ 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,12 +120,16 @@ 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 | ||||
| @@ -147,7 +151,9 @@ 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 | ||||
| @@ -170,7 +176,9 @@ 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 | ||||
| @@ -214,7 +222,9 @@ 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 | ||||
| @@ -283,7 +293,9 @@ 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 | ||||
| @@ -307,7 +319,9 @@ 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 | ||||
| @@ -335,7 +349,9 @@ 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 | ||||
| @@ -360,7 +376,9 @@ 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,9 +99,13 @@ 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. | ||||
| @@ -153,7 +157,9 @@ 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. | ||||
|   | ||||
| @@ -25,7 +25,10 @@ from sanic.models.futures import FutureRoute, FutureStatic | ||||
| from sanic.models.handler_types import RouteHandler | ||||
| from sanic.types import HashableDict | ||||
|  | ||||
| RouteWrapper = Callable[[RouteHandler], Union[RouteHandler, Tuple[Route, RouteHandler]]] | ||||
|  | ||||
| RouteWrapper = Callable[ | ||||
|     [RouteHandler], Union[RouteHandler, Tuple[Route, RouteHandler]] | ||||
| ] | ||||
|  | ||||
|  | ||||
| class RouteMixin(BaseMixin, metaclass=SanicMeta): | ||||
| @@ -812,5 +815,7 @@ class RouteMixin(BaseMixin, metaclass=SanicMeta): | ||||
|         } | ||||
|         if raw: | ||||
|             unexpected_arguments = ", ".join(raw.keys()) | ||||
|             raise TypeError(f"Unexpected keyword arguments: {unexpected_arguments}") | ||||
|             raise TypeError( | ||||
|                 f"Unexpected keyword arguments: {unexpected_arguments}" | ||||
|             ) | ||||
|         return HashableDict(ctx_kwargs) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| from enum import Enum | ||||
| from typing import Any, Callable, Coroutine | ||||
| from typing import Any, Callable, Coroutine, Dict, Optional, Set, Union | ||||
|  | ||||
| from sanic.base.meta import SanicMeta | ||||
| from sanic.models.futures import FutureSignal | ||||
| @@ -12,17 +12,17 @@ from sanic.types import HashableDict | ||||
|  | ||||
| class SignalMixin(metaclass=SanicMeta): | ||||
|     def __init__(self, *args, **kwargs) -> None: | ||||
|         self._future_signals: set[FutureSignal] = set() | ||||
|         self._future_signals: Set[FutureSignal] = set() | ||||
|  | ||||
|     def _apply_signal(self, signal: FutureSignal) -> Signal: | ||||
|         raise NotImplementedError  # noqa | ||||
|  | ||||
|     def signal( | ||||
|         self, | ||||
|         event: str | Enum, | ||||
|         event: Union[str, Enum], | ||||
|         *, | ||||
|         apply: bool = True, | ||||
|         condition: dict[str, Any] | None = None, | ||||
|         condition: Optional[Dict[str, Any]] = None, | ||||
|         exclusive: bool = True, | ||||
|     ) -> Callable[[SignalHandler], SignalHandler]: | ||||
|         """ | ||||
| @@ -64,9 +64,9 @@ class SignalMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     def add_signal( | ||||
|         self, | ||||
|         handler: Callable[..., Any] | None, | ||||
|         handler: Optional[Callable[..., Any]], | ||||
|         event: str, | ||||
|         condition: dict[str, Any] | None = None, | ||||
|         condition: Optional[Dict[str, Any]] = None, | ||||
|         exclusive: bool = True, | ||||
|     ) -> Callable[..., Any]: | ||||
|         """Registers a signal handler for a specific event. | ||||
| @@ -92,7 +92,9 @@ class SignalMixin(metaclass=SanicMeta): | ||||
|                 ... | ||||
|  | ||||
|             handler = noop | ||||
|         self.signal(event=event, condition=condition, exclusive=exclusive)(handler) | ||||
|         self.signal(event=event, condition=condition, exclusive=exclusive)( | ||||
|             handler | ||||
|         ) | ||||
|         return handler | ||||
|  | ||||
|     def event(self, event: str): | ||||
|   | ||||
| @@ -2,6 +2,8 @@ from __future__ import annotations | ||||
|  | ||||
| import os | ||||
| import platform | ||||
| import sys | ||||
|  | ||||
| from asyncio import ( | ||||
|     AbstractEventLoop, | ||||
|     CancelledError, | ||||
| @@ -30,7 +32,13 @@ from typing import ( | ||||
|     Any, | ||||
|     Callable, | ||||
|     ClassVar, | ||||
|     Dict, | ||||
|     List, | ||||
|     Mapping, | ||||
|     Optional, | ||||
|     Set, | ||||
|     Tuple, | ||||
|     Type, | ||||
|     Union, | ||||
|     cast, | ||||
| ) | ||||
| @@ -63,6 +71,7 @@ from sanic.worker.multiplexer import WorkerMultiplexer | ||||
| from sanic.worker.reloader import Reloader | ||||
| from sanic.worker.serve import worker_serve | ||||
|  | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from sanic import Sanic | ||||
|     from sanic.application.state import ApplicationState | ||||
| @@ -70,17 +79,20 @@ if TYPE_CHECKING: | ||||
|  | ||||
| SANIC_PACKAGES = ("sanic-routing", "sanic-testing", "sanic-ext") | ||||
|  | ||||
| from typing import Literal | ||||
| if sys.version_info < (3, 8):  # no cov | ||||
|     HTTPVersion = Union[HTTP, int] | ||||
| else:  # no cov | ||||
|     from typing import Literal | ||||
|  | ||||
| HTTPVersion = Union[HTTP, Literal[1], Literal[3]] | ||||
|     HTTPVersion = Union[HTTP, Literal[1], Literal[3]] | ||||
|  | ||||
|  | ||||
| class StartupMixin(metaclass=SanicMeta): | ||||
|     _app_registry: ClassVar[dict[str, Sanic]] | ||||
|     _app_registry: ClassVar[Dict[str, Sanic]] | ||||
|  | ||||
|     asgi: bool | ||||
|     config: Config | ||||
|     listeners: dict[str, list[ListenerType[Any]]] | ||||
|     listeners: Dict[str, List[ListenerType[Any]]] | ||||
|     state: ApplicationState | ||||
|     websocket_enabled: bool | ||||
|     multiplexer: WorkerMultiplexer | ||||
| @@ -100,7 +112,8 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|         """ | ||||
|         if not self.asgi: | ||||
|             if self.config.USE_UVLOOP is True or ( | ||||
|                 isinstance(self.config.USE_UVLOOP, Default) and not OS_IS_WINDOWS | ||||
|                 isinstance(self.config.USE_UVLOOP, Default) | ||||
|                 and not OS_IS_WINDOWS | ||||
|             ): | ||||
|                 try_use_uvloop() | ||||
|             elif OS_IS_WINDOWS: | ||||
| @@ -146,28 +159,28 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     def run( | ||||
|         self, | ||||
|         host: str | None = None, | ||||
|         port: int | None = None, | ||||
|         host: Optional[str] = None, | ||||
|         port: Optional[int] = None, | ||||
|         *, | ||||
|         dev: bool = False, | ||||
|         debug: bool = False, | ||||
|         auto_reload: bool | None = None, | ||||
|         auto_reload: Optional[bool] = None, | ||||
|         version: HTTPVersion = HTTP.VERSION_1, | ||||
|         ssl: None | SSLContext | dict | str | list | tuple = None, | ||||
|         sock: socket | None = None, | ||||
|         ssl: Union[None, SSLContext, dict, str, list, tuple] = None, | ||||
|         sock: Optional[socket] = None, | ||||
|         workers: int = 1, | ||||
|         protocol: type[Protocol] | None = None, | ||||
|         protocol: Optional[Type[Protocol]] = None, | ||||
|         backlog: int = 100, | ||||
|         register_sys_signals: bool = True, | ||||
|         access_log: bool | None = None, | ||||
|         unix: str | None = None, | ||||
|         loop: AbstractEventLoop | None = None, | ||||
|         reload_dir: list[str] | str | None = None, | ||||
|         noisy_exceptions: bool | None = None, | ||||
|         access_log: Optional[bool] = None, | ||||
|         unix: Optional[str] = None, | ||||
|         loop: Optional[AbstractEventLoop] = None, | ||||
|         reload_dir: Optional[Union[List[str], str]] = None, | ||||
|         noisy_exceptions: Optional[bool] = None, | ||||
|         motd: bool = True, | ||||
|         fast: bool = False, | ||||
|         verbosity: int = 0, | ||||
|         motd_display: dict[str, str] | None = None, | ||||
|         motd_display: Optional[Dict[str, str]] = None, | ||||
|         auto_tls: bool = False, | ||||
|         single_process: bool = False, | ||||
|     ) -> None: | ||||
| @@ -276,28 +289,28 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     def prepare( | ||||
|         self, | ||||
|         host: str | None = None, | ||||
|         port: int | None = None, | ||||
|         host: Optional[str] = None, | ||||
|         port: Optional[int] = None, | ||||
|         *, | ||||
|         dev: bool = False, | ||||
|         debug: bool = False, | ||||
|         auto_reload: bool | None = None, | ||||
|         auto_reload: Optional[bool] = None, | ||||
|         version: HTTPVersion = HTTP.VERSION_1, | ||||
|         ssl: None | SSLContext | dict | str | list | tuple = None, | ||||
|         sock: socket | None = None, | ||||
|         ssl: Union[None, SSLContext, dict, str, list, tuple] = None, | ||||
|         sock: Optional[socket] = None, | ||||
|         workers: int = 1, | ||||
|         protocol: type[Protocol] | None = None, | ||||
|         protocol: Optional[Type[Protocol]] = None, | ||||
|         backlog: int = 100, | ||||
|         register_sys_signals: bool = True, | ||||
|         access_log: bool | None = None, | ||||
|         unix: str | None = None, | ||||
|         loop: AbstractEventLoop | None = None, | ||||
|         reload_dir: list[str] | str | None = None, | ||||
|         noisy_exceptions: bool | None = None, | ||||
|         access_log: Optional[bool] = None, | ||||
|         unix: Optional[str] = None, | ||||
|         loop: Optional[AbstractEventLoop] = None, | ||||
|         reload_dir: Optional[Union[List[str], str]] = None, | ||||
|         noisy_exceptions: Optional[bool] = None, | ||||
|         motd: bool = True, | ||||
|         fast: bool = False, | ||||
|         verbosity: int = 0, | ||||
|         motd_display: dict[str, str] | None = None, | ||||
|         motd_display: Optional[Dict[str, str]] = None, | ||||
|         coffee: bool = False, | ||||
|         auto_tls: bool = False, | ||||
|         single_process: bool = False, | ||||
| @@ -372,7 +385,8 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|         if single_process and (fast or (workers > 1) or auto_reload): | ||||
|             raise RuntimeError( | ||||
|                 "Single process cannot be run with multiple workers " "or auto-reload" | ||||
|                 "Single process cannot be run with multiple workers " | ||||
|                 "or auto-reload" | ||||
|             ) | ||||
|  | ||||
|         if register_sys_signals is False and not single_process: | ||||
| @@ -391,7 +405,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             for directory in reload_dir: | ||||
|                 direc = Path(directory) | ||||
|                 if not direc.is_dir(): | ||||
|                     logger.warning(f"Directory {directory} could not be located") | ||||
|                     logger.warning( | ||||
|                         f"Directory {directory} could not be located" | ||||
|                     ) | ||||
|                 self.state.reload_dirs.add(Path(directory)) | ||||
|  | ||||
|         if loop is not None: | ||||
| @@ -406,7 +422,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             host, port = self.get_address(host, port, version, auto_tls) | ||||
|  | ||||
|         if protocol is None: | ||||
|             protocol = WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||
|             protocol = ( | ||||
|                 WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||
|             ) | ||||
|  | ||||
|         # Set explicitly passed configuration values | ||||
|         for attribute, value in { | ||||
| @@ -442,7 +460,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             register_sys_signals=register_sys_signals, | ||||
|             auto_tls=auto_tls, | ||||
|         ) | ||||
|         self.state.server_info.append(ApplicationServerInfo(settings=server_settings)) | ||||
|         self.state.server_info.append( | ||||
|             ApplicationServerInfo(settings=server_settings) | ||||
|         ) | ||||
|  | ||||
|         # if self.config.USE_UVLOOP is True or ( | ||||
|         #     self.config.USE_UVLOOP is _default and not OS_IS_WINDOWS | ||||
| @@ -451,20 +471,20 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     async def create_server( | ||||
|         self, | ||||
|         host: str | None = None, | ||||
|         port: int | None = None, | ||||
|         host: Optional[str] = None, | ||||
|         port: Optional[int] = None, | ||||
|         *, | ||||
|         debug: bool = False, | ||||
|         ssl: None | SSLContext | dict | str | list | tuple = None, | ||||
|         sock: socket | None = None, | ||||
|         protocol: type[Protocol] | None = None, | ||||
|         ssl: Union[None, SSLContext, dict, str, list, tuple] = None, | ||||
|         sock: Optional[socket] = None, | ||||
|         protocol: Optional[Type[Protocol]] = None, | ||||
|         backlog: int = 100, | ||||
|         access_log: bool | None = None, | ||||
|         unix: str | None = None, | ||||
|         access_log: Optional[bool] = None, | ||||
|         unix: Optional[str] = None, | ||||
|         return_asyncio_server: bool = True, | ||||
|         asyncio_server_kwargs: dict[str, Any] | None = None, | ||||
|         noisy_exceptions: bool | None = None, | ||||
|     ) -> AsyncioServer | None: | ||||
|         asyncio_server_kwargs: Optional[Dict[str, Any]] = None, | ||||
|         noisy_exceptions: Optional[bool] = None, | ||||
|     ) -> Optional[AsyncioServer]: | ||||
|         """ | ||||
|         Low level API for creating a Sanic Server instance. | ||||
|  | ||||
| @@ -538,7 +558,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             host, port = host, port = self.get_address(host, port) | ||||
|  | ||||
|         if protocol is None: | ||||
|             protocol = WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||
|             protocol = ( | ||||
|                 WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||
|             ) | ||||
|  | ||||
|         # Set explicitly passed configuration values | ||||
|         for attribute, value in { | ||||
| @@ -615,21 +637,21 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     def _helper( | ||||
|         self, | ||||
|         host: str | None = None, | ||||
|         port: int | None = None, | ||||
|         host: Optional[str] = None, | ||||
|         port: Optional[int] = None, | ||||
|         debug: bool = False, | ||||
|         version: HTTPVersion = HTTP.VERSION_1, | ||||
|         ssl: None | SSLContext | dict | str | list | tuple = None, | ||||
|         sock: socket | None = None, | ||||
|         unix: str | None = None, | ||||
|         ssl: Union[None, SSLContext, dict, str, list, tuple] = None, | ||||
|         sock: Optional[socket] = None, | ||||
|         unix: Optional[str] = None, | ||||
|         workers: int = 1, | ||||
|         loop: AbstractEventLoop | None = None, | ||||
|         protocol: type[Protocol] = HttpProtocol, | ||||
|         loop: Optional[AbstractEventLoop] = None, | ||||
|         protocol: Type[Protocol] = HttpProtocol, | ||||
|         backlog: int = 100, | ||||
|         register_sys_signals: bool = True, | ||||
|         run_async: bool = False, | ||||
|         auto_tls: bool = False, | ||||
|     ) -> dict[str, Any]: | ||||
|     ) -> Dict[str, Any]: | ||||
|         """Helper function used by `run` and `create_server`.""" | ||||
|         if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0: | ||||
|             raise ValueError( | ||||
| @@ -704,7 +726,7 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     def motd( | ||||
|         self, | ||||
|         server_settings: dict[str, Any] | None = None, | ||||
|         server_settings: Optional[Dict[str, Any]] = None, | ||||
|     ) -> None: | ||||
|         """Outputs the message of the day (MOTD). | ||||
|  | ||||
| @@ -733,8 +755,8 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             MOTD.output(logo, serve_location, display, extra) | ||||
|  | ||||
|     def get_motd_data( | ||||
|         self, server_settings: dict[str, Any] | None = None | ||||
|     ) -> tuple[dict[str, Any], dict[str, Any]]: | ||||
|         self, server_settings: Optional[Dict[str, Any]] = None | ||||
|     ) -> Tuple[Dict[str, Any], Dict[str, Any]]: | ||||
|         """Retrieves the message of the day (MOTD) data. | ||||
|  | ||||
|         Args: | ||||
| @@ -780,7 +802,10 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|                 reload_display += ", ".join( | ||||
|                     [ | ||||
|                         "", | ||||
|                         *(str(path.absolute()) for path in self.state.reload_dirs), | ||||
|                         *( | ||||
|                             str(path.absolute()) | ||||
|                             for path in self.state.reload_dirs | ||||
|                         ), | ||||
|                     ] | ||||
|                 ) | ||||
|             display["auto-reload"] = reload_display | ||||
| @@ -819,7 +844,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             return f"http://<{location}>" | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_server_location(server_settings: dict[str, Any] | None = None) -> str: | ||||
|     def get_server_location( | ||||
|         server_settings: Optional[Dict[str, Any]] = None | ||||
|     ) -> str: | ||||
|         """Using the server settings, retrieve the server location. | ||||
|  | ||||
|         Args: | ||||
| @@ -853,11 +880,11 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_address( | ||||
|         host: str | None, | ||||
|         port: int | None, | ||||
|         host: Optional[str], | ||||
|         port: Optional[int], | ||||
|         version: HTTPVersion = HTTP.VERSION_1, | ||||
|         auto_tls: bool = False, | ||||
|     ) -> tuple[str, int]: | ||||
|     ) -> Tuple[str, int]: | ||||
|         """Retrieve the host address and port, with default values based on the given parameters. | ||||
|  | ||||
|         Args: | ||||
| @@ -886,7 +913,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|     @classmethod | ||||
|     def _get_startup_method(cls) -> str: | ||||
|         return ( | ||||
|             cls.start_method if not isinstance(cls.start_method, Default) else "spawn" | ||||
|             cls.start_method | ||||
|             if not isinstance(cls.start_method, Default) | ||||
|             else "spawn" | ||||
|         ) | ||||
|  | ||||
|     @classmethod | ||||
| @@ -913,10 +942,10 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|     @classmethod | ||||
|     def serve( | ||||
|         cls, | ||||
|         primary: Sanic | None = None, | ||||
|         primary: Optional[Sanic] = None, | ||||
|         *, | ||||
|         app_loader: AppLoader | None = None, | ||||
|         factory: Callable[[], Sanic] | None = None, | ||||
|         app_loader: Optional[AppLoader] = None, | ||||
|         factory: Optional[Callable[[], Sanic]] = None, | ||||
|     ) -> None: | ||||
|         """Serve one or more Sanic applications. | ||||
|  | ||||
| @@ -967,7 +996,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|                     try: | ||||
|                         primary = apps[0] | ||||
|                     except IndexError: | ||||
|                         raise RuntimeError("Did not find any applications.") from None | ||||
|                         raise RuntimeError( | ||||
|                             "Did not find any applications." | ||||
|                         ) from None | ||||
|  | ||||
|             # This exists primarily for unit testing | ||||
|             if not primary.state.server_info:  # no cov | ||||
| @@ -1009,7 +1040,7 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             primary_server_info.settings["run_multiple"] = True | ||||
|             monitor_sub, monitor_pub = Pipe(True) | ||||
|             worker_state: Mapping[str, Any] = sync_manager.dict() | ||||
|             kwargs: dict[str, Any] = { | ||||
|             kwargs: Dict[str, Any] = { | ||||
|                 **primary_server_info.settings, | ||||
|                 "monitor_publisher": monitor_pub, | ||||
|                 "worker_state": worker_state, | ||||
| @@ -1061,7 +1092,7 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|                 worker_state, | ||||
|             ) | ||||
|             if cls.should_auto_reload(): | ||||
|                 reload_dirs: set[Path] = primary.state.reload_dirs.union( | ||||
|                 reload_dirs: Set[Path] = primary.state.reload_dirs.union( | ||||
|                     *(app.state.reload_dirs for app in apps) | ||||
|                 ) | ||||
|                 reloader = Reloader(monitor_pub, 0, reload_dirs, app_loader) | ||||
| @@ -1070,7 +1101,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             inspector = None | ||||
|             if primary.config.INSPECTOR: | ||||
|                 display, extra = primary.get_motd_data() | ||||
|                 packages = [pkg.strip() for pkg in display["packages"].split(",")] | ||||
|                 packages = [ | ||||
|                     pkg.strip() for pkg in display["packages"].split(",") | ||||
|                 ] | ||||
|                 module = import_module("sanic") | ||||
|                 sanic_version = f"sanic=={module.__version__}"  # type: ignore | ||||
|                 app_info = { | ||||
| @@ -1101,7 +1134,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             exit_code = 1 | ||||
|         except BaseException: | ||||
|             kwargs = primary_server_info.settings | ||||
|             error_logger.exception("Experienced exception while trying to serve") | ||||
|             error_logger.exception( | ||||
|                 "Experienced exception while trying to serve" | ||||
|             ) | ||||
|             raise | ||||
|         finally: | ||||
|             logger.info("Server Stopped") | ||||
| @@ -1129,7 +1164,7 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|             os._exit(exit_code) | ||||
|  | ||||
|     @classmethod | ||||
|     def serve_single(cls, primary: Sanic | None = None) -> None: | ||||
|     def serve_single(cls, primary: Optional[Sanic] = None) -> None: | ||||
|         """Serve a single process of a Sanic application. | ||||
|  | ||||
|         Similar to `serve`, but only serves a single process. When used, | ||||
| @@ -1207,7 +1242,9 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|         try: | ||||
|             worker_serve(monitor_publisher=None, **kwargs) | ||||
|         except BaseException: | ||||
|             error_logger.exception("Experienced exception while trying to serve") | ||||
|             error_logger.exception( | ||||
|                 "Experienced exception while trying to serve" | ||||
|             ) | ||||
|             raise | ||||
|         finally: | ||||
|             logger.info("Server Stopped") | ||||
| @@ -1226,7 +1263,7 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|         self, | ||||
|         primary: Sanic, | ||||
|         _, | ||||
|         apps: list[Sanic], | ||||
|         apps: List[Sanic], | ||||
|     ) -> None: | ||||
|         for app in apps: | ||||
|             if ( | ||||
| @@ -1271,7 +1308,7 @@ class StartupMixin(metaclass=SanicMeta): | ||||
|                     if not server_info.settings["loop"]: | ||||
|                         server_info.settings["loop"] = get_running_loop() | ||||
|  | ||||
|                     serve_args: dict[str, Any] = { | ||||
|                     serve_args: Dict[str, Any] = { | ||||
|                         **server_info.settings, | ||||
|                         "run_async": True, | ||||
|                         "reuse_port": bool(primary.state.workers - 1), | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user