diff --git a/docs/conf.py b/docs/conf.py index 07bfe0f9..a612844c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # Sanic documentation build configuration file, created by # sphinx-quickstart on Sun Dec 25 18:07:21 2016. @@ -10,7 +9,6 @@ import os import sys - # Add support for auto-doc @@ -19,8 +17,7 @@ import sys root_directory = os.path.dirname(os.getcwd()) sys.path.insert(0, root_directory) -import sanic - +import sanic # noqa: E402 # -- General configuration ------------------------------------------------ diff --git a/examples/add_task_sanic.py b/examples/add_task_sanic.py index 2487b85e..539788a5 100644 --- a/examples/add_task_sanic.py +++ b/examples/add_task_sanic.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- - import asyncio from sanic import Sanic - app = Sanic("Example") diff --git a/examples/amending_request_object.py b/examples/amending_request_object.py index 63f8d1d5..80902594 100644 --- a/examples/amending_request_object.py +++ b/examples/amending_request_object.py @@ -3,7 +3,6 @@ from random import randint from sanic import Sanic from sanic.response import text - app = Sanic("Example") @@ -25,5 +24,6 @@ def key_exist_handler(request): return text("num does not exist in request") + if __name__ == "__main__": app.run(host="0.0.0.0", port=8000, debug=True) diff --git a/examples/authorized_sanic.py b/examples/authorized_sanic.py index 40f0f5a4..4f1c83ef 100644 --- a/examples/authorized_sanic.py +++ b/examples/authorized_sanic.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- - from functools import wraps from sanic import Sanic from sanic.response import json - app = Sanic("Example") diff --git a/examples/blueprint_middlware_execution_order.py b/examples/blueprint_middlware_execution_order.py index b0448da8..11a88e4f 100644 --- a/examples/blueprint_middlware_execution_order.py +++ b/examples/blueprint_middlware_execution_order.py @@ -1,7 +1,6 @@ from sanic import Blueprint, Sanic from sanic.response import text - """ Demonstrates that blueprint request middleware are executed in the order they are added. And blueprint response middleware are executed in _reverse_ order. diff --git a/examples/blueprints.py b/examples/blueprints.py index e368573b..75a81a15 100644 --- a/examples/blueprints.py +++ b/examples/blueprints.py @@ -1,7 +1,6 @@ from sanic import Blueprint, Sanic from sanic.response import file, json - app = Sanic("Example") blueprint = Blueprint("bp_example", url_prefix="/my_blueprint") blueprint2 = Blueprint("bp_example2", url_prefix="/my_blueprint2") diff --git a/examples/delayed_response.py b/examples/delayed_response.py index 91e24773..07698aed 100644 --- a/examples/delayed_response.py +++ b/examples/delayed_response.py @@ -2,7 +2,6 @@ from asyncio import sleep from sanic import Sanic, response - app = Sanic("DelayedResponseApp", strict_slashes=True) app.config.AUTO_EXTEND = False diff --git a/examples/exception_monitoring.py b/examples/exception_monitoring.py index eb52ed9a..17cd7127 100644 --- a/examples/exception_monitoring.py +++ b/examples/exception_monitoring.py @@ -10,7 +10,6 @@ an external service. from sanic.exceptions import SanicException from sanic.handlers import ErrorHandler - """ Imports and code relevant for our CustomHandler class (Ordinarily this would be in a separate file) @@ -39,7 +38,6 @@ server's error_handler to an instance of our CustomHandler from sanic import Sanic - handler = CustomHandler() app = Sanic("Example", error_handler=handler) diff --git a/examples/hello_world.py b/examples/hello_world.py index 9e45b856..cacfbd67 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,6 +1,5 @@ from sanic import Sanic, response - app = Sanic("Example") diff --git a/examples/http_redirect.py b/examples/http_redirect.py index 34dc43ab..f3f176fc 100644 --- a/examples/http_redirect.py +++ b/examples/http_redirect.py @@ -2,7 +2,6 @@ from sanic import Sanic, response, text from sanic.handlers import ErrorHandler from sanic.server.async_server import AsyncioServer - HTTP_PORT = 9999 HTTPS_PORT = 8888 @@ -36,9 +35,7 @@ def proxy(request, path): @https.main_process_start async def start(app, _): - http_server = await http.create_server( - port=HTTP_PORT, return_asyncio_server=True - ) + http_server = await http.create_server(port=HTTP_PORT, return_asyncio_server=True) app.add_task(runner(http, http_server)) app.ctx.http_server = http_server app.ctx.http = http @@ -69,5 +66,6 @@ async def runner(app: Sanic, app_server: AsyncioServer): app.is_running = False app.is_stopping = True + if __name__ == "__main__": https.run(port=HTTPS_PORT, debug=True) diff --git a/examples/limit_concurrency.py b/examples/limit_concurrency.py index e97f964a..4e3636af 100644 --- a/examples/limit_concurrency.py +++ b/examples/limit_concurrency.py @@ -5,7 +5,6 @@ import httpx from sanic import Sanic from sanic.response import json - app = Sanic("Example") sem = None diff --git a/examples/log_request_id.py b/examples/log_request_id.py index 87825640..2d845d65 100644 --- a/examples/log_request_id.py +++ b/examples/log_request_id.py @@ -1,10 +1,8 @@ import logging - from contextvars import ContextVar from sanic import Sanic, response - log = logging.getLogger(__name__) diff --git a/examples/logdna_example.py b/examples/logdna_example.py index 9e9303b7..f942a796 100644 --- a/examples/logdna_example.py +++ b/examples/logdna_example.py @@ -1,6 +1,5 @@ import logging import socket - from os import getenv from platform import node from uuid import getnode as get_mac @@ -11,7 +10,6 @@ from sanic import Sanic from sanic.request import Request from sanic.response import json - log = logging.getLogger("logdna") log.setLevel(logging.INFO) @@ -35,9 +33,7 @@ logdna_options = { "mac": get_mac_address(), } -logdna_handler = LogDNAHandler( - getenv("LOGDNA_API_KEY"), options=logdna_options -) +logdna_handler = LogDNAHandler(getenv("LOGDNA_API_KEY"), options=logdna_options) logdna = logging.getLogger(__name__) logdna.setLevel(logging.INFO) @@ -48,7 +44,7 @@ app = Sanic("Example") @app.middleware def log_request(request: Request): - logdna.info("I was Here with a new Request to URL: {}".format(request.url)) + logdna.info(f"I was Here with a new Request to URL: {request.url}") @app.route("/") diff --git a/examples/modify_header_example.py b/examples/modify_header_example.py index 6455cb85..db3cf063 100644 --- a/examples/modify_header_example.py +++ b/examples/modify_header_example.py @@ -4,7 +4,6 @@ Modify header or status in response from sanic import Sanic, response - app = Sanic("Example") diff --git a/examples/override_logging.py b/examples/override_logging.py index ebc0e86f..020b933e 100644 --- a/examples/override_logging.py +++ b/examples/override_logging.py @@ -2,7 +2,6 @@ import logging from sanic import Sanic, text - logging_format = "[%(asctime)s] %(process)d-%(levelname)s " logging_format += "%(module)s::%(funcName)s():l%(lineno)d: " logging_format += "%(message)s" diff --git a/examples/pytest_xdist.py b/examples/pytest_xdist.py index 7adeb93a..920d987d 100644 --- a/examples/pytest_xdist.py +++ b/examples/pytest_xdist.py @@ -11,7 +11,6 @@ Run with xdist params: import re import pytest - from sanic_testing import SanicTestClient from sanic_testing.testing import PORT as PORT_BASE diff --git a/examples/redirect_example.py b/examples/redirect_example.py index 74fb10de..50200f90 100644 --- a/examples/redirect_example.py +++ b/examples/redirect_example.py @@ -1,6 +1,5 @@ from sanic import Sanic, response - app = Sanic("Example") diff --git a/examples/request_stream/server.py b/examples/request_stream/server.py index bffc7451..9f7b77f3 100644 --- a/examples/request_stream/server.py +++ b/examples/request_stream/server.py @@ -4,7 +4,6 @@ from sanic.response import stream, text from sanic.views import HTTPMethodView from sanic.views import stream as stream_decorator - bp = Blueprint("bp_example") app = Sanic("Example") diff --git a/examples/request_timeout.py b/examples/request_timeout.py index 07934b0c..1be8b875 100644 --- a/examples/request_timeout.py +++ b/examples/request_timeout.py @@ -4,7 +4,6 @@ from sanic import Sanic, response from sanic.config import Config from sanic.exceptions import RequestTimeout - Config.REQUEST_TIMEOUT = 1 app = Sanic("Example") diff --git a/examples/rollbar_example.py b/examples/rollbar_example.py index 53d53c5a..c63f6f5f 100644 --- a/examples/rollbar_example.py +++ b/examples/rollbar_example.py @@ -6,7 +6,6 @@ from sanic import Sanic from sanic.exceptions import SanicException from sanic.handlers import ErrorHandler - rollbar.init(getenv("ROLLBAR_API_KEY")) diff --git a/examples/run_asgi.py b/examples/run_asgi.py index bc559789..aedc1039 100644 --- a/examples/run_asgi.py +++ b/examples/run_asgi.py @@ -10,7 +10,6 @@ from pathlib import Path from sanic import Sanic, response - app = Sanic("Example") @@ -43,9 +42,7 @@ async def handler_file(request): @app.route("/file_stream") async def handler_file_stream(request): - return await response.file_stream( - Path("../") / "setup.py", chunk_size=1024 - ) + return await response.file_stream(Path("../") / "setup.py", chunk_size=1024) @app.post("/stream", stream=True) diff --git a/examples/run_async.py b/examples/run_async.py index 84c8a6be..a627a59e 100644 --- a/examples/run_async.py +++ b/examples/run_async.py @@ -4,7 +4,6 @@ import uvloop from sanic import Sanic, response - app = Sanic("Example") diff --git a/examples/run_async_advanced.py b/examples/run_async_advanced.py index c782d49a..e9cb8805 100644 --- a/examples/run_async_advanced.py +++ b/examples/run_async_advanced.py @@ -1,5 +1,4 @@ import asyncio - from signal import SIGINT, signal import uvloop @@ -7,7 +6,6 @@ import uvloop from sanic import Sanic, response from sanic.server import AsyncioServer - app = Sanic("Example") @@ -35,11 +33,10 @@ async def after_server_stop(app, loop): async def test(request): return response.json({"answer": "42"}) + if __name__ == "__main__": asyncio.set_event_loop(uvloop.new_event_loop()) - serv_coro = app.create_server( - host="0.0.0.0", port=8000, return_asyncio_server=True - ) + serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True) loop = asyncio.get_event_loop() serv_task = asyncio.ensure_future(serv_coro, loop=loop) signal(SIGINT, lambda s, f: loop.stop()) diff --git a/examples/sentry_example.py b/examples/sentry_example.py index 1dfc1ba4..753af0b7 100644 --- a/examples/sentry_example.py +++ b/examples/sentry_example.py @@ -6,7 +6,6 @@ from sentry_sdk.integrations.sanic import SanicIntegration from sanic import Sanic from sanic.response import json - sentry_init( dsn=getenv("SENTRY_DSN"), integrations=[SanicIntegration()], diff --git a/examples/simple_async_view.py b/examples/simple_async_view.py index 4e73967c..f0690dae 100644 --- a/examples/simple_async_view.py +++ b/examples/simple_async_view.py @@ -2,7 +2,6 @@ from sanic import Sanic from sanic.response import text from sanic.views import HTTPMethodView - app = Sanic("some_name") diff --git a/examples/static_assets.py b/examples/static_assets.py index 347b489c..7cd223b3 100644 --- a/examples/static_assets.py +++ b/examples/static_assets.py @@ -1,6 +1,5 @@ from sanic import Sanic - app = Sanic("Example") app.static("/", "./static") diff --git a/examples/teapot.py b/examples/teapot.py index 5d742efc..5c52fb69 100644 --- a/examples/teapot.py +++ b/examples/teapot.py @@ -1,7 +1,6 @@ from sanic import Sanic from sanic import response as res - app = Sanic("Example") diff --git a/examples/try_everything.py b/examples/try_everything.py index f1377060..211f0389 100644 --- a/examples/try_everything.py +++ b/examples/try_everything.py @@ -4,7 +4,6 @@ from sanic import Sanic, response from sanic.exceptions import ServerError from sanic.log import logger as log - app = Sanic("Example") @@ -20,7 +19,7 @@ def test_sync(request): @app.route("/dynamic//") def test_params(request, name, i): - return response.text("yeehaww {} {}".format(name, i)) + return response.text(f"yeehaww {name} {i}") @app.route("/exception") @@ -43,9 +42,7 @@ async def test_file(request): @app.route("/file_stream") async def test_file_stream(request): - return await response.file_stream( - os.path.abspath("setup.py"), chunk_size=1024 - ) + return await response.file_stream(os.path.abspath("setup.py"), chunk_size=1024) # ----------------------------------------------- # diff --git a/examples/unix_socket.py b/examples/unix_socket.py index 347c46b2..608502ad 100644 --- a/examples/unix_socket.py +++ b/examples/unix_socket.py @@ -1,6 +1,5 @@ from sanic import Sanic, response - app = Sanic("Example") diff --git a/examples/url_for_example.py b/examples/url_for_example.py index c3d936fa..f855c505 100644 --- a/examples/url_for_example.py +++ b/examples/url_for_example.py @@ -1,6 +1,5 @@ from sanic import Sanic, response - app = Sanic("Example") @@ -14,7 +13,7 @@ async def index(request): @app.route("/posts/") async def post_handler(request, post_id): - return response.text("Post - {}".format(post_id)) + return response.text(f"Post - {post_id}") if __name__ == "__main__": diff --git a/examples/versioned_blueprint_group.py b/examples/versioned_blueprint_group.py index 56715acc..e9812dcf 100644 --- a/examples/versioned_blueprint_group.py +++ b/examples/versioned_blueprint_group.py @@ -2,7 +2,6 @@ from sanic import Sanic from sanic.blueprints import Blueprint from sanic.response import json - app = Sanic(name="blue-print-group-version-example") bp1 = Blueprint(name="ultron", url_prefix="/ultron") diff --git a/examples/vhosts.py b/examples/vhosts.py index 335a3be8..bbac0f31 100644 --- a/examples/vhosts.py +++ b/examples/vhosts.py @@ -1,7 +1,6 @@ from sanic import Sanic, response from sanic.blueprints import Blueprint - # Usage # curl -H "Host: example.com" localhost:8000 # curl -H "Host: sub.example.com" localhost:8000 @@ -12,9 +11,7 @@ app = Sanic("Example") bp = Blueprint("bp", host="bp.example.com") -@app.route( - "/", host=["example.com", "somethingelse.com", "therestofyourdomains.com"] -) +@app.route("/", host=["example.com", "somethingelse.com", "therestofyourdomains.com"]) async def hello_0(request): return response.text("Some defaults") diff --git a/examples/websocket.py b/examples/websocket.py index ce5a105c..c273ee98 100644 --- a/examples/websocket.py +++ b/examples/websocket.py @@ -1,7 +1,6 @@ from sanic import Sanic from sanic.response import redirect - app = Sanic("Example") diff --git a/guide/content/en/migrate.py b/guide/content/en/migrate.py index e0510af4..5850597f 100644 --- a/guide/content/en/migrate.py +++ b/guide/content/en/migrate.py @@ -7,14 +7,10 @@ from emoji import EMOJI COLUMN_PATTERN = re.compile(r"---:1\s*(.*?)\s*:--:1\s*(.*?)\s*:---", re.DOTALL) PYTHON_HIGHLIGHT_PATTERN = re.compile(r"```python\{+.*?\}", re.DOTALL) BASH_HIGHLIGHT_PATTERN = re.compile(r"```bash\{+.*?\}", re.DOTALL) -NOTIFICATION_PATTERN = re.compile( - r":::\s*(\w+)\s*(.*?)\n([\s\S]*?):::", re.MULTILINE -) +NOTIFICATION_PATTERN = re.compile(r":::\s*(\w+)\s*(.*?)\n([\s\S]*?):::", re.MULTILINE) EMOJI_PATTERN = re.compile(r":(\w+):") CURRENT_DIR = Path(__file__).parent -SOURCE_DIR = ( - CURRENT_DIR.parent.parent.parent.parent / "sanic-guide" / "src" / "en" -) +SOURCE_DIR = CURRENT_DIR.parent.parent.parent.parent / "sanic-guide" / "src" / "en" def convert_columns(content: str): diff --git a/guide/pyproject.toml b/guide/pyproject.toml new file mode 100644 index 00000000..1d5780d3 --- /dev/null +++ b/guide/pyproject.toml @@ -0,0 +1,5 @@ +[tool.ruff] +extend = "../pyproject.toml" + +[tool.ruff.isort] +known-first-party = ["webapp"] diff --git a/guide/webapp/display/layouts/elements/footer.py b/guide/webapp/display/layouts/elements/footer.py index 4ef2051a..f5545580 100644 --- a/guide/webapp/display/layouts/elements/footer.py +++ b/guide/webapp/display/layouts/elements/footer.py @@ -13,9 +13,7 @@ def do_footer(builder: Builder, request: Request) -> None: def _pagination(request: Request) -> Builder: - return E.div( - _pagination_left(request), _pagination_right(request), class_="level" - ) + return E.div(_pagination_left(request), _pagination_right(request), class_="level") def _pagination_left(request: Request) -> Builder: @@ -64,9 +62,7 @@ def _content() -> Builder: href="https://github.com/sanic-org/sanic/blob/master/LICENSE", target="_blank", rel="nofollow noopener noreferrer", - ).br()( - E.small(f"Copyright © 2018-{year} Sanic Community Organization") - ), + ).br()(E.small(f"Copyright © 2018-{year} Sanic Community Organization")), ) return E.div( inner, diff --git a/guide/webapp/display/layouts/elements/navbar.py b/guide/webapp/display/layouts/elements/navbar.py index fb26fcdd..3bb1810d 100644 --- a/guide/webapp/display/layouts/elements/navbar.py +++ b/guide/webapp/display/layouts/elements/navbar.py @@ -1,17 +1,14 @@ -from webapp.display.layouts.models import MenuItem - from html5tagger import Builder, E # type: ignore from sanic import Request +from webapp.display.layouts.models import MenuItem + def do_navbar(builder: Builder, request: Request) -> None: navbar_items = [ - _render_navbar_item(item, request) - for item in request.app.config.NAVBAR + _render_navbar_item(item, request) for item in request.app.config.NAVBAR ] - container = E.div( - _search_form(request), *navbar_items, class_="navbar-end" - ) + container = E.div(_search_form(request), *navbar_items, class_="navbar-end") builder.nav( E.div(container, class_="navbar-menu"), @@ -46,10 +43,7 @@ def _render_navbar_item(item: MenuItem, request: Request) -> Builder: return E.div( E.a(item.label, class_="navbar-link"), E.div( - *( - _render_navbar_item(subitem, request) - for subitem in item.items - ), + *(_render_navbar_item(subitem, request) for subitem in item.items), class_="navbar-dropdown", ), class_="navbar-item has-dropdown is-hoverable", diff --git a/guide/webapp/display/layouts/elements/sidebar.py b/guide/webapp/display/layouts/elements/sidebar.py index 530f6237..60a07e37 100644 --- a/guide/webapp/display/layouts/elements/sidebar.py +++ b/guide/webapp/display/layouts/elements/sidebar.py @@ -1,9 +1,9 @@ -from webapp.display.layouts.models import MenuItem -from webapp.display.text import slugify - from html5tagger import Builder, E # type: ignore from sanic import Request +from webapp.display.layouts.models import MenuItem +from webapp.display.text import slugify + def do_sidebar(builder: Builder, request: Request) -> None: builder.a(class_="burger")(E.span().span().span().span()) @@ -15,9 +15,7 @@ def _menu_items(request: Request) -> list[Builder]: _sanic_logo(request), *_sidebar_items(request), E.hr(), - E.p("Current with version ").strong( - request.app.config.GENERAL.current_version - ), + E.p("Current with version ").strong(request.app.config.GENERAL.current_version), E.hr(), E.p("Want more? ").a( "sanicbook.com", href="https://sanicbook.com", target="_blank" @@ -73,9 +71,7 @@ def _single_sidebar_item(item: MenuItem, request: Request) -> Builder: kwargs = {} classes: list[str] = [] li_classes = "menu-item" - _, page, _ = request.app.ctx.get_page( - request.ctx.language, item.path or "" - ) + _, page, _ = request.app.ctx.get_page(request.ctx.language, item.path or "") if request.path == path: classes.append("is-active") if item.href: diff --git a/guide/webapp/display/layouts/main.py b/guide/webapp/display/layouts/main.py index dbbaeac1..44f77918 100644 --- a/guide/webapp/display/layouts/main.py +++ b/guide/webapp/display/layouts/main.py @@ -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 diff --git a/guide/webapp/display/markdown.py b/guide/webapp/display/markdown.py index 58d33bf5..2236dda6 100644 --- a/guide/webapp/display/markdown.py +++ b/guide/webapp/display/markdown.py @@ -1,6 +1,7 @@ import re from textwrap import dedent +from html5tagger import HTML, Builder, E # type: ignore from mistune import HTMLRenderer, create_markdown, escape from mistune.directives import RSTDirective, TableOfContents from mistune.util import safe_entity @@ -8,8 +9,6 @@ from pygments import highlight from pygments.formatters import html from pygments.lexers import get_lexer_by_name -from html5tagger import HTML, Builder, E # type: ignore - from .code_style import SanicCodeStyle from .plugins.attrs import Attributes from .plugins.columns import Column @@ -37,9 +36,9 @@ class DocsRenderer(HTMLRenderer): class_="code-block__copy", onclick="copyCode(this)", ): - builder.div( - class_="code-block__rectangle code-block__filled" - ).div(class_="code-block__rectangle code-block__outlined") + builder.div(class_="code-block__rectangle code-block__filled").div( + class_="code-block__rectangle code-block__outlined" + ) else: builder.pre(E.code(escape(code))) return str(builder) @@ -47,9 +46,7 @@ class DocsRenderer(HTMLRenderer): def heading(self, text: str, level: int, **attrs) -> str: ident = slugify(text) if level > 1: - text += self._make_tag( - "a", {"href": f"#{ident}", "class": "anchor"}, "#" - ) + text += self._make_tag("a", {"href": f"#{ident}", "class": "anchor"}, "#") return self._make_tag( f"h{level}", {"id": ident, "class": f"is-size-{level}"}, text ) @@ -93,9 +90,7 @@ class DocsRenderer(HTMLRenderer): def _make_tag( self, tag: str, attributes: dict[str, str], text: str | None = None ) -> str: - attrs = " ".join( - f'{key}="{value}"' for key, value in attributes.items() - ) + attrs = " ".join(f'{key}="{value}"' for key, value in attributes.items()) if text is None: return f"<{tag} {attrs} />" return f"<{tag} {attrs}>{text}" diff --git a/guide/webapp/display/page/docobject.py b/guide/webapp/display/page/docobject.py index b812b440..c09cd1ca 100644 --- a/guide/webapp/display/page/docobject.py +++ b/guide/webapp/display/page/docobject.py @@ -10,7 +10,6 @@ from html import escape from docstring_parser import Docstring, DocstringParam, DocstringRaises from docstring_parser import parse as parse_docstring from docstring_parser.common import DocstringExample - from html5tagger import HTML, Builder, E # type: ignore from ..markdown import render_markdown, slugify @@ -120,9 +119,7 @@ def _extract_docobjects(package_name: str) -> dict[str, DocObject]: docstrings = {} package = importlib.import_module(package_name) - for _, name, _ in pkgutil.walk_packages( - package.__path__, package_name + "." - ): + for _, name, _ in pkgutil.walk_packages(package.__path__, package_name + "."): module = importlib.import_module(name) for obj_name, obj in inspect.getmembers(module): if ( @@ -156,9 +153,7 @@ def _docobject_to_html( ) -> None: anchor_id = slugify(docobject.full_name.replace(".", "-")) anchor = E.a("#", class_="anchor", href=f"#{anchor_id}") - class_name, heading = _define_heading_and_class( - docobject, anchor, as_method - ) + class_name, heading = _define_heading_and_class(docobject, anchor, as_method) with builder.div(class_=class_name): builder(heading) @@ -212,9 +207,7 @@ def _docobject_to_html( if docobject.docstring.params: with builder.div(class_="box mt-5"): - builder.h5( - "Parameters", class_="is-size-5 has-text-weight-bold" - ) + builder.h5("Parameters", class_="is-size-5 has-text-weight-bold") _render_params(builder, docobject.docstring.params) if docobject.docstring.returns: @@ -239,9 +232,7 @@ def _signature_to_html( parts = [] parts.append("") for decorator in decorators: - parts.append( - f"@{decorator}
" - ) + parts.append(f"@{decorator}
") parts.append( f"{object_type} " f"{name}(" @@ -255,9 +246,7 @@ def _signature_to_html( annotation = "" if param.annotation != inspect.Parameter.empty: annotation = escape(str(param.annotation)) - parts.append( - f": {annotation}" - ) + parts.append(f": {annotation}") if param.default != inspect.Parameter.empty: default = escape(str(param.default)) if annotation == "str": @@ -268,9 +257,7 @@ def _signature_to_html( parts.append(")") if signature.return_annotation != inspect.Signature.empty: return_annotation = escape(str(signature.return_annotation)) - parts.append( - f": -> {return_annotation}" - ) + parts.append(f": -> {return_annotation}") parts.append("
") return "".join(parts) @@ -318,10 +305,7 @@ def _render_params(builder: Builder, params: list[DocstringParam]) -> None: builder.dd( HTML( render_markdown( - param.description - or param.arg_name - or param.type_name - or "" + param.description or param.arg_name or param.type_name or "" ) ) ) @@ -334,11 +318,7 @@ def _render_raises(builder: Builder, raises: list[DocstringRaises]) -> None: with builder.dl(class_="mt-2"): builder.dt(raise_.type_name, class_="is-family-monospace") builder.dd( - HTML( - render_markdown( - raise_.description or raise_.type_name or "" - ) - ) + HTML(render_markdown(raise_.description or raise_.type_name or "")) ) @@ -354,11 +334,7 @@ def _render_returns(builder: Builder, docobject: DocObject) -> None: if not return_type or return_type == inspect.Signature.empty: return_type = "N/A" - term = ( - "Return" - if not docobject.docstring.returns.is_generator - else "Yields" - ) + term = "Return" if not docobject.docstring.returns.is_generator else "Yields" builder.h5(term, class_="is-size-5 has-text-weight-bold") with builder.dl(class_="mt-2"): builder.dt(return_type, class_="is-family-monospace") @@ -373,17 +349,11 @@ def _render_returns(builder: Builder, docobject: DocObject) -> None: ) -def _render_examples( - builder: Builder, examples: list[DocstringExample] -) -> None: +def _render_examples(builder: Builder, examples: list[DocstringExample]) -> None: with builder.div(class_="box mt-5"): builder.h5("Examples", class_="is-size-5 has-text-weight-bold") for example in examples: with builder.div(class_="mt-2"): builder( - HTML( - render_markdown( - example.description or example.snippet or "" - ) - ) + HTML(render_markdown(example.description or example.snippet or "")) ) diff --git a/guide/webapp/display/page/page.py b/guide/webapp/display/page/page.py index a4b833d9..3c168f9e 100644 --- a/guide/webapp/display/page/page.py +++ b/guide/webapp/display/page/page.py @@ -2,7 +2,6 @@ from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path -from typing import Type from frontmatter import parse @@ -12,10 +11,8 @@ from ..layouts.main import MainLayout from ..markdown import render_markdown from .docobject import organize_docobjects -_PAGE_CACHE: dict[ - str, dict[str, tuple[Page | None, Page | None, Page | None]] -] = {} -_LAYOUTS_CACHE: dict[str, Type[BaseLayout]] = { +_PAGE_CACHE: dict[str, dict[str, tuple[Page | None, Page | None, Page | None]]] = {} +_LAYOUTS_CACHE: dict[str, type[BaseLayout]] = { "home": HomeLayout, "main": MainLayout, } @@ -43,7 +40,7 @@ class Page: DEFAULT_LANGUAGE = _DEFAULT - def get_layout(self) -> Type[BaseLayout]: + def get_layout(self) -> type[BaseLayout]: return _LAYOUTS_CACHE[self.meta.layout] @property diff --git a/guide/webapp/display/page/renderer.py b/guide/webapp/display/page/renderer.py index 3040ba9f..370a4c8c 100644 --- a/guide/webapp/display/page/renderer.py +++ b/guide/webapp/display/page/renderer.py @@ -1,13 +1,12 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Type - -from webapp.display.base import BaseRenderer from html5tagger import HTML, Builder # type: ignore from sanic import Request +from webapp.display.base import BaseRenderer + from ..layouts.base import BaseLayout from .page import Page @@ -21,13 +20,9 @@ class PageRenderer(BaseRenderer): self._body(request, builder, language, path) return builder - def _body( - self, request: Request, builder: Builder, language: str, path: str - ): + def _body(self, request: Request, builder: Builder, language: str, path: str): prev_page, current_page, next_page = Page.get(language, path) - request.ctx.language = ( - Page.DEFAULT_LANGUAGE if language == "api" else language - ) + request.ctx.language = Page.DEFAULT_LANGUAGE if language == "api" else language request.ctx.current_page = current_page request.ctx.previous_page = prev_page request.ctx.next_page = next_page @@ -39,9 +34,7 @@ class PageRenderer(BaseRenderer): @contextmanager def _base(self, request: Request, builder: Builder, page: Page | None): - layout_type: Type[BaseLayout] = ( - page.get_layout() if page else BaseLayout - ) + layout_type: type[BaseLayout] = page.get_layout() if page else BaseLayout layout = layout_type(builder) with layout(request, builder.full): yield diff --git a/guide/webapp/display/plugins/attrs.py b/guide/webapp/display/plugins/attrs.py index 7117bd0f..d605ba41 100644 --- a/guide/webapp/display/plugins/attrs.py +++ b/guide/webapp/display/plugins/attrs.py @@ -2,12 +2,11 @@ from re import Match from textwrap import dedent from typing import Any +from html5tagger import HTML, E from mistune.block_parser import BlockParser from mistune.core import BlockState from mistune.directives import DirectivePlugin -from html5tagger import HTML, E - class Attributes(DirectivePlugin): def __call__(self, directive, md): @@ -16,9 +15,7 @@ class Attributes(DirectivePlugin): if md.renderer.NAME == "html": md.renderer.register("attrs", self._render) - def parse( - self, block: BlockParser, m: Match, state: BlockState - ) -> dict[str, Any]: + def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: info = m.groupdict() options = dict(self.parse_options(m)) new_state = block.state_cls() diff --git a/guide/webapp/display/plugins/columns.py b/guide/webapp/display/plugins/columns.py index 4ab7feab..7e448e8e 100644 --- a/guide/webapp/display/plugins/columns.py +++ b/guide/webapp/display/plugins/columns.py @@ -10,9 +10,7 @@ from mistune.markdown import Markdown class Column(DirectivePlugin): - def parse( - self, block: BlockParser, m: Match, state: BlockState - ) -> dict[str, Any]: + def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: info = m.groupdict() new_state = block.state_cls() @@ -36,9 +34,7 @@ class Column(DirectivePlugin): def _render_column(self, renderer: HTMLRenderer, text: str, **attrs): start = ( - '
\n' - if attrs.get("first") - else "" + '
\n' if attrs.get("first") else "" ) end = "
\n" if attrs.get("last") else "" col = f'
{text}
\n' diff --git a/guide/webapp/display/plugins/hook.py b/guide/webapp/display/plugins/hook.py index db1b647a..e0ff1295 100644 --- a/guide/webapp/display/plugins/hook.py +++ b/guide/webapp/display/plugins/hook.py @@ -16,16 +16,12 @@ class Hook(DirectivePlugin): for type_ in ("column", "tab"): if token["type"] == type_: maybe_next = ( - state.tokens[idx + 1] - if idx + 1 < len(state.tokens) - else None + state.tokens[idx + 1] if idx + 1 < len(state.tokens) else None ) token.setdefault("attrs", {}) if prev and prev["type"] != type_: token["attrs"]["first"] = True - if ( - maybe_next and maybe_next["type"] != type_ - ) or not maybe_next: + if (maybe_next and maybe_next["type"] != type_) or not maybe_next: token["attrs"]["last"] = True prev = token diff --git a/guide/webapp/display/plugins/mermaid.py b/guide/webapp/display/plugins/mermaid.py index 83fa9189..b814b812 100644 --- a/guide/webapp/display/plugins/mermaid.py +++ b/guide/webapp/display/plugins/mermaid.py @@ -3,19 +3,16 @@ from re import Match from textwrap import dedent from typing import Any +from html5tagger import HTML, E from mistune import HTMLRenderer from mistune.block_parser import BlockParser from mistune.core import BlockState from mistune.directives import DirectivePlugin, RSTDirective from mistune.markdown import Markdown -from html5tagger import HTML, E - class Mermaid(DirectivePlugin): - def parse( - self, block: BlockParser, m: Match, state: BlockState - ) -> dict[str, Any]: + def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: info = m.groupdict() new_state = block.state_cls() diff --git a/guide/webapp/display/plugins/notification.py b/guide/webapp/display/plugins/notification.py index 911b4c31..aaf8a338 100644 --- a/guide/webapp/display/plugins/notification.py +++ b/guide/webapp/display/plugins/notification.py @@ -1,6 +1,5 @@ -from mistune.directives import Admonition - from html5tagger import HTML, E +from mistune.directives import Admonition class Notification(Admonition): @@ -20,12 +19,8 @@ class Notification(Admonition): if md.renderer.NAME == "html": md.renderer.register("admonition", self._render_admonition) - md.renderer.register( - "admonition_title", self._render_admonition_title - ) - md.renderer.register( - "admonition_content", self._render_admonition_content - ) + md.renderer.register("admonition_title", self._render_admonition_title) + md.renderer.register("admonition_content", self._render_admonition_content) def _render_admonition(self, _, text, name, **attrs) -> str: return str( diff --git a/guide/webapp/display/plugins/tabs.py b/guide/webapp/display/plugins/tabs.py index b99e70db..882254e9 100644 --- a/guide/webapp/display/plugins/tabs.py +++ b/guide/webapp/display/plugins/tabs.py @@ -10,9 +10,7 @@ from mistune.markdown import Markdown class Tabs(DirectivePlugin): - def parse( - self, block: BlockParser, m: Match, state: BlockState - ) -> dict[str, Any]: + def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]: info = m.groupdict() new_state = block.state_cls() @@ -41,9 +39,7 @@ class Tabs(DirectivePlugin): def _render_tab(self, renderer: HTMLRenderer, text: str, **attrs): start = '
    \n' if attrs.get("first") else "" end = ( - '
\n' - if attrs.get("last") - else "" + '
\n' if attrs.get("last") else "" ) content = f'
{text}
\n' tab = f'
  • {attrs["title"]}{content}
  • \n' diff --git a/guide/webapp/display/search/renderer.py b/guide/webapp/display/search/renderer.py index fb06aff1..284fd5bd 100644 --- a/guide/webapp/display/search/renderer.py +++ b/guide/webapp/display/search/renderer.py @@ -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 diff --git a/guide/webapp/display/search/search.py b/guide/webapp/display/search/search.py index cede028e..d84ba79b 100644 --- a/guide/webapp/display/search/search.py +++ b/guide/webapp/display/search/search.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import ClassVar from msgspec import Struct + from webapp.display.page import Page @@ -91,9 +92,7 @@ def _inverse_document_frequency(docs: list[Document]) -> dict[str, float]: return {word: num_docs / count for word, count in word_count.items()} -def _tf_idf_vector( - document: Document, idf: dict[str, float] -) -> dict[str, float]: +def _tf_idf_vector(document: Document, idf: dict[str, float]) -> dict[str, float]: """Calculate the TF-IDF vector for a document.""" return { word: tf * idf[word] @@ -102,9 +101,7 @@ def _tf_idf_vector( } -def _cosine_similarity( - vec1: dict[str, float], vec2: dict[str, float] -) -> float: +def _cosine_similarity(vec1: dict[str, float], vec2: dict[str, float]) -> float: """Calculate the cosine similarity between two vectors.""" if not vec1 or not vec2: return 0.0 @@ -126,9 +123,7 @@ def _search( tf_idf_query = _tf_idf_vector( Document(page=dummy_page, language=language).process(stemmer), idf ) - similarities = [ - _cosine_similarity(tf_idf_query, vector) for vector in vectors - ] + similarities = [_cosine_similarity(tf_idf_query, vector) for vector in vectors] return [ (similarity, document) for similarity, document in sorted( @@ -155,16 +150,13 @@ class Searcher: } self._vectors = { language: [ - _tf_idf_vector(document, self._idf[language]) - for document in documents + _tf_idf_vector(document, self._idf[language]) for document in documents ] for language, documents in self._documents.items() } self._stemmer = stemmer - def search( - self, query: str, language: str - ) -> list[tuple[float, Document]]: + def search(self, query: str, language: str) -> list[tuple[float, Document]]: return _search( query, language, diff --git a/guide/webapp/endpoint/search.py b/guide/webapp/endpoint/search.py index 1b962d45..030ed24d 100644 --- a/guide/webapp/endpoint/search.py +++ b/guide/webapp/endpoint/search.py @@ -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="//search") diff --git a/guide/webapp/worker/config.py b/guide/webapp/worker/config.py index b6166094..f95a0d91 100644 --- a/guide/webapp/worker/config.py +++ b/guide/webapp/worker/config.py @@ -1,6 +1,7 @@ from pathlib import Path from msgspec import yaml + from webapp.display.layouts.models import GeneralConfig, MenuItem diff --git a/guide/webapp/worker/factory.py b/guide/webapp/worker/factory.py index dc983f09..80c98437 100644 --- a/guide/webapp/worker/factory.py +++ b/guide/webapp/worker/factory.py @@ -1,5 +1,7 @@ from pathlib import Path +from sanic import Request, Sanic, html, redirect + from webapp.display.layouts.models import MenuItem from webapp.display.page import Page, PageRenderer from webapp.endpoint.view import bp @@ -7,8 +9,6 @@ from webapp.worker.config import load_config, load_menu from webapp.worker.reload import setup_livereload from webapp.worker.style import setup_style -from sanic import Request, Sanic, html, redirect - def _compile_sidebar_order(items: list[MenuItem]) -> list[str]: order = [] @@ -28,13 +28,9 @@ def create_app(root: Path) -> Sanic: app.config.STYLE_DIR = root / "style" app.config.NODE_MODULES_DIR = root / "node_modules" app.config.LANGUAGES = ["en"] - app.config.SIDEBAR = load_menu( - app.config.CONFIG_DIR / "en" / "sidebar.yaml" - ) + app.config.SIDEBAR = load_menu(app.config.CONFIG_DIR / "en" / "sidebar.yaml") app.config.NAVBAR = load_menu(app.config.CONFIG_DIR / "en" / "navbar.yaml") - app.config.GENERAL = load_config( - app.config.CONFIG_DIR / "en" / "general.yaml" - ) + app.config.GENERAL = load_config(app.config.CONFIG_DIR / "en" / "general.yaml") setup_livereload(app) setup_style(app) @@ -66,8 +62,6 @@ def create_app(root: Path) -> Sanic: @app.on_request async def set_language(request: Request): - request.ctx.language = request.match_info.get( - "language", Page.DEFAULT_LANGUAGE - ) + request.ctx.language = request.match_info.get("language", Page.DEFAULT_LANGUAGE) return app diff --git a/guide/webapp/worker/reload.py b/guide/webapp/worker/reload.py index 3269a12d..8efa7e8a 100644 --- a/guide/webapp/worker/reload.py +++ b/guide/webapp/worker/reload.py @@ -5,7 +5,6 @@ from queue import Empty, Queue from typing import Any import ujson - from sanic import Request, Sanic, Websocket @@ -54,16 +53,12 @@ class Livereload: "serverName": SERVER_NAME, } - def __init__( - self, reload_queue: Queue, debug: bool, state: dict[str, Any] - ): + def __init__(self, reload_queue: Queue, debug: bool, state: dict[str, Any]): self.reload_queue = reload_queue self.app = Sanic(self.SERVER_NAME) self.debug = debug self.state = state - self.app.static( - "/livereload.js", Path(__file__).parent / "livereload.js" - ) + self.app.static("/livereload.js", Path(__file__).parent / "livereload.js") self.app.add_websocket_route( self.livereload_handler, "/livereload", name="livereload" ) @@ -109,7 +104,5 @@ class Livereload: break -def _run_reload_server( - reload_queue: Queue, debug: bool, state: dict[str, Any] -): +def _run_reload_server(reload_queue: Queue, debug: bool, state: dict[str, Any]): Livereload(reload_queue, debug, state).run() diff --git a/guide/webapp/worker/style.py b/guide/webapp/worker/style.py index 5b1f3fff..01d6d92a 100644 --- a/guide/webapp/worker/style.py +++ b/guide/webapp/worker/style.py @@ -1,10 +1,10 @@ # from scss.compiler import compile_string from pygments.formatters import html -from sass import compile as compile_scss -from webapp.display.code_style import SanicCodeStyle - from sanic import Sanic +from sass import compile as compile_scss + +from webapp.display.code_style import SanicCodeStyle def setup_style(app: Sanic) -> None: diff --git a/pyproject.toml b/pyproject.toml index bc12d5c1..23b7a48b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,20 +2,28 @@ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" -[tool.black] -line-length = 79 +[tool.ruff] +extend-select = ["I", "W", "UP", "C4", "ISC"] +# Worth selecting but still too broken: ASYNC, S, B, DTZ, FA +ignore = [ + "D100", + "D101", + "D102", + "D103", + "E402", + "E741", + "F811", + "F821", + # ruff format complains about these: + "ISC001", + "W191", +] +show-source = true +show-fixes = true -[tool.isort] -atomic = true -default_section = "THIRDPARTY" -include_trailing_comma = true -known_first_party = "sanic" -known_third_party = "pytest" -line_length = 79 -lines_after_imports = 2 -lines_between_types = 1 -multi_line_output = 3 -profile = "black" +[tool.ruff.isort] +known-first-party = ["sanic"] +known-third-party = ["pytest"] [[tool.mypy.overrides]] module = [ diff --git a/sanic/__init__.py b/sanic/__init__.py index 8438ded4..9c0583ec 100644 --- a/sanic/__init__.py +++ b/sanic/__init__.py @@ -36,7 +36,6 @@ from sanic.response import ( ) from sanic.server.websockets.impl import WebsocketImplProtocol as Websocket - DefaultSanic: TypeAlias = "Sanic[Config, SimpleNamespace]" """ A type alias for a Sanic app with a default config and namespace. diff --git a/sanic/__main__.py b/sanic/__main__.py index 7db13563..bc14db89 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -1,7 +1,6 @@ from sanic.cli.app import SanicCLI from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support - if OS_IS_WINDOWS: enable_windows_color_support() diff --git a/sanic/app.py b/sanic/app.py index 452954ca..2c114557 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -5,7 +5,6 @@ import logging import logging.config import re import sys - from asyncio import ( AbstractEventLoop, CancelledError, @@ -32,19 +31,12 @@ from typing import ( Callable, ClassVar, Coroutine, - Deque, - Dict, Generic, Iterable, Iterator, - List, Literal, - Optional, - Set, - Tuple, Type, TypeVar, - Union, cast, overload, ) @@ -96,7 +88,6 @@ from sanic.worker.inspector import Inspector from sanic.worker.loader import CertLoader from sanic.worker.manager import WorkerManager - if TYPE_CHECKING: try: from sanic_ext import Extend # type: ignore @@ -173,7 +164,7 @@ class Sanic( "websocket_tasks", ) - _app_registry: ClassVar[Dict[str, "Sanic"]] = {} + _app_registry: ClassVar[dict[str, Sanic]] = {} test_mode: ClassVar[bool] = False @overload @@ -182,19 +173,19 @@ class Sanic( name: str, config: None = None, ctx: None = None, - router: Optional[Router] = None, - signal_router: Optional[SignalRouter] = None, - error_handler: Optional[ErrorHandler] = None, - env_prefix: Optional[str] = SANIC_PREFIX, - request_class: Optional[Type[Request]] = None, + router: Router | None = None, + signal_router: SignalRouter | None = None, + error_handler: ErrorHandler | None = None, + env_prefix: str | None = SANIC_PREFIX, + request_class: type[Request] | None = None, strict_slashes: bool = False, - log_config: Optional[Dict[str, Any]] = None, + log_config: dict[str, Any] | None = None, configure_logging: bool = True, - dumps: Optional[Callable[..., AnyStr]] = None, - loads: Optional[Callable[..., Any]] = None, + dumps: Callable[..., AnyStr] | None = None, + loads: Callable[..., Any] | None = None, inspector: bool = False, - inspector_class: Optional[Type[Inspector]] = None, - certloader_class: Optional[Type[CertLoader]] = None, + inspector_class: type[Inspector] | None = None, + certloader_class: type[CertLoader] | None = None, ) -> None: ... @@ -202,21 +193,21 @@ class Sanic( def __init__( self: Sanic[config_type, SimpleNamespace], name: str, - config: Optional[config_type] = None, + config: config_type | None = None, ctx: None = None, - router: Optional[Router] = None, - signal_router: Optional[SignalRouter] = None, - error_handler: Optional[ErrorHandler] = None, - env_prefix: Optional[str] = SANIC_PREFIX, - request_class: Optional[Type[Request]] = None, + router: Router | None = None, + signal_router: SignalRouter | None = None, + error_handler: ErrorHandler | None = None, + env_prefix: str | None = SANIC_PREFIX, + request_class: type[Request] | None = None, strict_slashes: bool = False, - log_config: Optional[Dict[str, Any]] = None, + log_config: dict[str, Any] | None = None, configure_logging: bool = True, - dumps: Optional[Callable[..., AnyStr]] = None, - loads: Optional[Callable[..., Any]] = None, + dumps: Callable[..., AnyStr] | None = None, + loads: Callable[..., Any] | None = None, inspector: bool = False, - inspector_class: Optional[Type[Inspector]] = None, - certloader_class: Optional[Type[CertLoader]] = None, + inspector_class: type[Inspector] | None = None, + certloader_class: type[CertLoader] | None = None, ) -> None: ... @@ -225,20 +216,20 @@ class Sanic( self: Sanic[Config, ctx_type], name: str, config: None = None, - ctx: Optional[ctx_type] = None, - router: Optional[Router] = None, - signal_router: Optional[SignalRouter] = None, - error_handler: Optional[ErrorHandler] = None, - env_prefix: Optional[str] = SANIC_PREFIX, - request_class: Optional[Type[Request]] = None, + ctx: ctx_type | None = None, + router: Router | None = None, + signal_router: SignalRouter | None = None, + error_handler: ErrorHandler | None = None, + env_prefix: str | None = SANIC_PREFIX, + request_class: type[Request] | None = None, strict_slashes: bool = False, - log_config: Optional[Dict[str, Any]] = None, + log_config: dict[str, Any] | None = None, configure_logging: bool = True, - dumps: Optional[Callable[..., AnyStr]] = None, - loads: Optional[Callable[..., Any]] = None, + dumps: Callable[..., AnyStr] | None = None, + loads: Callable[..., Any] | None = None, inspector: bool = False, - inspector_class: Optional[Type[Inspector]] = None, - certloader_class: Optional[Type[CertLoader]] = None, + inspector_class: type[Inspector] | None = None, + certloader_class: type[CertLoader] | None = None, ) -> None: ... @@ -246,42 +237,42 @@ class Sanic( def __init__( self: Sanic[config_type, ctx_type], name: str, - config: Optional[config_type] = None, - ctx: Optional[ctx_type] = None, - router: Optional[Router] = None, - signal_router: Optional[SignalRouter] = None, - error_handler: Optional[ErrorHandler] = None, - env_prefix: Optional[str] = SANIC_PREFIX, - request_class: Optional[Type[Request]] = None, + config: config_type | None = None, + ctx: ctx_type | None = None, + router: Router | None = None, + signal_router: SignalRouter | None = None, + error_handler: ErrorHandler | None = None, + env_prefix: str | None = SANIC_PREFIX, + request_class: type[Request] | None = None, strict_slashes: bool = False, - log_config: Optional[Dict[str, Any]] = None, + log_config: dict[str, Any] | None = None, configure_logging: bool = True, - dumps: Optional[Callable[..., AnyStr]] = None, - loads: Optional[Callable[..., Any]] = None, + dumps: Callable[..., AnyStr] | None = None, + loads: Callable[..., Any] | None = None, inspector: bool = False, - inspector_class: Optional[Type[Inspector]] = None, - certloader_class: Optional[Type[CertLoader]] = None, + inspector_class: type[Inspector] | None = None, + certloader_class: type[CertLoader] | None = None, ) -> None: ... def __init__( self, name: str, - config: Optional[config_type] = None, - ctx: Optional[ctx_type] = None, - router: Optional[Router] = None, - signal_router: Optional[SignalRouter] = None, - error_handler: Optional[ErrorHandler] = None, - env_prefix: Optional[str] = SANIC_PREFIX, - request_class: Optional[Type[Request]] = None, + config: config_type | None = None, + ctx: ctx_type | None = None, + router: Router | None = None, + signal_router: SignalRouter | None = None, + error_handler: ErrorHandler | None = None, + env_prefix: str | None = SANIC_PREFIX, + request_class: type[Request] | None = None, strict_slashes: bool = False, - log_config: Optional[Dict[str, Any]] = None, + log_config: dict[str, Any] | None = None, configure_logging: bool = True, - dumps: Optional[Callable[..., AnyStr]] = None, - loads: Optional[Callable[..., Any]] = None, + dumps: Callable[..., AnyStr] | None = None, + loads: Callable[..., Any] | None = None, inspector: bool = False, - inspector_class: Optional[Type[Inspector]] = None, - certloader_class: Optional[Type[CertLoader]] = None, + inspector_class: type[Inspector] | None = None, + certloader_class: type[CertLoader] | None = None, ) -> None: super().__init__(name=name) # logging @@ -303,41 +294,39 @@ class Sanic( self.config.INSPECTOR = inspector # Then we can do the rest - self._asgi_app: Optional[ASGIApp] = None - self._asgi_lifespan: Optional[Lifespan] = None + self._asgi_app: ASGIApp | None = None + self._asgi_lifespan: Lifespan | None = None self._asgi_client: Any = None - self._blueprint_order: List[Blueprint] = [] - self._delayed_tasks: List[str] = [] + self._blueprint_order: list[Blueprint] = [] + self._delayed_tasks: list[str] = [] self._future_registry: FutureRegistry = FutureRegistry() - self._inspector: Optional[Inspector] = None - self._manager: Optional[WorkerManager] = None + self._inspector: Inspector | None = None + self._manager: WorkerManager | None = None self._state: ApplicationState = ApplicationState(app=self) - self._task_registry: Dict[str, Union[Task, None]] = {} + self._task_registry: dict[str, Task | None] = {} self._test_client: Any = None self._test_manager: Any = None self.asgi = False self.auto_reload = False - self.blueprints: Dict[str, Blueprint] = {} - self.certloader_class: Type[CertLoader] = ( - certloader_class or CertLoader - ) + self.blueprints: dict[str, Blueprint] = {} + self.certloader_class: type[CertLoader] = certloader_class or CertLoader self.configure_logging: bool = configure_logging self.ctx: ctx_type = cast(ctx_type, ctx or SimpleNamespace()) self.error_handler: ErrorHandler = error_handler or ErrorHandler() - self.inspector_class: Type[Inspector] = inspector_class or Inspector - self.listeners: Dict[str, List[ListenerType[Any]]] = defaultdict(list) - self.named_request_middleware: Dict[str, Deque[Middleware]] = {} - self.named_response_middleware: Dict[str, Deque[Middleware]] = {} - self.request_class: Type[Request] = request_class or Request - self.request_middleware: Deque[Middleware] = deque() - self.response_middleware: Deque[Middleware] = deque() + self.inspector_class: type[Inspector] = inspector_class or Inspector + self.listeners: dict[str, list[ListenerType[Any]]] = defaultdict(list) + self.named_request_middleware: dict[str, deque[Middleware]] = {} + self.named_response_middleware: dict[str, deque[Middleware]] = {} + self.request_class: type[Request] = request_class or Request + self.request_middleware: deque[Middleware] = deque() + self.response_middleware: deque[Middleware] = deque() self.router: Router = router or Router() self.shared_ctx: SharedContext = SharedContext() self.signal_router: SignalRouter = signal_router or SignalRouter() - self.sock: Optional[socket] = None + self.sock: socket | None = None self.strict_slashes: bool = strict_slashes self.websocket_enabled: bool = False - self.websocket_tasks: Set[Future[Any]] = set() + self.websocket_tasks: set[Future[Any]] = set() # Register alternative method names self.go_fast = self.run @@ -396,15 +385,11 @@ class Sanic( try: _event = ListenerEvent[event.upper()] except (ValueError, AttributeError): - valid = ", ".join( - map(lambda x: x.lower(), ListenerEvent.__members__.keys()) - ) + valid = ", ".join(x.lower() for x in ListenerEvent.__members__.keys()) raise BadRequest(f"Invalid event: {event}. Use one of: {valid}") if "." in _event: - self.signal(_event.value)( - partial(self._listener, listener=listener) - ) + self.signal(_event.value)(partial(self._listener, listener=listener)) else: self.listeners[_event.value].append(listener) @@ -412,11 +397,11 @@ class Sanic( def register_middleware( self, - middleware: Union[MiddlewareType, Middleware], + middleware: MiddlewareType | Middleware, attach_to: str = "request", *, - priority: Union[Default, int] = _default, - ) -> Union[MiddlewareType, Middleware]: + priority: Default | int = _default, + ) -> MiddlewareType | Middleware: """Register a middleware to be called before a request is handled. Args: @@ -461,7 +446,7 @@ class Sanic( route_names: Iterable[str], attach_to: str = "request", *, - priority: Union[Default, int] = _default, + priority: Default | int = _default, ): """Used to register named middleqare (middleware typically on blueprints) @@ -512,7 +497,7 @@ class Sanic( def _apply_exception_handler( self, handler: FutureException, - route_names: Optional[List[str]] = None, + route_names: list[str] | None = None, ): """Decorate a function to be registered as a handler for exceptions @@ -531,9 +516,7 @@ class Sanic( def _apply_listener(self, listener: FutureListener): return self.register_listener(listener.listener, listener.event) - def _apply_route( - self, route: FutureRoute, overwrite: bool = False - ) -> List[Route]: + def _apply_route(self, route: FutureRoute, overwrite: bool = False) -> list[Route]: params = route._asdict() params["overwrite"] = overwrite websocket = params.pop("websocket", False) @@ -567,7 +550,7 @@ class Sanic( def _apply_middleware( self, middleware: FutureMiddleware, - route_names: Optional[List[str]] = None, + route_names: list[str] | None = None, ): with self.amend(): if route_names: @@ -588,8 +571,8 @@ class Sanic( self, event: str, *, - condition: Optional[Dict[str, str]] = None, - context: Optional[Dict[str, Any]] = None, + condition: dict[str, str] | None = None, + context: dict[str, Any] | None = None, fail_not_found: bool = True, inline: Literal[True], reverse: bool = False, @@ -601,8 +584,8 @@ class Sanic( self, event: str, *, - condition: Optional[Dict[str, str]] = None, - context: Optional[Dict[str, Any]] = None, + condition: dict[str, str] | None = None, + context: dict[str, Any] | None = None, fail_not_found: bool = True, inline: Literal[False] = False, reverse: bool = False, @@ -613,12 +596,12 @@ class Sanic( self, event: str, *, - condition: Optional[Dict[str, str]] = None, - context: Optional[Dict[str, Any]] = None, + condition: dict[str, str] | None = None, + context: dict[str, Any] | None = None, fail_not_found: bool = True, inline: bool = False, reverse: bool = False, - ) -> Coroutine[Any, Any, Awaitable[Union[Task, Any]]]: + ) -> Coroutine[Any, Any, Awaitable[Task | Any]]: """Dispatches an event to the signal router. Args: @@ -662,9 +645,7 @@ class Sanic( fail_not_found=fail_not_found, ) - async def event( - self, event: str, timeout: Optional[Union[int, float]] = None - ) -> None: + async def event(self, event: str, timeout: int | float | None = None) -> None: """Wait for a specific event to be triggered. This method waits for a named event to be triggered and can be used @@ -749,9 +730,7 @@ class Sanic( async def report(exception: Exception) -> None: await handler(self, exception) - self.add_signal( - handler=report, event=Event.SERVER_EXCEPTION_REPORT.value - ) + self.add_signal(handler=report, event=Event.SERVER_EXCEPTION_REPORT.value) return report @@ -780,13 +759,13 @@ class Sanic( def blueprint( self, - blueprint: Union[Blueprint, Iterable[Blueprint], BlueprintGroup], + blueprint: Blueprint | (Iterable[Blueprint] | BlueprintGroup), *, - url_prefix: Optional[str] = None, - version: Optional[Union[int, float, str]] = None, - strict_slashes: Optional[bool] = None, - version_prefix: Optional[str] = None, - name_prefix: Optional[str] = None, + url_prefix: str | None = None, + version: int | float | str | None = None, + strict_slashes: bool | None = None, + version_prefix: str | None = None, + name_prefix: str | None = None, ) -> None: """Register a blueprint on the application. @@ -812,7 +791,7 @@ class Sanic( app.blueprint(bp, url_prefix='/blueprint') ``` """ # noqa: E501 - options: Dict[str, Any] = {} + options: dict[str, Any] = {} if url_prefix is not None: options["url_prefix"] = url_prefix if version is not None: @@ -825,7 +804,7 @@ class Sanic( options["name_prefix"] = name_prefix if isinstance(blueprint, (Iterable, BlueprintGroup)): for item in blueprint: - params: Dict[str, Any] = {**options} + params: dict[str, Any] = {**options} if isinstance(blueprint, BlueprintGroup): merge_from = [ options.get("url_prefix", ""), @@ -840,14 +819,12 @@ class Sanic( for _attr in ["version", "strict_slashes"]: if getattr(item, _attr) is None: - params[_attr] = getattr( - blueprint, _attr - ) or options.get(_attr) + params[_attr] = getattr(blueprint, _attr) or options.get( + _attr + ) if item.version_prefix == "/v": if blueprint.version_prefix == "/v": - params["version_prefix"] = options.get( - "version_prefix" - ) + params["version_prefix"] = options.get("version_prefix") else: params["version_prefix"] = blueprint.version_prefix name_prefix = getattr(blueprint, "name_prefix", None) @@ -857,17 +834,14 @@ class Sanic( return if blueprint.name in self.blueprints: assert self.blueprints[blueprint.name] is blueprint, ( - 'A blueprint with the name "%s" is already registered. ' - "Blueprint names must be unique." % (blueprint.name,) + f'A blueprint with the name "{blueprint.name}" is already registered. ' + "Blueprint names must be unique." ) else: self.blueprints[blueprint.name] = blueprint self._blueprint_order.append(blueprint) - if ( - self.strict_slashes is not None - and blueprint.strict_slashes is None - ): + if self.strict_slashes is not None and blueprint.strict_slashes is None: blueprint.strict_slashes = self.strict_slashes blueprint.register(self, options) @@ -923,7 +897,7 @@ class Sanic( # http://subdomain.example.com/view-name """ # noqa: E501 # find the route by the supplied view name - kw: Dict[str, str] = {} + kw: dict[str, str] = {} # special static files url_for if "." not in view_name: @@ -937,9 +911,7 @@ class Sanic( route = self.router.find_route_by_view_name(view_name, **kw) if not route: - raise URLBuildError( - f"Endpoint with name `{view_name}` was not found" - ) + raise URLBuildError(f"Endpoint with name `{view_name}` was not found") uri = route.path @@ -978,9 +950,7 @@ class Sanic( scheme = kwargs.pop("_scheme", "") if route.extra.hosts and external: if not host and len(route.extra.hosts) > 1: - raise ValueError( - f"Host is ambiguous: {', '.join(route.extra.hosts)}" - ) + raise ValueError(f"Host is ambiguous: {', '.join(route.extra.hosts)}") elif host and host not in route.extra.hosts: raise ValueError( f"Requested host ({host}) is not available for this " @@ -1096,10 +1066,7 @@ class Sanic( context={"request": request, "exception": exception}, ) - if ( - request.stream is not None - and request.stream.stage is not Stage.HANDLER - ): + if request.stream is not None and request.stream.stage is not Stage.HANDLER: error_logger.exception(exception, exc_info=True) logger.error( "The error response will not be sent to the client for " @@ -1146,10 +1113,7 @@ class Sanic( response = self.error_handler.default(request, e) elif self.debug: response = HTTPResponse( - ( - f"Error while handling error: {e}\n" - f"Stack: {format_exc()}" - ), + (f"Error while handling error: {e}\n" f"Stack: {format_exc()}"), status=500, ) else: @@ -1194,9 +1158,7 @@ class Sanic( ) await response.eof() else: - raise ServerError( - f"Invalid response type {response!r} (need HTTPResponse)" - ) + raise ServerError(f"Invalid response type {response!r} (need HTTPResponse)") async def handle_request(self, request: Request) -> None: # no cov """Handles a request by dispatching it to the appropriate handler. @@ -1221,13 +1183,11 @@ class Sanic( # Define `response` var here to remove warnings about # allocation before assignment below. - response: Optional[ - Union[ - BaseHTTPResponse, - Coroutine[Any, Any, Optional[BaseHTTPResponse]], - ResponseStream, - ] - ] = None + response: ( + BaseHTTPResponse + | (Coroutine[Any, Any, BaseHTTPResponse | None] | ResponseStream) + | None + ) = None run_middleware = True try: await self.dispatch( @@ -1285,10 +1245,8 @@ class Sanic( if handler is None: raise ServerError( - ( - "'None' was returned while requesting a " - "handler from the router" - ) + "'None' was returned while requesting a " + "handler from the router" ) # Run response handler @@ -1347,17 +1305,14 @@ class Sanic( else: if not hasattr(handler, "is_websocket"): raise ServerError( - f"Invalid response type {response!r} " - "(need HTTPResponse)" + f"Invalid response type {response!r} " "(need HTTPResponse)" ) except CancelledError: # type: ignore raise except Exception as e: # Response Generation Failed - await self.handle_exception( - request, e, run_middleware=run_middleware - ) + await self.handle_exception(request, e, run_middleware=run_middleware) async def _websocket_handler( self, handler, request, *args, subprotocols=None, **kwargs @@ -1436,9 +1391,7 @@ class Sanic( # Execution # -------------------------------------------------------------------- # - async def _run_request_middleware( - self, request, middleware_collection - ): # no cov + async def _run_request_middleware(self, request, middleware_collection): # no cov request._request_middleware_started = True for middleware in middleware_collection: @@ -1515,9 +1468,7 @@ class Sanic( task.cancel() @staticmethod - async def _listener( - app: Sanic, loop: AbstractEventLoop, listener: ListenerType - ): + async def _listener(app: Sanic, loop: AbstractEventLoop, listener: ListenerType): try: maybe_coro = listener(app) # type: ignore except TypeError: @@ -1546,9 +1497,7 @@ class Sanic( if isawaitable(task): await task except CancelledError: - error_logger.warning( - f"Task {task} was cancelled before it completed." - ) + error_logger.warning(f"Task {task} was cancelled before it completed.") raise except Exception as e: await app.dispatch( @@ -1566,7 +1515,7 @@ class Sanic( app, loop, *, - name: Optional[str] = None, + name: str | None = None, register: bool = True, ) -> Task: if not isinstance(task, Future): @@ -1628,11 +1577,11 @@ class Sanic( def add_task( self, - task: Union[Future[Any], Coroutine[Any, Any, Any], Awaitable[Any]], + task: Future[Any] | (Coroutine[Any, Any, Any] | Awaitable[Any]), *, - name: Optional[str] = None, + name: str | None = None, register: bool = True, - ) -> Optional[Task[Any]]: + ) -> Task[Any] | None: """Schedule a task to run later, after the loop has started. While this is somewhat similar to `asyncio.create_task`, it can be @@ -1657,18 +1606,14 @@ class Sanic( """ # noqa: E501 try: loop = self.loop # Will raise SanicError if loop is not started - return self._loop_add_task( - task, self, loop, name=name, register=register - ) + return self._loop_add_task(task, self, loop, name=name, register=register) except SanicException: task_name = f"sanic.delayed_task.{hash(task)}" if not self._delayed_tasks: self.after_server_start(partial(self.dispatch_delayed_tasks)) if name: - raise RuntimeError( - "Cannot name task outside of a running application" - ) + raise RuntimeError("Cannot name task outside of a running application") self.signal(task_name)(partial(self.run_delayed_task, task=task)) self._delayed_tasks.append(task_name) @@ -1679,18 +1624,14 @@ class Sanic( ... @overload - def get_task( - self, name: str, *, raise_exception: Literal[False] - ) -> Optional[Task]: + def get_task(self, name: str, *, raise_exception: Literal[False]) -> Task | None: ... @overload - def get_task(self, name: str, *, raise_exception: bool) -> Optional[Task]: + def get_task(self, name: str, *, raise_exception: bool) -> Task | None: ... - def get_task( - self, name: str, *, raise_exception: bool = True - ) -> Optional[Task]: + def get_task(self, name: str, *, raise_exception: bool = True) -> Task | None: """Get a named task. This method is used to get a task by its name. Optionally, you can @@ -1708,15 +1649,13 @@ class Sanic( return self._task_registry[name] except KeyError: if raise_exception: - raise SanicException( - f'Registered task named "{name}" not found.' - ) + raise SanicException(f'Registered task named "{name}" not found.') return None async def cancel_task( self, name: str, - msg: Optional[str] = None, + msg: str | None = None, *, raise_exception: bool = True, ) -> None: @@ -1751,7 +1690,7 @@ class Sanic( """ # noqa: E501 task = self.get_task(name, raise_exception=raise_exception) if task and not task.cancelled(): - args: Tuple[str, ...] = () + args: tuple[str, ...] = () if msg: if sys.version_info >= (3, 9): args = (msg,) @@ -1784,7 +1723,7 @@ class Sanic( } def shutdown_tasks( - self, timeout: Optional[float] = None, increment: float = 0.1 + self, timeout: float | None = None, increment: float = 0.1 ) -> None: """Cancel all tasks except the server task. @@ -1822,11 +1761,7 @@ class Sanic( Iterable[Task[Any]]: The tasks that are currently registered with the application. """ - return ( - task - for task in iter(self._task_registry.values()) - if task is not None - ) + return (task for task in iter(self._task_registry.values()) if task is not None) # -------------------------------------------------------------------- # # ASGI @@ -1853,7 +1788,7 @@ class Sanic( # Configuration # -------------------------------------------------------------------- # - def update_config(self, config: Union[bytes, str, dict, Any]) -> None: + def update_config(self, config: Any) -> None: """Update the application configuration. This method is used to update the application configuration. It can @@ -1863,7 +1798,7 @@ class Sanic( See [Configuration](/en/guide/deployment/configuration) for details. Args: - config (Union[bytes, str, dict, Any]): The configuration object, + config (bytes | str | dict | Any): The configuration object, dictionary, or path to a configuration file. """ @@ -1903,7 +1838,7 @@ class Sanic( return self._state @property - def reload_dirs(self) -> Set[Path]: + def reload_dirs(self) -> set[Path]: """The directories that are monitored for auto-reload. Returns: @@ -1948,9 +1883,9 @@ class Sanic( def extend( self, *, - extensions: Optional[List[Type[Extension]]] = None, + extensions: list[type[Extension]] | None = None, built_in_extensions: bool = True, - config: Optional[Union[Config, Dict[str, Any]]] = None, + config: Config | dict[str, Any] | None = None, **kwargs, ) -> Extend: """Extend Sanic with additional functionality using Sanic Extensions. @@ -2068,9 +2003,7 @@ class Sanic( del cls._app_registry[name] @classmethod - def get_app( - cls, name: Optional[str] = None, *, force_create: bool = False - ) -> Sanic: + def get_app(cls, name: str | None = None, *, force_create: bool = False) -> Sanic: """Retrieve an instantiated Sanic instance by name. This method is best used when needing to get access to an already @@ -2277,9 +2210,7 @@ class Sanic( self.finalize() route_names = [route.extra.ident for route in self.router.routes] - duplicates = { - name for name in route_names if route_names.count(name) > 1 - } + duplicates = {name for name in route_names if route_names.count(name) > 1} if duplicates: names = ", ".join(duplicates) message = ( @@ -2316,7 +2247,7 @@ class Sanic( self, concern: str, action: str, - loop: Optional[AbstractEventLoop] = None, + loop: AbstractEventLoop | None = None, ) -> None: event = f"server.{concern}.{action}" if action not in ("before", "after") or concern not in ( @@ -2324,9 +2255,7 @@ class Sanic( "shutdown", ): raise SanicException(f"Invalid server event: {event}") - logger.debug( - f"Triggering server events: {event}", extra={"verbosity": 1} - ) + logger.debug(f"Triggering server events: {event}", extra={"verbosity": 1}) reverse = concern == "shutdown" if loop is None: loop = self.loop @@ -2347,7 +2276,7 @@ class Sanic( def refresh( self, - passthru: Optional[Dict[str, Any]] = None, + passthru: dict[str, Any] | None = None, ) -> Sanic: """Refresh the application instance. **This is used internally by Sanic**. @@ -2392,9 +2321,7 @@ class Sanic( Inspector: An instance of Inspector. """ if environ.get("SANIC_WORKER_PROCESS") or not self._inspector: - raise SanicException( - "Can only access the inspector from the main process" - ) + raise SanicException("Can only access the inspector from the main process") return self._inspector @property @@ -2427,7 +2354,5 @@ class Sanic( """ if environ.get("SANIC_WORKER_PROCESS") or not self._manager: - raise SanicException( - "Can only access the manager from the main process" - ) + raise SanicException("Can only access the manager from the main process") return self._manager diff --git a/sanic/application/ext.py b/sanic/application/ext.py index 86b482ae..515bc1bc 100644 --- a/sanic/application/ext.py +++ b/sanic/application/ext.py @@ -4,7 +4,6 @@ from contextlib import suppress from importlib import import_module from typing import TYPE_CHECKING - if TYPE_CHECKING: from sanic import Sanic diff --git a/sanic/application/logo.py b/sanic/application/logo.py index 0a2f56a2..4941f90f 100644 --- a/sanic/application/logo.py +++ b/sanic/application/logo.py @@ -1,11 +1,9 @@ import re import sys - from os import environ from sanic.helpers import is_atty - BASE_LOGO = """ Sanic @@ -63,10 +61,7 @@ def get_logo(full: bool = False, coffee: bool = False) -> str: else BASE_LOGO ) - if ( - sys.platform == "darwin" - and environ.get("TERM_PROGRAM") == "Apple_Terminal" - ): + if sys.platform == "darwin" and environ.get("TERM_PROGRAM") == "Apple_Terminal": logo = ansi_pattern.sub("", logo) return logo diff --git a/sanic/application/motd.py b/sanic/application/motd.py index 0409bd06..c618cc45 100644 --- a/sanic/application/motd.py +++ b/sanic/application/motd.py @@ -79,9 +79,7 @@ class MOTDTTY(MOTD): def set_variables(self): # no cov """Set the variables used for display.""" fallback = (108, 24) - terminal_width = max( - get_terminal_size(fallback=fallback).columns, fallback[0] - ) + terminal_width = max(get_terminal_size(fallback=fallback).columns, fallback[0]) self.max_value_width = terminal_width - fallback[0] + 36 self.key_width = 4 diff --git a/sanic/application/spinner.py b/sanic/application/spinner.py index 8bc6d6ae..e2b42af5 100644 --- a/sanic/application/spinner.py +++ b/sanic/application/spinner.py @@ -1,12 +1,10 @@ import os import sys import time - from contextlib import contextmanager from queue import Queue from threading import Thread - if os.name == "nt": # noqa import ctypes # noqa @@ -47,21 +45,16 @@ class Spinner: # noqa @staticmethod def cursor(): while True: - for cursor in "|/-\\": - yield cursor + yield from "|/-\\" @staticmethod def hide(): if os.name == "nt": ci = _CursorInfo() handle = ctypes.windll.kernel32.GetStdHandle(-11) - ctypes.windll.kernel32.GetConsoleCursorInfo( - handle, ctypes.byref(ci) - ) + ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) ci.visible = False - ctypes.windll.kernel32.SetConsoleCursorInfo( - handle, ctypes.byref(ci) - ) + ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) elif os.name == "posix": sys.stdout.write("\033[?25l") sys.stdout.flush() @@ -71,13 +64,9 @@ class Spinner: # noqa if os.name == "nt": ci = _CursorInfo() handle = ctypes.windll.kernel32.GetStdHandle(-11) - ctypes.windll.kernel32.GetConsoleCursorInfo( - handle, ctypes.byref(ci) - ) + ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) ci.visible = True - ctypes.windll.kernel32.SetConsoleCursorInfo( - handle, ctypes.byref(ci) - ) + ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) elif os.name == "posix": sys.stdout.write("\033[?25h") sys.stdout.flush() diff --git a/sanic/application/state.py b/sanic/application/state.py index 34aa6af9..186ed92e 100644 --- a/sanic/application/state.py +++ b/sanic/application/state.py @@ -1,18 +1,16 @@ from __future__ import annotations import logging - from dataclasses import dataclass, field from pathlib import Path from socket import socket from ssl import SSLContext -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union +from typing import TYPE_CHECKING, Any from sanic.application.constants import Mode, Server, ServerStage from sanic.log import VerbosityFilter, logger from sanic.server.async_server import AsyncioServer - if TYPE_CHECKING: from sanic import Sanic @@ -21,9 +19,9 @@ if TYPE_CHECKING: class ApplicationServerInfo: """Information about a server instance.""" - settings: Dict[str, Any] + settings: dict[str, Any] stage: ServerStage = field(default=ServerStage.STOPPED) - server: Optional[AsyncioServer] = field(default=None) + server: AsyncioServer | None = field(default=None) @dataclass @@ -40,11 +38,11 @@ class ApplicationState: fast: bool = field(default=False) host: str = field(default="") port: int = field(default=0) - ssl: Optional[SSLContext] = field(default=None) - sock: Optional[socket] = field(default=None) - unix: Optional[str] = field(default=None) + ssl: SSLContext | None = field(default=None) + sock: socket | None = field(default=None) + unix: str | None = field(default=None) mode: Mode = field(default=Mode.PRODUCTION) - reload_dirs: Set[Path] = field(default_factory=set) + reload_dirs: set[Path] = field(default_factory=set) auto_reload: bool = field(default=False) server: Server = field(default=Server.SANIC) is_running: bool = field(default=False) @@ -53,7 +51,7 @@ class ApplicationState: verbosity: int = field(default=0) workers: int = field(default=0) primary: bool = field(default=True) - server_info: List[ApplicationServerInfo] = field(default_factory=list) + server_info: list[ApplicationServerInfo] = field(default_factory=list) # This property relates to the ApplicationState instance and should # not be changed except in the __post_init__ method @@ -64,14 +62,12 @@ class ApplicationState: def __setattr__(self, name: str, value: Any) -> None: if self._init and name == "_init": - raise RuntimeError( - "Cannot change the value of _init after instantiation" - ) + raise RuntimeError("Cannot change the value of _init after instantiation") super().__setattr__(name, value) if self._init and hasattr(self, f"set_{name}"): getattr(self, f"set_{name}")(value) - def set_mode(self, value: Union[str, Mode]): + def set_mode(self, value: str | Mode): if hasattr(self.app, "error_handler"): self.app.error_handler.debug = self.app.debug if getattr(self.app, "configure_logging", False) and self.app.debug: @@ -107,9 +103,7 @@ class ApplicationState: if all(info.stage is ServerStage.SERVING for info in self.server_info): return ServerStage.SERVING - elif any( - info.stage is ServerStage.SERVING for info in self.server_info - ): + elif any(info.stage is ServerStage.SERVING for info in self.server_info): return ServerStage.PARTIAL return ServerStage.STOPPED diff --git a/sanic/asgi.py b/sanic/asgi.py index 43a111a3..7419eb45 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -1,8 +1,7 @@ from __future__ import annotations import warnings - -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from sanic.compat import Header from sanic.exceptions import BadRequest, ServerError @@ -15,7 +14,6 @@ from sanic.response import BaseHTTPResponse from sanic.server import ConnInfo from sanic.server.websockets.connection import WebSocketConnection - if TYPE_CHECKING: from sanic import Sanic @@ -109,9 +107,9 @@ class ASGIApp: request: Request transport: MockTransport lifespan: Lifespan - ws: Optional[WebSocketConnection] + ws: WebSocketConnection | None stage: Stage - response: Optional[BaseHTTPResponse] + response: BaseHTTPResponse | None @classmethod async def create( @@ -142,9 +140,7 @@ class ASGIApp: ] ) except UnicodeDecodeError: - raise BadRequest( - "Header names can only contain US-ASCII characters" - ) + raise BadRequest("Header names can only contain US-ASCII characters") if scope["type"] == "http": version = scope["http_version"] @@ -153,9 +149,7 @@ class ASGIApp: version = "1.1" method = "GET" - instance.ws = instance.transport.create_websocket_connection( - send, receive - ) + instance.ws = instance.transport.create_websocket_connection(send, receive) else: raise ServerError("Received unknown ASGI scope") @@ -189,7 +183,7 @@ class ASGIApp: return instance - async def read(self) -> Optional[bytes]: + async def read(self) -> bytes | None: """ Read and stream the body in chunks from an incoming ASGI message. """ diff --git a/sanic/base/root.py b/sanic/base/root.py index 3f3ba58e..4f5229c4 100644 --- a/sanic/base/root.py +++ b/sanic/base/root.py @@ -1,5 +1,4 @@ import re - from typing import Any, Optional from sanic.base.meta import SanicMeta @@ -11,7 +10,6 @@ from sanic.mixins.routes import RouteMixin from sanic.mixins.signals import SignalMixin from sanic.mixins.static import StaticMixin - VALID_NAME = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_\-]*$") @@ -26,9 +24,7 @@ class BaseSanic( ): __slots__ = ("name",) - def __init__( - self, name: Optional[str] = None, *args: Any, **kwargs: Any - ) -> None: + def __init__(self, name: Optional[str] = None, *args: Any, **kwargs: Any) -> None: class_name = self.__class__.__name__ if name is None: diff --git a/sanic/blueprint_group.py b/sanic/blueprint_group.py index b95bc520..147aa877 100644 --- a/sanic/blueprint_group.py +++ b/sanic/blueprint_group.py @@ -1,4 +1,3 @@ from .blueprints import BlueprintGroup - __all__ = ["BlueprintGroup"] # noqa: F405 diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 28199e9e..758277e3 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio import sys - from collections import defaultdict from collections.abc import MutableSequence from copy import deepcopy @@ -14,15 +13,9 @@ from typing import ( TYPE_CHECKING, Any, Callable, - Dict, Iterable, Iterator, - List, - Optional, Sequence, - Set, - Tuple, - Union, overload, ) @@ -39,7 +32,6 @@ from sanic.models.handler_types import ( RouteHandler, ) - if TYPE_CHECKING: from sanic import Sanic @@ -122,10 +114,10 @@ class Blueprint(BaseSanic): def __init__( self, name: str, - url_prefix: Optional[str] = None, - host: Optional[Union[List[str], str]] = None, - version: Optional[Union[int, str, float]] = None, - strict_slashes: Optional[bool] = None, + url_prefix: str | None = None, + host: list[str] | str | None = None, + version: int | str | float | None = None, + strict_slashes: bool | None = None, version_prefix: str = "/v", ): super().__init__(name=name) @@ -136,9 +128,7 @@ class Blueprint(BaseSanic): self.host = host self.strict_slashes = strict_slashes self.url_prefix = ( - url_prefix[:-1] - if url_prefix and url_prefix.endswith("/") - else url_prefix + url_prefix[:-1] if url_prefix and url_prefix.endswith("/") else url_prefix ) self.version = version self.version_prefix = version_prefix @@ -161,7 +151,7 @@ class Blueprint(BaseSanic): return f"Blueprint({args})" @property - def apps(self) -> Set[Sanic]: + def apps(self) -> set[Sanic]: """Get the set of apps that this blueprint is registered to. Returns: @@ -172,9 +162,7 @@ class Blueprint(BaseSanic): an app. """ if not self._apps: - raise SanicException( - f"{self} has not yet been registered to an app" - ) + raise SanicException(f"{self} has not yet been registered to an app") return self._apps @property @@ -196,23 +184,23 @@ class Blueprint(BaseSanic): def reset(self) -> None: """Reset the blueprint to its initial state.""" - self._apps: Set[Sanic] = set() + self._apps: set[Sanic] = set() self._allow_route_overwrite = False - self.exceptions: List[RouteHandler] = [] - self.listeners: Dict[str, List[ListenerType[Any]]] = {} - self.middlewares: List[MiddlewareType] = [] - self.routes: List[Route] = [] - self.statics: List[RouteHandler] = [] - self.websocket_routes: List[Route] = [] + self.exceptions: list[RouteHandler] = [] + self.listeners: dict[str, list[ListenerType[Any]]] = {} + self.middlewares: list[MiddlewareType] = [] + self.routes: list[Route] = [] + self.statics: list[RouteHandler] = [] + self.websocket_routes: list[Route] = [] def copy( self, name: str, - url_prefix: Optional[Union[str, Default]] = _default, - version: Optional[Union[int, str, float, Default]] = _default, - version_prefix: Union[str, Default] = _default, - allow_route_overwrite: Union[bool, Default] = _default, - strict_slashes: Optional[Union[bool, Default]] = _default, + url_prefix: str | Default | None = _default, + version: int | str | float | Default | None = _default, + version_prefix: str | Default = _default, + allow_route_overwrite: bool | Default = _default, + strict_slashes: bool | Default | None = _default, with_registration: bool = True, with_ctx: bool = False, ): @@ -277,12 +265,12 @@ class Blueprint(BaseSanic): @staticmethod def group( - *blueprints: Union[Blueprint, BlueprintGroup], - url_prefix: Optional[str] = None, - version: Optional[Union[int, str, float]] = None, - strict_slashes: Optional[bool] = None, + *blueprints: Blueprint | BlueprintGroup, + url_prefix: str | None = None, + version: int | str | float | None = None, + strict_slashes: bool | None = None, version_prefix: str = "/v", - name_prefix: Optional[str] = "", + name_prefix: str | None = "", ) -> BlueprintGroup: """Group multiple blueprints (or other blueprint groups) together. @@ -353,9 +341,7 @@ class Blueprint(BaseSanic): opt_strict_slashes = options.get("strict_slashes", None) opt_version_prefix = options.get("version_prefix", self.version_prefix) opt_name_prefix = options.get("name_prefix", None) - error_format = options.get( - "error_format", app.config.FALLBACK_ERROR_FORMAT - ) + error_format = options.get("error_format", app.config.FALLBACK_ERROR_FORMAT) routes = [] middleware = [] @@ -381,9 +367,7 @@ class Blueprint(BaseSanic): version_prefix = prefix break - version = self._extract_value( - future.version, opt_version, self.version - ) + version = self._extract_value(future.version, opt_version, self.version) strict_slashes = self._extract_value( future.strict_slashes, opt_strict_slashes, self.strict_slashes ) @@ -419,22 +403,16 @@ class Blueprint(BaseSanic): continue registered.add(apply_route) - route = app._apply_route( - apply_route, overwrite=self._allow_route_overwrite - ) + route = app._apply_route(apply_route, overwrite=self._allow_route_overwrite) # If it is a copied BP, then make sure all of the names of routes # matchup with the new BP name if self.copied_from: for r in route: r.name = r.name.replace(self.copied_from, self.name) - r.extra.ident = r.extra.ident.replace( - self.copied_from, self.name - ) + r.extra.ident = r.extra.ident.replace(self.copied_from, self.name) - operation = ( - routes.extend if isinstance(route, list) else routes.append - ) + operation = routes.extend if isinstance(route, list) else routes.append operation(route) # Static Files @@ -479,7 +457,7 @@ class Blueprint(BaseSanic): continue future.condition.update({"__blueprint__": self.name}) # Force exclusive to be False - app._apply_signal(tuple((*future[:-1], False))) + app._apply_signal((*future[:-1], False)) self.routes += [route for route in routes if isinstance(route, Route)] self.websocket_routes += [ @@ -512,11 +490,9 @@ class Blueprint(BaseSanic): condition = kwargs.pop("condition", {}) condition.update({"__blueprint__": self.name}) kwargs["condition"] = condition - await asyncio.gather( - *[app.dispatch(*args, **kwargs) for app in self.apps] - ) + await asyncio.gather(*[app.dispatch(*args, **kwargs) for app in self.apps]) - def event(self, event: str, timeout: Optional[Union[int, float]] = None): + def event(self, event: str, timeout: int | float | None = None): """Wait for a signal event to be dispatched. Args: @@ -550,7 +526,7 @@ class Blueprint(BaseSanic): return value @staticmethod - def _setup_uri(base: str, prefix: Optional[str]): + def _setup_uri(base: str, prefix: str | None): uri = base if prefix: uri = prefix @@ -563,7 +539,7 @@ class Blueprint(BaseSanic): @staticmethod def register_futures( - apps: Set[Sanic], bp: Blueprint, futures: Sequence[Tuple[Any, ...]] + apps: set[Sanic], bp: Blueprint, futures: Sequence[tuple[Any, ...]] ): """Register futures to the apps. @@ -575,7 +551,7 @@ class Blueprint(BaseSanic): """ for app in apps: - app._future_registry.update(set((bp, item) for item in futures)) + app._future_registry.update({(bp, item) for item in futures}) if sys.version_info < (3, 9): @@ -667,13 +643,13 @@ class BlueprintGroup(bpg_base): def __init__( self, - url_prefix: Optional[str] = None, - version: Optional[Union[int, str, float]] = None, - strict_slashes: Optional[bool] = None, + url_prefix: str | None = None, + version: int | str | float | None = None, + strict_slashes: bool | None = None, version_prefix: str = "/v", - name_prefix: Optional[str] = "", + name_prefix: str | None = "", ): - self._blueprints: List[Blueprint] = [] + self._blueprints: list[Blueprint] = [] self._url_prefix = url_prefix self._version = version self._version_prefix = version_prefix @@ -681,7 +657,7 @@ class BlueprintGroup(bpg_base): self._name_prefix = name_prefix @property - def url_prefix(self) -> Optional[Union[int, str, float]]: + def url_prefix(self) -> int | str | float | None: """The URL prefix for the Blueprint Group. Returns: @@ -691,7 +667,7 @@ class BlueprintGroup(bpg_base): return self._url_prefix @property - def blueprints(self) -> List[Blueprint]: + def blueprints(self) -> list[Blueprint]: """A list of all the available blueprints under this group. Returns: @@ -701,7 +677,7 @@ class BlueprintGroup(bpg_base): return self._blueprints @property - def version(self) -> Optional[Union[str, int, float]]: + def version(self) -> str | int | float | None: """API Version for the Blueprint Group, if any. Returns: @@ -710,7 +686,7 @@ class BlueprintGroup(bpg_base): return self._version @property - def strict_slashes(self) -> Optional[bool]: + def strict_slashes(self) -> bool | None: """Whether to enforce strict slashes for the Blueprint Group. Returns: @@ -728,7 +704,7 @@ class BlueprintGroup(bpg_base): return self._version_prefix @property - def name_prefix(self) -> Optional[str]: + def name_prefix(self) -> str | None: """Name prefix for the Blueprint Group. This is mainly needed when blueprints are copied in order to @@ -755,9 +731,7 @@ class BlueprintGroup(bpg_base): def __getitem__(self, item: slice) -> MutableSequence[Blueprint]: ... - def __getitem__( - self, item: Union[int, slice] - ) -> Union[Blueprint, MutableSequence[Blueprint]]: + def __getitem__(self, item: int | slice) -> Blueprint | MutableSequence[Blueprint]: """Get the Blueprint object at the specified index. This method returns a blueprint inside the group specified by @@ -785,8 +759,8 @@ class BlueprintGroup(bpg_base): def __setitem__( self, - index: Union[int, slice], - item: Union[Blueprint, Iterable[Blueprint]], + index: int | slice, + item: Blueprint | Iterable[Blueprint], ) -> None: """Set the Blueprint object at the specified index. @@ -824,7 +798,7 @@ class BlueprintGroup(bpg_base): def __delitem__(self, index: slice) -> None: ... - def __delitem__(self, index: Union[int, slice]) -> None: + def __delitem__(self, index: int | slice) -> None: """Delete the Blueprint object at the specified index. Abstract method implemented to turn the `BlueprintGroup` class diff --git a/sanic/cli/app.py b/sanic/cli/app.py index b8db98b9..306f76c3 100644 --- a/sanic/cli/app.py +++ b/sanic/cli/app.py @@ -1,7 +1,6 @@ import os import shutil import sys - from argparse import Namespace from functools import partial from textwrap import indent @@ -57,9 +56,7 @@ Or, a path to a directory to run as a simple HTTP server: ) self.parser._positionals.title = "Required\n========\n Positional" self.parser._optionals.title = "Optional\n========\n General" - self.main_process = ( - os.environ.get("SANIC_RELOADER_PROCESS", "") != "true" - ) + self.main_process = os.environ.get("SANIC_RELOADER_PROCESS", "") != "true" self.args: Namespace = Namespace() self.groups: List[Group] = [] self.inspecting = False @@ -127,11 +124,7 @@ Or, a path to a directory to run as a simple HTTP server: key = key.lstrip("-") except ValueError: value = False if arg.startswith("--no-") else True - key = ( - arg.replace("--no-", "") - .lstrip("-") - .replace("-", "_") - ) + key = arg.replace("--no-", "").lstrip("-").replace("-", "_") setattr(self.args, key, value) kwargs = {**self.args.__dict__} @@ -181,8 +174,7 @@ Or, a path to a directory to run as a simple HTTP server: " Example Module: project.sanic_server.app" ) error_logger.error( - "\nThe error below might have caused the above one:\n" - f"{e.msg}" + "\nThe error below might have caused the above one:\n" f"{e.msg}" ) sys.exit(1) else: @@ -196,7 +188,7 @@ Or, a path to a directory to run as a simple HTTP server: if self.args.tlshost: ssl.append(None) if self.args.cert is not None or self.args.key is not None: - ssl.append(dict(cert=self.args.cert, key=self.args.key)) + ssl.append({"cert": self.args.cert, "key": self.args.key}) if self.args.tls: ssl += self.args.tls if not ssl: diff --git a/sanic/cli/arguments.py b/sanic/cli/arguments.py index c4d64089..61fbf23f 100644 --- a/sanic/cli/arguments.py +++ b/sanic/cli/arguments.py @@ -1,7 +1,6 @@ from __future__ import annotations from argparse import ArgumentParser, _ArgumentGroup -from typing import List, Optional, Type, Union from sanic_routing import __version__ as __routing_version__ @@ -10,14 +9,14 @@ from sanic.http.constants import HTTP class Group: - name: Optional[str] - container: Union[ArgumentParser, _ArgumentGroup] - _registry: List[Type[Group]] = [] + name: str | None + container: ArgumentParser | _ArgumentGroup + _registry: list[type[Group]] = [] def __init_subclass__(cls) -> None: Group._registry.append(cls) - def __init__(self, parser: ArgumentParser, title: Optional[str]): + def __init__(self, parser: ArgumentParser, title: str | None): self.parser = parser if title: @@ -245,10 +244,7 @@ class DevelopmentGroup(Group): "--auto-reload", dest="auto_reload", action="store_true", - help=( - "Watch source directory for file changes and reload on " - "changes" - ), + help=("Watch source directory for file changes and reload on " "changes"), ) self.container.add_argument( "-R", diff --git a/sanic/cli/inspector_client.py b/sanic/cli/inspector_client.py index fd22bbd8..5249627e 100644 --- a/sanic/cli/inspector_client.py +++ b/sanic/cli/inspector_client.py @@ -1,10 +1,9 @@ from __future__ import annotations import sys - from http.client import RemoteDisconnected from textwrap import indent -from typing import Any, Dict, Optional +from typing import Any from urllib.error import URLError from urllib.request import Request as URequest from urllib.request import urlopen @@ -13,7 +12,6 @@ from sanic.application.logo import get_logo from sanic.application.motd import MOTDTTY from sanic.log import Colors - try: # no cov from ujson import dumps, loads except ModuleNotFoundError: # no cov @@ -27,7 +25,7 @@ class InspectorClient: port: int, secure: bool, raw: bool, - api_key: Optional[str], + api_key: str | None, ) -> None: self.scheme = "https" if secure else "http" self.host = host @@ -47,11 +45,7 @@ class InspectorClient: return result = self.request(action, **kwargs).get("result") if result: - out = ( - dumps(result) - if isinstance(result, (list, dict)) - else str(result) - ) + out = dumps(result) if isinstance(result, (list, dict)) else str(result) sys.stdout.write(out + "\n") def info(self) -> None: @@ -89,7 +83,7 @@ class InspectorClient: def request(self, action: str, method: str = "POST", **kwargs: Any) -> Any: url = f"{self.base_url}/{action}" - params: Dict[str, Any] = {"method": method, "headers": {}} + params: dict[str, Any] = {"method": method, "headers": {}} if kwargs: params["data"] = dumps(kwargs).encode() params["headers"]["content-type"] = "application/json" diff --git a/sanic/compat.py b/sanic/compat.py index 62a5df95..2d888fea 100644 --- a/sanic/compat.py +++ b/sanic/compat.py @@ -3,25 +3,16 @@ import os import platform import signal import sys - from contextlib import contextmanager from enum import Enum -from typing import Awaitable, Union +from typing import Awaitable, Literal, Union from multidict import CIMultiDict # type: ignore from sanic.helpers import Default from sanic.log import error_logger - -if sys.version_info < (3, 8): # no cov - StartMethod = Union[Default, str] -else: # no cov - from typing import Literal - - StartMethod = Union[ - Default, Literal["fork"], Literal["forkserver"], Literal["spawn"] - ] +StartMethod = Union[Default, Literal["fork"], Literal["forkserver"], Literal["spawn"]] OS_IS_WINDOWS = os.name == "nt" PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy" @@ -142,7 +133,10 @@ if use_trio: # pragma: no cover return trio.Path(path).stat() open_async = trio.open_file - CancelledErrors = tuple([asyncio.CancelledError, trio.Cancelled]) + CancelledErrors: tuple[type[BaseException], ...] = ( + asyncio.CancelledError, + trio.Cancelled, + ) else: if PYPY_IMPLEMENTATION: pypy_os_module_patch() @@ -156,7 +150,7 @@ else: async def open_async(file, mode="r", **kwargs): return aio_open(file, mode, **kwargs) - CancelledErrors = tuple([asyncio.CancelledError]) + CancelledErrors = (asyncio.CancelledError,) def ctrlc_workaround_for_windows(app): diff --git a/sanic/config.py b/sanic/config.py index 305949fb..f7facff4 100644 --- a/sanic/config.py +++ b/sanic/config.py @@ -1,12 +1,10 @@ from __future__ import annotations -import sys - from abc import ABCMeta from inspect import getmembers, isclass, isdatadescriptor from os import environ from pathlib import Path -from typing import Any, Callable, Dict, Optional, Sequence, Union +from typing import Any, Callable, Literal, Sequence, Union from warnings import filterwarnings from sanic.constants import LocalCertCreator @@ -16,20 +14,14 @@ from sanic.http import Http from sanic.log import error_logger from sanic.utils import load_module_from_file_location, str_to_bool - -if sys.version_info >= (3, 8): - from typing import Literal - - FilterWarningType = Union[ - Literal["default"], - Literal["error"], - Literal["ignore"], - Literal["always"], - Literal["module"], - Literal["once"], - ] -else: - FilterWarningType = str +FilterWarningType = Union[ + Literal["default"], + Literal["error"], + Literal["ignore"], + Literal["always"], + Literal["module"], + Literal["once"], +] SANIC_PREFIX = "SANIC_" @@ -100,25 +92,25 @@ class Config(dict, metaclass=DescriptorMeta): EVENT_AUTOREGISTER: bool DEPRECATION_FILTER: FilterWarningType FORWARDED_FOR_HEADER: str - FORWARDED_SECRET: Optional[str] + FORWARDED_SECRET: str | None GRACEFUL_SHUTDOWN_TIMEOUT: float INSPECTOR: bool INSPECTOR_HOST: str INSPECTOR_PORT: int - INSPECTOR_TLS_KEY: Union[Path, str, Default] - INSPECTOR_TLS_CERT: Union[Path, str, Default] + INSPECTOR_TLS_KEY: Path | str | Default + INSPECTOR_TLS_CERT: Path | str | Default INSPECTOR_API_KEY: str KEEP_ALIVE_TIMEOUT: int KEEP_ALIVE: bool - LOCAL_CERT_CREATOR: Union[str, LocalCertCreator] - LOCAL_TLS_KEY: Union[Path, str, Default] - LOCAL_TLS_CERT: Union[Path, str, Default] + LOCAL_CERT_CREATOR: str | LocalCertCreator + LOCAL_TLS_KEY: Path | str | Default + LOCAL_TLS_CERT: Path | str | Default LOCALHOST: str MOTD: bool - MOTD_DISPLAY: Dict[str, str] + MOTD_DISPLAY: dict[str, str] NOISY_EXCEPTIONS: bool - PROXIES_COUNT: Optional[int] - REAL_IP_HEADER: Optional[str] + PROXIES_COUNT: int | None + REAL_IP_HEADER: str | None REQUEST_BUFFER_SIZE: int REQUEST_MAX_HEADER_SIZE: int REQUEST_ID_HEADER: str @@ -127,21 +119,19 @@ class Config(dict, metaclass=DescriptorMeta): RESPONSE_TIMEOUT: int SERVER_NAME: str TLS_CERT_PASSWORD: str - TOUCHUP: Union[Default, bool] - USE_UVLOOP: Union[Default, bool] + TOUCHUP: Default | bool + USE_UVLOOP: Default | bool WEBSOCKET_MAX_SIZE: int WEBSOCKET_PING_INTERVAL: int WEBSOCKET_PING_TIMEOUT: int def __init__( self, - defaults: Optional[ - Dict[str, Union[str, bool, int, float, None]] - ] = None, - env_prefix: Optional[str] = SANIC_PREFIX, - keep_alive: Optional[bool] = None, + defaults: dict[str, str | bool | int | float | None] | None = None, + env_prefix: str | None = SANIC_PREFIX, + keep_alive: bool | None = None, *, - converters: Optional[Sequence[Callable[[str], Any]]] = None, + converters: Sequence[Callable[[str], Any]] | None = None, ): defaults = defaults or {} super().__init__({**DEFAULT_CONFIG, **defaults}) @@ -209,7 +199,7 @@ class Config(dict, metaclass=DescriptorMeta): ``` """ kwargs.update({k: v for item in other for k, v in dict(item).items()}) - setters: Dict[str, Any] = { + setters: dict[str, Any] = { k: kwargs.pop(k) for k in {**kwargs}.keys() if k in self.__class__.__setters__ @@ -237,9 +227,7 @@ class Config(dict, metaclass=DescriptorMeta): if attr == "LOCAL_CERT_CREATOR" and not isinstance( self.LOCAL_CERT_CREATOR, LocalCertCreator ): - self.LOCAL_CERT_CREATOR = LocalCertCreator[ - self.LOCAL_CERT_CREATOR.upper() - ] + self.LOCAL_CERT_CREATOR = LocalCertCreator[self.LOCAL_CERT_CREATOR.upper()] elif attr == "DEPRECATION_FILTER": self._configure_warnings() @@ -276,7 +264,7 @@ class Config(dict, metaclass=DescriptorMeta): module=r"sanic.*", ) - def _check_error_format(self, format: Optional[str] = None): + def _check_error_format(self, format: str | None = None): check_error_format(format or self.FALLBACK_ERROR_FORMAT) def load_environment_vars(self, prefix=SANIC_PREFIX): @@ -332,7 +320,7 @@ class Config(dict, metaclass=DescriptorMeta): except ValueError: pass - def update_config(self, config: Union[bytes, str, dict, Any]): + def update_config(self, config: bytes | str | dict | Any): """Update app.config. .. note:: diff --git a/sanic/cookies/__init__.py b/sanic/cookies/__init__.py index a5cbe463..5d605d39 100644 --- a/sanic/cookies/__init__.py +++ b/sanic/cookies/__init__.py @@ -1,4 +1,3 @@ from .response import Cookie, CookieJar - __all__ = ("Cookie", "CookieJar") diff --git a/sanic/cookies/request.py b/sanic/cookies/request.py index 456c8872..90dbd0bb 100644 --- a/sanic/cookies/request.py +++ b/sanic/cookies/request.py @@ -1,12 +1,10 @@ import re - from typing import Any, Dict, List, Optional from sanic.cookies.response import Cookie from sanic.log import deprecation from sanic.request.parameters import RequestParameters - COOKIE_NAME_RESERVED_CHARS = re.compile( '[\x00-\x1F\x7F-\xFF()<>@,;:\\\\"/[\\]?={} \x09]' ) @@ -149,9 +147,7 @@ class CookieRequestParameters(RequestParameters): except KeyError: return super().get(name, default) - def getlist( - self, name: str, default: Optional[Any] = None - ) -> Optional[Any]: + def getlist(self, name: str, default: Optional[Any] = None) -> Optional[Any]: try: return self._get_prefixed_cookie(name) except KeyError: diff --git a/sanic/cookies/response.py b/sanic/cookies/response.py index 0a55cd00..95f65dfb 100644 --- a/sanic/cookies/response.py +++ b/sanic/cookies/response.py @@ -2,31 +2,25 @@ from __future__ import annotations import re import string -import sys - from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Union from sanic.exceptions import ServerError from sanic.log import deprecation - if TYPE_CHECKING: from sanic.compat import Header -if sys.version_info < (3, 8): # no cov - SameSite = str -else: # no cov - from typing import Literal +from typing import Literal - SameSite = Union[ - Literal["Strict"], - Literal["Lax"], - Literal["None"], - Literal["strict"], - Literal["lax"], - Literal["none"], - ] +SameSite = Union[ + Literal["Strict"], + Literal["Lax"], + Literal["None"], + Literal["strict"], + Literal["lax"], + Literal["none"], +] DEFAULT_MAX_AGE = 0 SAMESITE_VALUES = ("strict", "lax", "none") @@ -180,7 +174,7 @@ class CookieJar(dict): return CookieJar.HEADER_KEY @property - def cookie_headers(self) -> Dict[str, str]: # no cov + def cookie_headers(self) -> dict[str, str]: # no cov """Deprecated in v24.3""" deprecation( "The CookieJar.coookie_headers property has been deprecated " @@ -191,7 +185,7 @@ class CookieJar(dict): return {key: self.header_key for key in self} @property - def cookies(self) -> List[Cookie]: + def cookies(self) -> list[Cookie]: """A list of cookies in the CookieJar. Returns: @@ -203,10 +197,10 @@ class CookieJar(dict): self, key: str, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, - ) -> Optional[Cookie]: + ) -> Cookie | None: """Fetch a cookie from the CookieJar. Args: @@ -236,7 +230,7 @@ class CookieJar(dict): self, key: str, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, ) -> bool: @@ -271,14 +265,14 @@ class CookieJar(dict): value: str, *, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, secure: bool = True, - max_age: Optional[int] = None, - expires: Optional[datetime] = None, + max_age: int | None = None, + expires: datetime | None = None, httponly: bool = False, - samesite: Optional[SameSite] = "Lax", + samesite: SameSite | None = "Lax", partitioned: bool = False, - comment: Optional[str] = None, + comment: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, ) -> Cookie: @@ -362,7 +356,7 @@ class CookieJar(dict): key: str, *, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, ) -> None: @@ -390,7 +384,7 @@ class CookieJar(dict): :type secure_prefix: bool """ # remove it from header - cookies: List[Cookie] = self.headers.popall(self.HEADER_KEY, []) + cookies: list[Cookie] = self.headers.popall(self.HEADER_KEY, []) for cookie in cookies: if ( cookie.key != Cookie.make_key(key, host_prefix, secure_prefix) @@ -481,14 +475,14 @@ class Cookie(dict): value: str, *, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, secure: bool = True, - max_age: Optional[int] = None, - expires: Optional[datetime] = None, + max_age: int | None = None, + expires: datetime | None = None, httponly: bool = False, - samesite: Optional[SameSite] = "Lax", + samesite: SameSite | None = "Lax", partitioned: bool = False, - comment: Optional[str] = None, + comment: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, ): @@ -502,9 +496,7 @@ class Cookie(dict): "Cannot set host_prefix on a cookie without secure=True" ) if path != "/": - raise ServerError( - "Cannot set host_prefix on a cookie unless path='/'" - ) + raise ServerError("Cannot set host_prefix on a cookie unless path='/'") if domain: raise ServerError( "Cannot set host_prefix on a cookie with a defined domain" @@ -561,7 +553,7 @@ class Cookie(dict): # in v24.3 when this is no longer a dict def _set_value(self, key: str, value: Any) -> None: if key not in self._keys: - raise KeyError("Unknown cookie property: %s=%s" % (key, value)) + raise KeyError(f"Unknown cookie property: {key}={value}") if value is not None: if key.lower() == "max-age" and not str(value).isdigit(): @@ -604,21 +596,18 @@ class Cookie(dict): def __str__(self): """Format as a Set-Cookie header value.""" - output = ["%s=%s" % (self.key, _quote(self.value))] + output = [f"{self.key}={_quote(self.value)}"] key_index = list(self._keys) - for key, value in sorted( - self.items(), key=lambda x: key_index.index(x[0]) - ): + for key, value in sorted(self.items(), key=lambda x: key_index.index(x[0])): if value is not None and value is not False: if key == "max-age": try: output.append("%s=%d" % (self._keys[key], value)) except TypeError: - output.append("%s=%s" % (self._keys[key], value)) + output.append(f"{self._keys[key]}={value}") elif key == "expires": output.append( - "%s=%s" - % ( + "{}={}".format( self._keys[key], value.strftime("%a, %d-%b-%Y %T GMT"), ) @@ -626,7 +615,7 @@ class Cookie(dict): elif key in self._flags: output.append(self._keys[key]) else: - output.append("%s=%s" % (self._keys[key], value)) + output.append(f"{self._keys[key]}={value}") return "; ".join(output) @@ -640,7 +629,7 @@ class Cookie(dict): self._set_value("path", value) @property - def expires(self) -> Optional[datetime]: # no cov + def expires(self) -> datetime | None: # no cov """The expiration date of the cookie. Defaults to `None`.""" return self.get("expires") @@ -649,7 +638,7 @@ class Cookie(dict): self._set_value("expires", value) @property - def comment(self) -> Optional[str]: # no cov + def comment(self) -> str | None: # no cov """A comment for the cookie. Defaults to `None`.""" return self.get("comment") @@ -658,7 +647,7 @@ class Cookie(dict): self._set_value("comment", value) @property - def domain(self) -> Optional[str]: # no cov + def domain(self) -> str | None: # no cov """The domain of the cookie. Defaults to `None`.""" return self.get("domain") @@ -667,7 +656,7 @@ class Cookie(dict): self._set_value("domain", value) @property - def max_age(self) -> Optional[int]: # no cov + def max_age(self) -> int | None: # no cov """The maximum age of the cookie in seconds. Defaults to `None`.""" return self.get("max-age") @@ -694,7 +683,7 @@ class Cookie(dict): self._set_value("httponly", value) @property - def samesite(self) -> Optional[SameSite]: # no cov + def samesite(self) -> SameSite | None: # no cov """The SameSite attribute for the cookie. Defaults to `"Lax"`.""" return self.get("samesite") diff --git a/sanic/errorpages.py b/sanic/errorpages.py index 08341476..93d45f03 100644 --- a/sanic/errorpages.py +++ b/sanic/errorpages.py @@ -16,7 +16,6 @@ from __future__ import annotations import sys import typing as t - from functools import partial from traceback import extract_tb @@ -26,7 +25,6 @@ from sanic.log import deprecation, logger from sanic.pages.error import ErrorPage from sanic.response import html, json, text - dumps: t.Callable[..., str] try: from ujson import dumps @@ -73,7 +71,7 @@ class BaseRenderer: self.debug = debug @property - def headers(self) -> t.Dict[str, str]: + def headers(self) -> dict[str, str]: """The headers to be used for the response.""" if isinstance(self.exception, SanicException): return getattr(self.exception, "headers", {}) @@ -192,8 +190,7 @@ class TextRenderer(BaseRenderer): lines += [ f"{self.exception.__class__.__name__}: {self.exception} while " f"handling path {self.request.path}", - f"Traceback of {self.request.app.name} " - "(most recent call last):\n", + f"Traceback of {self.request.app.name} " "(most recent call last):\n", ] while exc_value: @@ -326,8 +323,8 @@ def exception_response( exception: Exception, debug: bool, fallback: str, - base: t.Type[BaseRenderer], - renderer: t.Optional[t.Type[BaseRenderer]] = None, + base: type[BaseRenderer], + renderer: type[BaseRenderer] | None = None, ) -> HTTPResponse: """Render a response for the default FALLBACK exception handler.""" if not renderer: @@ -390,9 +387,7 @@ def guess_mime(req: Request, fallback: str) -> str: if m: format = CONFIG_BY_MIME[m.mime] source = formats[format] - logger.debug( - f"The client accepts {m.header}, using '{format}' from {source}" - ) + logger.debug(f"The client accepts {m.header}, using '{format}' from {source}") else: logger.debug(f"No format found, the client accepts {req.accept!r}") return m.mime diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 52e21058..c6ec73a0 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -69,9 +69,7 @@ class SanicException(Exception): ) -> None: self.context = context self.extra = extra - status_code = status_code or getattr( - self.__class__, "status_code", None - ) + status_code = status_code or getattr(self.__class__, "status_code", None) quiet = quiet or getattr(self.__class__, "quiet", None) headers = headers or getattr(self.__class__, "headers", {}) if message is None: @@ -621,9 +619,7 @@ class Unauthorized(HTTPException): # if auth-scheme is specified, set "WWW-Authenticate" header if scheme is not None: - values = [ - '{!s}="{!s}"'.format(k, v) for k, v in challenges.items() - ] + values = [f'{k!s}="{v!s}"' for k, v in challenges.items()] challenge = ", ".join(values) self.headers = { diff --git a/sanic/handlers/__init__.py b/sanic/handlers/__init__.py index 4777a8da..d51d67f9 100644 --- a/sanic/handlers/__init__.py +++ b/sanic/handlers/__init__.py @@ -2,7 +2,6 @@ from .content_range import ContentRangeHandler from .directory import DirectoryHandler from .error import ErrorHandler - __all__ = ( "ContentRangeHandler", "DirectoryHandler", diff --git a/sanic/handlers/content_range.py b/sanic/handlers/content_range.py index c2db0cc7..f9dbbb6d 100644 --- a/sanic/handlers/content_range.py +++ b/sanic/handlers/content_range.py @@ -1,7 +1,6 @@ from __future__ import annotations import os - from typing import TYPE_CHECKING from sanic.exceptions import ( @@ -11,7 +10,6 @@ from sanic.exceptions import ( ) from sanic.models.protocol_types import Range - if TYPE_CHECKING: from sanic import Request @@ -33,27 +31,19 @@ class ContentRangeHandler(Range): raise HeaderNotFound("Range Header Not Found") unit, _, value = tuple(map(str.strip, _range.partition("="))) if unit != "bytes": - raise InvalidRangeType( - "%s is not a valid Range Type" % (unit,), self - ) + raise InvalidRangeType(f"{unit} is not a valid Range Type", self) start_b, _, end_b = tuple(map(str.strip, value.partition("-"))) try: self.start = int(start_b) if start_b else None except ValueError: - raise RangeNotSatisfiable( - "'%s' is invalid for Content Range" % (start_b,), self - ) + raise RangeNotSatisfiable(f"'{start_b}' is invalid for Content Range", self) try: self.end = int(end_b) if end_b else None except ValueError: - raise RangeNotSatisfiable( - "'%s' is invalid for Content Range" % (end_b,), self - ) + raise RangeNotSatisfiable(f"'{end_b}' is invalid for Content Range", self) if self.end is None: if self.start is None: - raise RangeNotSatisfiable( - "Invalid for Content Range parameters", self - ) + raise RangeNotSatisfiable("Invalid for Content Range parameters", self) else: # this case represents `Content-Range: bytes 5-` self.end = self.total - 1 @@ -63,14 +53,9 @@ class ContentRangeHandler(Range): self.start = self.total - self.end self.end = self.total - 1 if self.start >= self.end: - raise RangeNotSatisfiable( - "Invalid for Content Range parameters", self - ) + raise RangeNotSatisfiable("Invalid for Content Range parameters", self) self.size = self.end - self.start + 1 - self.headers = { - "Content-Range": "bytes %s-%s/%s" - % (self.start, self.end, self.total) - } + self.headers = {"Content-Range": f"bytes {self.start}-{self.end}/{self.total}"} def __bool__(self): return hasattr(self, "size") and self.size > 0 diff --git a/sanic/handlers/directory.py b/sanic/handlers/directory.py index 319cae45..8581328c 100644 --- a/sanic/handlers/directory.py +++ b/sanic/handlers/directory.py @@ -4,7 +4,7 @@ from datetime import datetime from operator import itemgetter from pathlib import Path from stat import S_ISDIR -from typing import Dict, Iterable, Optional, Sequence, Union, cast +from typing import Iterable, Sequence, cast from sanic.exceptions import NotFound from sanic.pages.directory_page import DirectoryPage, FileInfo @@ -28,7 +28,7 @@ class DirectoryHandler: uri: str, directory: Path, directory_view: bool = False, - index: Optional[Union[str, Sequence[str]]] = None, + index: str | Sequence[str] | None = None, ) -> None: if isinstance(index, str): index = [index] @@ -60,9 +60,7 @@ class DirectoryHandler: return await file(index_file) if self.directory_view: - return self._index( - self.directory / current, path, request.app.debug - ) + return self._index(self.directory / current, path, request.app.debug) if self.index: raise NotFound("File not found") @@ -72,20 +70,16 @@ class DirectoryHandler: def _index(self, location: Path, path: str, debug: bool): # Remove empty path elements, append slash if "//" in path or not path.endswith("/"): - return redirect( - "/" + "".join([f"{p}/" for p in path.split("/") if p]) - ) + return redirect("/" + "".join([f"{p}/" for p in path.split("/") if p])) # Render file browser page = DirectoryPage(self._iter_files(location), path, debug) return html(page.render()) - def _prepare_file(self, path: Path) -> Dict[str, Union[int, str]]: + def _prepare_file(self, path: Path) -> dict[str, int | str]: stat = path.stat() modified = ( - datetime.fromtimestamp(stat.st_mtime) - .isoformat()[:19] - .replace("T", " ") + datetime.fromtimestamp(stat.st_mtime).isoformat()[:19].replace("T", " ") ) is_dir = S_ISDIR(stat.st_mode) icon = "📁" if is_dir else "📄" diff --git a/sanic/handlers/error.py b/sanic/handlers/error.py index 50f73695..2aaa2900 100644 --- a/sanic/handlers/error.py +++ b/sanic/handlers/error.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Dict, List, Optional, Tuple, Type - from sanic.errorpages import BaseRenderer, TextRenderer, exception_response from sanic.exceptions import ServerError from sanic.log import error_logger @@ -25,20 +23,20 @@ class ErrorHandler: def __init__( self, - base: Type[BaseRenderer] = TextRenderer, + base: type[BaseRenderer] = TextRenderer, ): - self.cached_handlers: Dict[ - Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler] + self.cached_handlers: dict[ + tuple[type[BaseException], str | None], RouteHandler | None ] = {} self.debug = False self.base = base - def _full_lookup(self, exception, route_name: Optional[str] = None): + def _full_lookup(self, exception, route_name: str | None = None): return self.lookup(exception, route_name) def _add( self, - key: Tuple[Type[BaseException], Optional[str]], + key: tuple[type[BaseException], str | None], handler: RouteHandler, ) -> None: if key in self.cached_handlers: @@ -53,7 +51,7 @@ class ErrorHandler: raise ServerError(message) self.cached_handlers[key] = handler - def add(self, exception, handler, route_names: Optional[List[str]] = None): + def add(self, exception, handler, route_names: list[str] | None = None): """Add a new exception handler to an already existing handler object. Args: @@ -72,7 +70,7 @@ class ErrorHandler: else: self._add((exception, None), handler) - def lookup(self, exception, route_name: Optional[str] = None): + def lookup(self, exception, route_name: str | None = None): """Lookup the existing instance of `ErrorHandler` and fetch the registered handler for a specific type of exception. This method leverages a dict lookup to speedup the retrieval process. @@ -98,9 +96,7 @@ class ErrorHandler: exception_key = (ancestor, name) if exception_key in self.cached_handlers: handler = self.cached_handlers[exception_key] - self.cached_handlers[ - (exception_class, route_name) - ] = handler + self.cached_handlers[(exception_class, route_name)] = handler return handler if ancestor is BaseException: @@ -135,13 +131,11 @@ class ErrorHandler: url = repr(request.url) except AttributeError: # no cov url = "unknown" - response_message = ( - "Exception raised in exception handler " '"%s" for uri: %s' - ) - error_logger.exception(response_message, handler.__name__, url) + response_message = f'Exception raised in exception handler "{handler.__name__}" for uri: {url}' + error_logger.exception(response_message) if self.debug: - return text(response_message % (handler.__name__, url), 500) + return text(response_message, 500) else: return text("An error occurred while handling an error", 500) return response @@ -200,6 +194,4 @@ class ErrorHandler: except AttributeError: # no cov url = "unknown" - error_logger.exception( - "Exception occurred while handling uri: %s", url - ) + error_logger.exception("Exception occurred while handling uri: %s", url) diff --git a/sanic/headers.py b/sanic/headers.py index 5a7e1664..73686214 100644 --- a/sanic/headers.py +++ b/sanic/headers.py @@ -1,14 +1,12 @@ from __future__ import annotations import re - -from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union +from typing import Any, Dict, Iterable, Tuple, Union from urllib.parse import unquote from sanic.exceptions import InvalidHeader from sanic.helpers import STATUS_CODES - # TODO: # - the Options object should be a typed object to allow for less casting # across the application (in request.py for example) @@ -21,9 +19,7 @@ _token, _quoted = r"([\w!#$%&'*+\-.^_`|~]+)", r'"([^"]*)"' _param = re.compile(rf";\s*{_token}=(?:{_token}|{_quoted})", re.ASCII) _ipv6 = "(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}" _ipv6_re = re.compile(_ipv6) -_host_re = re.compile( - r"((?:\[" + _ipv6 + r"\])|[a-zA-Z0-9.\-]{1,253})(?::(\d{1,5}))?" -) +_host_re = re.compile(r"((?:\[" + _ipv6 + r"\])|[a-zA-Z0-9.\-]{1,253})(?::(\d{1,5}))?") # RFC's quoted-pair escapes are mostly ignored by browsers. Chrome, Firefox and # curl all have different escaping, that we try to handle as well as possible, @@ -85,8 +81,8 @@ class MediaType: def match( self, - mime_with_params: Union[str, MediaType], - ) -> Optional[MediaType]: + mime_with_params: str | MediaType, + ) -> MediaType | None: """Match this media type against another media type. Check if this media type matches the given mime type/subtype. @@ -124,9 +120,7 @@ class MediaType: or mt.subtype == "*" ) # Type match - and ( - self.type == mt.type or self.type == "*" or mt.type == "*" - ) + and (self.type == mt.type or self.type == "*" or mt.type == "*") ) else None ) @@ -141,7 +135,7 @@ class MediaType: return any(part == "*" for part in (self.subtype, self.type)) @classmethod - def _parse(cls, mime_with_params: str) -> Optional[MediaType]: + def _parse(cls, mime_with_params: str) -> MediaType | None: mtype = mime_with_params.strip() if "/" not in mime_with_params: return None @@ -151,12 +145,10 @@ class MediaType: if not type_ or not subtype: raise ValueError(f"Invalid media type: {mtype}") - params = dict( - [ - (key.strip(), value.strip()) - for key, value in (param.split("=", 1) for param in raw_params) - ] - ) + params = { + key.strip(): value.strip() + for key, value in (param.split("=", 1) for param in raw_params) + } return cls(type_.lstrip(), subtype.rstrip(), **params) @@ -173,7 +165,7 @@ class Matched: header (MediaType): The header to match against, if any. """ - def __init__(self, mime: str, header: Optional[MediaType]): + def __init__(self, mime: str, header: MediaType | None): self.mime = mime self.header = header @@ -200,7 +192,7 @@ class Matched: ) ) - def _compare(self, other) -> Tuple[bool, Matched]: + def _compare(self, other) -> tuple[bool, Matched]: if isinstance(other, str): parsed = Matched.parse(other) if self.mime == other: @@ -215,7 +207,7 @@ class Matched: f"mime types of '{self.mime}' and '{other}'" ) - def match(self, other: Union[str, Matched]) -> Optional[Matched]: + def match(self, other: str | Matched) -> Matched | None: """Match this MIME string against another MIME string. Check if this MIME string matches the given MIME string. Wildcards are supported both ways on both type and subtype. @@ -296,7 +288,7 @@ class AcceptList(list): return ", ".join(str(m) for m in self) -def parse_accept(accept: Optional[str]) -> AcceptList: +def parse_accept(accept: str | None) -> AcceptList: """Parse an Accept header and order the acceptable media types according to RFC 7231, s. 5.3.2 https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 @@ -316,9 +308,7 @@ def parse_accept(accept: Optional[str]) -> AcceptList: accept = "*/*" # No header means that all types are accepted try: a = [ - mt - for mt in [MediaType._parse(mtype) for mtype in accept.split(",")] - if mt + mt for mt in [MediaType._parse(mtype) for mtype in accept.split(",")] if mt ] if not a: raise ValueError @@ -327,7 +317,7 @@ def parse_accept(accept: Optional[str]) -> AcceptList: raise InvalidHeader(f"Invalid header value in Accept: {accept}") -def parse_content_header(value: str) -> Tuple[str, Options]: +def parse_content_header(value: str) -> tuple[str, Options]: """Parse content-type and content-disposition header values. E.g. `form-data; name=upload; filename="file.txt"` to @@ -346,11 +336,10 @@ def parse_content_header(value: str) -> Tuple[str, Options]: """ pos = value.find(";") if pos == -1: - options: Dict[str, Union[int, str]] = {} + options: dict[str, int | str] = {} else: options = { - m.group(1) - .lower(): (m.group(2) or m.group(3)) + m.group(1).lower(): (m.group(2) or m.group(3)) .replace("%22", '"') .replace("%0D%0A", "\n") for m in _param.finditer(value[pos:]) @@ -367,7 +356,7 @@ def parse_content_header(value: str) -> Tuple[str, Options]: _rparam = re.compile(f"(?:{_token}|{_quoted})={_token}\\s*($|[;,])", re.ASCII) -def parse_forwarded(headers, config) -> Optional[Options]: +def parse_forwarded(headers, config) -> Options | None: """Parse RFC 7239 Forwarded headers. The value of `by` or `secret` must match `config.FORWARDED_SECRET` :return: dict with keys and values, or None if nothing matched @@ -381,7 +370,7 @@ def parse_forwarded(headers, config) -> Optional[Options]: return None # Loop over = elements from right to left sep = pos = None - options: List[Tuple[str, str]] = [] + options: list[tuple[str, str]] = [] found = False for m in _rparam.finditer(header[::-1]): # Start of new element? (on parser skips and non-semicolon right sep) @@ -405,7 +394,7 @@ def parse_forwarded(headers, config) -> Optional[Options]: return fwd_normalize(reversed(options)) if found else None -def parse_xforwarded(headers, config) -> Optional[Options]: +def parse_xforwarded(headers, config) -> Options | None: """Parse traditional proxy headers.""" real_ip_header = config.REAL_IP_HEADER proxies_count = config.PROXIES_COUNT @@ -416,11 +405,7 @@ def parse_xforwarded(headers, config) -> Optional[Options]: # Combine, split and filter multiple headers' entries forwarded_for = headers.getall(config.FORWARDED_FOR_HEADER) proxies = [ - p - for p in ( - p.strip() for h in forwarded_for for p in h.split(",") - ) - if p + p for p in (p.strip() for h in forwarded_for for p in h.split(",")) if p ] addr = proxies[-proxies_count] except (KeyError, IndexError): @@ -452,7 +437,7 @@ def fwd_normalize(fwd: OptionsIterable) -> Options: Returns: Options: A dict of normalized key-value pairs. """ - ret: Dict[str, Union[int, str]] = {} + ret: dict[str, int | str] = {} for key, val in fwd: if val is not None: try: @@ -489,7 +474,7 @@ def fwd_normalize_address(addr: str) -> str: return addr.lower() -def parse_host(host: str) -> Tuple[Optional[str], Optional[int]]: +def parse_host(host: str) -> tuple[str | None, int | None]: """Split host:port into hostname and port. Args: @@ -531,9 +516,9 @@ def format_http1_response(status: int, headers: HeaderBytesIterable) -> bytes: def parse_credentials( - header: Optional[str], - prefixes: Optional[Union[List, Tuple, Set]] = None, -) -> Tuple[Optional[str], Optional[str]]: + header: str | None, + prefixes: list | tuple | set | None = None, +) -> tuple[str | None, str | None]: """Parses any header with the aim to retrieve any credentials from it. Args: diff --git a/sanic/helpers.py b/sanic/helpers.py index d7ac8968..44077ddc 100644 --- a/sanic/helpers.py +++ b/sanic/helpers.py @@ -1,12 +1,10 @@ """Defines basics of HTTP standard.""" import sys - from importlib import import_module from inspect import ismodule from typing import Dict - STATUS_CODES: Dict[int, bytes] = { 100: b"Continue", 101: b"Switching Protocols", @@ -132,7 +130,7 @@ def remove_entity_headers(headers, allowed=("content-location", "expires")): returns the headers without the entity headers """ - allowed = set([h.lower() for h in allowed]) + allowed = {h.lower() for h in allowed} headers = { header: value for header, value in headers.items() diff --git a/sanic/http/__init__.py b/sanic/http/__init__.py index 10451282..fdf45874 100644 --- a/sanic/http/__init__.py +++ b/sanic/http/__init__.py @@ -2,5 +2,4 @@ from .constants import Stage from .http1 import Http from .http3 import Http3 - __all__ = ("Http", "Stage", "Http3") diff --git a/sanic/http/http1.py b/sanic/http/http1.py index 07fb7d8f..0446e59e 100644 --- a/sanic/http/http1.py +++ b/sanic/http/http1.py @@ -1,7 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional - +from typing import TYPE_CHECKING if TYPE_CHECKING: from sanic.request import Request @@ -25,7 +24,6 @@ from sanic.http.stream import Stream from sanic.log import access_logger, error_logger, logger from sanic.touchup import TouchUpMeta - HTTP_CONTINUE = b"HTTP/1.1 100 Continue\r\n\r\n" @@ -363,26 +361,20 @@ class Http(Stream, metaclass=TouchUpMeta): self.response_func = None self.stage = Stage.IDLE - async def http1_response_chunked( - self, data: bytes, end_stream: bool - ) -> None: + async def http1_response_chunked(self, data: bytes, end_stream: bool) -> None: """Format a part of response body in chunked encoding.""" # Chunked encoding size = len(data) if end_stream: await self._send( - b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) - if size - else b"0\r\n\r\n" + b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) if size else b"0\r\n\r\n" ) self.response_func = None self.stage = Stage.IDLE elif size: await self._send(b"%x\r\n%b\r\n" % (size, data)) - async def http1_response_normal( - self, data: bytes, end_stream: bool - ) -> None: + async def http1_response_normal(self, data: bytes, end_stream: bool) -> None: """Format / keep track of non-chunked response.""" bytes_left = self.response_bytes_left - len(data) if bytes_left <= 0: @@ -420,9 +412,7 @@ class Http(Stream, metaclass=TouchUpMeta): exception, (ServiceUnavailable, RequestCancelled) ) try: - await app.handle_exception( - self.request, exception, request_middleware - ) + await app.handle_exception(self.request, exception, request_middleware) except Exception as e: await app.handle_exception(self.request, e, False) @@ -481,7 +471,7 @@ class Http(Stream, metaclass=TouchUpMeta): if data: yield data - async def read(self) -> Optional[bytes]: # no cov + async def read(self) -> bytes | None: # no cov """Read some bytes of request body.""" # Send a 100-continue if needed diff --git a/sanic/http/http3.py b/sanic/http/http3.py index d53a9637..c7e68df7 100644 --- a/sanic/http/http3.py +++ b/sanic/http/http3.py @@ -1,17 +1,12 @@ from __future__ import annotations import asyncio - from abc import ABC, abstractmethod from ssl import SSLContext from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - List, - Optional, - Tuple, Union, cast, ) @@ -32,7 +27,6 @@ from sanic.log import Colors, logger from sanic.models.protocol_types import TransportProtocol from sanic.models.server_types import ConnInfo - try: from aioquic.h0.connection import H0_ALPN, H0Connection from aioquic.h3.connection import H3_ALPN, H3Connection @@ -71,10 +65,7 @@ class HTTP3Transport(TransportProtocol): return self._protocol def get_extra_info(self, info: str, default: Any = None) -> Any: - if ( - info in ("socket", "sockname", "peername") - and self._protocol._transport - ): + if info in ("socket", "sockname", "peername") and self._protocol._transport: return self._protocol._transport.get_extra_info(info, default) elif info == "network_paths": return self._protocol._quic._network_paths @@ -109,19 +100,18 @@ class HTTPReceiver(Receiver, Stream): self.request_body = None self.stage = Stage.IDLE self.headers_sent = False - self.response: Optional[BaseHTTPResponse] = None + self.response: BaseHTTPResponse | None = None self.request_max_size = self.protocol.request_max_size self.request_bytes = 0 - async def run(self, exception: Optional[Exception] = None): + async def run(self, exception: Exception | None = None): """Handle the request and response cycle.""" self.stage = Stage.HANDLER self.head_only = self.request.method.upper() == "HEAD" if exception: logger.info( # no cov - f"{Colors.BLUE}[exception]: " - f"{Colors.RED}{exception}{Colors.END}", + f"{Colors.BLUE}[exception]: " f"{Colors.RED}{exception}{Colors.END}", exc_info=True, extra={"verbosity": 1}, ) @@ -146,17 +136,13 @@ class HTTPReceiver(Receiver, Stream): await app.handle_exception(self.request, exception) - def _prepare_headers( - self, response: BaseHTTPResponse - ) -> List[Tuple[bytes, bytes]]: + def _prepare_headers(self, response: BaseHTTPResponse) -> list[tuple[bytes, bytes]]: size = len(response.body) if response.body else 0 headers = response.headers status = response.status if not has_message_body(status) and ( - size - or "content-length" in headers - or "transfer-encoding" in headers + size or "content-length" in headers or "transfer-encoding" in headers ): headers.pop("content-length", None) headers.pop("transfer-encoding", None) @@ -249,11 +235,7 @@ class HTTPReceiver(Receiver, Stream): ): size = len(data) if end_stream: - data = ( - b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) - if size - else b"0\r\n\r\n" - ) + data = b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) if size else b"0\r\n\r\n" elif size: data = b"%x\r\n%b\r\n" % (size, data) @@ -304,7 +286,7 @@ class Http3: ) -> None: self.protocol = protocol self.transmit = transmit - self.receivers: Dict[int, Receiver] = {} + self.receivers: dict[int, Receiver] = {} def http_event_received(self, event: H3Event) -> None: logger.debug( # no cov @@ -330,11 +312,8 @@ class Http3: extra={"verbosity": 2}, ) - def get_or_make_receiver(self, event: H3Event) -> Tuple[Receiver, bool]: - if ( - isinstance(event, HeadersReceived) - and event.stream_id not in self.receivers - ): + def get_or_make_receiver(self, event: H3Event) -> tuple[Receiver, bool]: + if isinstance(event, HeadersReceived) and event.stream_id not in self.receivers: request = self._make_request(event) receiver = HTTPReceiver(self.transmit, self.protocol, request) request.stream = receiver @@ -357,9 +336,7 @@ class Http3: ) ) except UnicodeDecodeError: - raise BadRequest( - "Header names may only contain US-ASCII characters." - ) + raise BadRequest("Header names may only contain US-ASCII characters.") method = headers[":method"] path = headers[":path"] scheme = headers.pop(":scheme", "") @@ -396,18 +373,16 @@ class SessionTicketStore: """ def __init__(self) -> None: - self.tickets: Dict[bytes, SessionTicket] = {} + self.tickets: dict[bytes, SessionTicket] = {} def add(self, ticket: SessionTicket) -> None: self.tickets[ticket.ticket] = ticket - def pop(self, label: bytes) -> Optional[SessionTicket]: + def pop(self, label: bytes) -> SessionTicket | None: return self.tickets.pop(label, None) -def get_config( - app: Sanic, ssl: Union[SanicSSLContext, CertSelector, SSLContext] -): +def get_config(app: Sanic, ssl: SanicSSLContext | CertSelector | SSLContext): # TODO: # - proper selection needed if service with multiple certs insted of # just taking the first @@ -430,8 +405,6 @@ def get_config( ) password = app.config.TLS_CERT_PASSWORD or None - config.load_cert_chain( - ssl.sanic["cert"], ssl.sanic["key"], password=password - ) + config.load_cert_chain(ssl.sanic["cert"], ssl.sanic["key"], password=password) return config diff --git a/sanic/http/stream.py b/sanic/http/stream.py index f6436916..64eb0fcd 100644 --- a/sanic/http/stream.py +++ b/sanic/http/stream.py @@ -1,10 +1,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Tuple, Union +from typing import TYPE_CHECKING from sanic.http.constants import Stage - if TYPE_CHECKING: from sanic.response import BaseHTTPResponse from sanic.server.protocols.http_protocol import HttpProtocol @@ -12,16 +11,14 @@ if TYPE_CHECKING: class Stream: stage: Stage - response: Optional[BaseHTTPResponse] + response: BaseHTTPResponse | None protocol: HttpProtocol - url: Optional[str] - request_body: Optional[bytes] - request_max_size: Union[int, float] + url: str | None + request_body: bytes | None + request_max_size: int | float - __touchup__: Tuple[str, ...] = tuple() + __touchup__: tuple[str, ...] = () __slots__ = ("request_max_size",) - def respond( - self, response: BaseHTTPResponse - ) -> BaseHTTPResponse: # no cov + def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse: # no cov raise NotImplementedError("Not implemented") diff --git a/sanic/http/tls/__init__.py b/sanic/http/tls/__init__.py index b12fe529..ad3e69b9 100644 --- a/sanic/http/tls/__init__.py +++ b/sanic/http/tls/__init__.py @@ -1,5 +1,4 @@ from .context import process_to_context from .creators import get_ssl_context - __all__ = ("get_ssl_context", "process_to_context") diff --git a/sanic/http/tls/context.py b/sanic/http/tls/context.py index b210530f..9fa95b21 100644 --- a/sanic/http/tls/context.py +++ b/sanic/http/tls/context.py @@ -2,12 +2,10 @@ from __future__ import annotations import os import ssl - -from typing import Any, Dict, Iterable, Optional, Union +from typing import Any, Iterable from sanic.log import logger - # Only allow secure ciphers, notably leaving out AES-CBC mode # OpenSSL chooses ECDSA or RSA depending on the cert in use CIPHERS_TLS12 = [ @@ -19,11 +17,14 @@ CIPHERS_TLS12 = [ "ECDHE-RSA-AES128-GCM-SHA256", ] +TlsDef = None | ssl.SSLContext | dict[str, Any] | str +TlsDefs = TlsDef | list[TlsDef] | tuple[TlsDef, ...] + def create_context( - certfile: Optional[str] = None, - keyfile: Optional[str] = None, - password: Optional[str] = None, + certfile: str | None = None, + keyfile: str | None = None, + password: str | None = None, purpose: ssl.Purpose = ssl.Purpose.CLIENT_AUTH, ) -> ssl.SSLContext: """Create a context with secure crypto and HTTP/1.1 in protocols.""" @@ -38,9 +39,7 @@ def create_context( return context -def shorthand_to_ctx( - ctxdef: Union[None, ssl.SSLContext, dict, str] -) -> Optional[ssl.SSLContext]: +def shorthand_to_ctx(ctxdef: TlsDef) -> ssl.SSLContext | None: """Convert an ssl argument shorthand to an SSLContext object.""" if ctxdef is None or isinstance(ctxdef, ssl.SSLContext): return ctxdef @@ -54,9 +53,7 @@ def shorthand_to_ctx( ) -def process_to_context( - ssldef: Union[None, ssl.SSLContext, dict, str, list, tuple] -) -> Optional[ssl.SSLContext]: +def process_to_context(ssldef: TlsDefs) -> ssl.SSLContext | None: """Process app.run ssl argument from easy formats to full SSLContext.""" return ( CertSelector(map(shorthand_to_ctx, ssldef)) @@ -71,13 +68,9 @@ def load_cert_dir(p: str) -> ssl.SSLContext: keyfile = os.path.join(p, "privkey.pem") certfile = os.path.join(p, "fullchain.pem") if not os.access(keyfile, os.R_OK): - raise ValueError( - f"Certificate not found or permission denied {keyfile}" - ) + raise ValueError(f"Certificate not found or permission denied {keyfile}") if not os.access(certfile, os.R_OK): - raise ValueError( - f"Certificate not found or permission denied {certfile}" - ) + raise ValueError(f"Certificate not found or permission denied {certfile}") return CertSimple(certfile, keyfile) @@ -89,9 +82,7 @@ def find_cert(self: CertSelector, server_name: str): if not server_name: if self.sanic_fallback: return self.sanic_fallback - raise ValueError( - "The client provided no SNI to match for certificate." - ) + raise ValueError("The client provided no SNI to match for certificate.") for ctx in self.sanic_select: if match_hostname(ctx, server_name): return ctx @@ -100,9 +91,7 @@ def find_cert(self: CertSelector, server_name: str): raise ValueError(f"No certificate found matching hostname {server_name!r}") -def match_hostname( - ctx: Union[ssl.SSLContext, CertSelector], hostname: str -) -> bool: +def match_hostname(ctx: ssl.SSLContext | CertSelector, hostname: str) -> bool: """Match names from CertSelector against a received hostname.""" # Local certs are considered trusted, so this can be less pedantic # and thus faster than the deprecated ssl.match_hostname function is. @@ -119,7 +108,7 @@ def match_hostname( def selector_sni_callback( sslobj: ssl.SSLObject, server_name: str, ctx: CertSelector -) -> Optional[int]: +) -> int | None: """Select a certificate matching the SNI.""" # Call server_name_callback to store the SNI on sslobj server_name_callback(sslobj, server_name, ctx) @@ -142,7 +131,7 @@ def server_name_callback( class SanicSSLContext(ssl.SSLContext): - sanic: Dict[str, os.PathLike] + sanic: dict[str, os.PathLike] @classmethod def create_from_ssl_context(cls, context: ssl.SSLContext): @@ -153,7 +142,7 @@ class SanicSSLContext(ssl.SSLContext): class CertSimple(SanicSSLContext): """A wrapper for creating SSLContext with a sanic attribute.""" - sanic: Dict[str, Any] + sanic: dict[str, Any] def __new__(cls, cert, key, **kw): # try common aliases, rename to cert/key @@ -166,9 +155,7 @@ class CertSimple(SanicSSLContext): if "names" not in kw: cert = ssl._ssl._test_decode_cert(certfile) # type: ignore kw["names"] = [ - name - for t, name in cert["subjectAltName"] - if t in ["DNS", "IP Address"] + name for t, name in cert["subjectAltName"] if t in ["DNS", "IP Address"] ] subject = {k: v for item in cert["subject"] for k, v in item} self = create_context(certfile, keyfile, password) @@ -190,7 +177,7 @@ class CertSelector(ssl.SSLContext): def __new__(cls, ctxs): return super().__new__(cls) - def __init__(self, ctxs: Iterable[Optional[ssl.SSLContext]]): + def __init__(self, ctxs: Iterable[ssl.SSLContext | None]): super().__init__() self.sni_callback = selector_sni_callback # type: ignore self.sanic_select = [] @@ -205,7 +192,5 @@ class CertSelector(ssl.SSLContext): if i == 0: self.sanic_fallback = ctx if not all_names: - raise ValueError( - "No certificates with SubjectAlternativeNames found." - ) + raise ValueError("No certificates with SubjectAlternativeNames found.") logger.info(f"Certificate vhosts: {', '.join(all_names)}") diff --git a/sanic/http/tls/creators.py b/sanic/http/tls/creators.py index ad3b1b4f..a33d0cb8 100644 --- a/sanic/http/tls/creators.py +++ b/sanic/http/tls/creators.py @@ -3,13 +3,12 @@ from __future__ import annotations import ssl import subprocess import sys - from abc import ABC, abstractmethod from contextlib import suppress from pathlib import Path from tempfile import mkdtemp from types import ModuleType -from typing import TYPE_CHECKING, Optional, Tuple, Type, Union, cast +from typing import TYPE_CHECKING, cast from sanic.application.constants import Mode from sanic.application.spinner import loading @@ -22,7 +21,6 @@ from sanic.exceptions import SanicException from sanic.helpers import Default from sanic.http.tls.context import CertSimple, SanicSSLContext - try: import trustme @@ -47,7 +45,7 @@ CIPHERS_TLS12 = [ ] -def _make_path(maybe_path: Union[Path, str], tmpdir: Optional[Path]) -> Path: +def _make_path(maybe_path: Path | str, tmpdir: Path | None) -> Path: if isinstance(maybe_path, Path): return maybe_path else: @@ -60,9 +58,7 @@ def _make_path(maybe_path: Union[Path, str], tmpdir: Optional[Path]) -> Path: return path -def get_ssl_context( - app: Sanic, ssl: Optional[ssl.SSLContext] -) -> ssl.SSLContext: +def get_ssl_context(app: Sanic, ssl: ssl.SSLContext | None) -> ssl.SSLContext: if ssl: return ssl @@ -96,16 +92,8 @@ class CertCreator(ABC): if isinstance(self.key, Default) or isinstance(self.cert, Default): self.tmpdir = Path(mkdtemp()) - key = ( - DEFAULT_LOCAL_TLS_KEY - if isinstance(self.key, Default) - else self.key - ) - cert = ( - DEFAULT_LOCAL_TLS_CERT - if isinstance(self.cert, Default) - else self.cert - ) + key = DEFAULT_LOCAL_TLS_KEY if isinstance(self.key, Default) else self.key + cert = DEFAULT_LOCAL_TLS_CERT if isinstance(self.cert, Default) else self.cert self.key_path = _make_path(key, self.tmpdir) self.cert_path = _make_path(cert, self.tmpdir) @@ -126,11 +114,9 @@ class CertCreator(ABC): local_tls_key, local_tls_cert, ) -> CertCreator: - creator: Optional[CertCreator] = None + creator: CertCreator | None = None - cert_creator_options: Tuple[ - Tuple[Type[CertCreator], LocalCertCreator], ... - ] = ( + cert_creator_options: tuple[tuple[type[CertCreator], LocalCertCreator], ...] = ( (MkcertCreator, LocalCertCreator.MKCERT), (TrustmeCreator, LocalCertCreator.TRUSTME), ) @@ -160,8 +146,8 @@ class CertCreator(ABC): @staticmethod def _try_select( app: Sanic, - creator: Optional[CertCreator], - creator_class: Type[CertCreator], + creator: CertCreator | None, + creator_class: type[CertCreator], creator_requirement: LocalCertCreator, creator_requested: LocalCertCreator, local_tls_key, diff --git a/sanic/log.py b/sanic/log.py index 013d9c23..f65848a4 100644 --- a/sanic/log.py +++ b/sanic/log.py @@ -1,13 +1,11 @@ import logging import sys - from enum import Enum from typing import TYPE_CHECKING, Any, Dict from warnings import warn from sanic.helpers import is_atty - # Python 3.11 changed the way Enum formatting works for mixed-in types. if sys.version_info < (3, 11, 0): @@ -19,10 +17,10 @@ else: from enum import StrEnum -LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov - version=1, - disable_existing_loggers=False, - loggers={ +LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = { # no cov + "version": 1, + "disable_existing_loggers": False, + "loggers": { "sanic.root": {"level": "INFO", "handlers": ["console"]}, "sanic.error": { "level": "INFO", @@ -43,7 +41,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov "qualname": "sanic.server", }, }, - handlers={ + "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "generic", @@ -60,7 +58,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov "stream": sys.stdout, }, }, - formatters={ + "formatters": { "generic": { "format": "%(asctime)s [%(process)s] [%(levelname)s] %(message)s", "datefmt": "[%Y-%m-%d %H:%M:%S %z]", @@ -68,12 +66,12 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov }, "access": { "format": "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: " - + "%(request)s %(message)s %(status)s %(byte)s", + "%(request)s %(message)s %(status)s %(byte)s", "datefmt": "[%Y-%m-%d %H:%M:%S %z]", "class": "logging.Formatter", }, }, -) +} """ Defult logging configuration """ diff --git a/sanic/middleware.py b/sanic/middleware.py index e624b5ef..30b9bc03 100644 --- a/sanic/middleware.py +++ b/sanic/middleware.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections import deque from enum import IntEnum, auto from itertools import count -from typing import Deque, Sequence, Union +from typing import Sequence from sanic.models.handler_types import MiddlewareType @@ -69,9 +69,9 @@ class Middleware: @classmethod def convert( cls, - *middleware_collections: Sequence[Union[Middleware, MiddlewareType]], + *middleware_collections: Sequence[Middleware | MiddlewareType], location: MiddlewareLocation, - ) -> Deque[Middleware]: + ) -> deque[Middleware]: """Convert middleware collections to a deque of Middleware objects. Args: diff --git a/sanic/mixins/exceptions.py b/sanic/mixins/exceptions.py index e52efb56..d6e2b70a 100644 --- a/sanic/mixins/exceptions.py +++ b/sanic/mixins/exceptions.py @@ -14,7 +14,7 @@ class ExceptionMixin(metaclass=SanicMeta): def exception( self, *exceptions: Union[Type[Exception], List[Type[Exception]]], - apply: bool = True + apply: bool = True, ) -> Callable: """Decorator used to register an exception handler for the current application or blueprint instance. @@ -79,9 +79,7 @@ class ExceptionMixin(metaclass=SanicMeta): return decorator - def all_exceptions( - self, handler: Callable[..., Any] - ) -> Callable[..., Any]: + def all_exceptions(self, handler: Callable[..., Any]) -> Callable[..., Any]: """Enables the process of creating a global exception handler as a convenience. This following two examples are equivalent: diff --git a/sanic/mixins/listeners.py b/sanic/mixins/listeners.py index 6b547ca9..0894f9cb 100644 --- a/sanic/mixins/listeners.py +++ b/sanic/mixins/listeners.py @@ -120,16 +120,12 @@ class ListenerMixin(metaclass=SanicMeta): if callable(listener_or_event): if event_or_none is None: - raise BadRequest( - "Invalid event registration: Missing event name." - ) + raise BadRequest("Invalid event registration: Missing event name.") return register_listener(listener_or_event, event_or_none) else: return partial(register_listener, event=listener_or_event) - def main_process_start( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def main_process_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the main_process_start event. This event is fired only on the main process and **NOT** on any @@ -151,9 +147,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "main_process_start") - def main_process_ready( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def main_process_ready(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the main_process_ready event. This event is fired only on the main process and **NOT** on any @@ -176,9 +170,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "main_process_ready") - def main_process_stop( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def main_process_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the main_process_stop event. This event is fired only on the main process and **NOT** on any @@ -222,9 +214,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "reload_process_start") - def reload_process_stop( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def reload_process_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the reload_process_stop event. This event is fired only on the reload process and **NOT** on any @@ -293,9 +283,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "after_reload_trigger") - def before_server_start( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def before_server_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the before_server_start event. This event is fired on all worker processes. You should typically @@ -319,9 +307,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "before_server_start") - def after_server_start( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def after_server_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the after_server_start event. This event is fired on all worker processes. You should typically @@ -349,9 +335,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "after_server_start") - def before_server_stop( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def before_server_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the before_server_stop event. This event is fired on all worker processes. This event is fired @@ -376,9 +360,7 @@ class ListenerMixin(metaclass=SanicMeta): """ # noqa: E501 return self.listener(listener, "before_server_stop") - def after_server_stop( - self, listener: ListenerType[Sanic] - ) -> ListenerType[Sanic]: + def after_server_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]: """Decorator for registering a listener for the after_server_stop event. This event is fired on all worker processes. This event is fired diff --git a/sanic/mixins/middleware.py b/sanic/mixins/middleware.py index 358c1b8d..8953548a 100644 --- a/sanic/mixins/middleware.py +++ b/sanic/mixins/middleware.py @@ -25,7 +25,7 @@ class MiddlewareMixin(metaclass=SanicMeta): attach_to: str = "request", apply: bool = True, *, - priority: int = 0 + priority: int = 0, ) -> MiddlewareType: ... @@ -36,7 +36,7 @@ class MiddlewareMixin(metaclass=SanicMeta): attach_to: str = "request", apply: bool = True, *, - priority: int = 0 + priority: int = 0, ) -> Callable[[MiddlewareType], MiddlewareType]: ... @@ -46,7 +46,7 @@ class MiddlewareMixin(metaclass=SanicMeta): attach_to: str = "request", apply: bool = True, *, - priority: int = 0 + priority: int = 0, ) -> Union[MiddlewareType, Callable[[MiddlewareType], MiddlewareType]]: """Decorator for registering middleware. @@ -99,13 +99,9 @@ class MiddlewareMixin(metaclass=SanicMeta): # Detect which way this was called, @middleware or @middleware('AT') if callable(middleware_or_request): - return register_middleware( - middleware_or_request, attach_to=attach_to - ) + return register_middleware(middleware_or_request, attach_to=attach_to) else: - return partial( - register_middleware, attach_to=middleware_or_request - ) + return partial(register_middleware, attach_to=middleware_or_request) def on_request(self, middleware=None, *, priority=0) -> MiddlewareType: """Register a middleware to be called before a request is handled. @@ -157,9 +153,7 @@ class MiddlewareMixin(metaclass=SanicMeta): if callable(middleware): return self.middleware(middleware, "response", priority=priority) else: - return partial( - self.middleware, attach_to="response", priority=priority - ) + return partial(self.middleware, attach_to="response", priority=priority) def finalize_middleware(self) -> None: """Finalize the middleware configuration for the Sanic application. diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index bc194db8..f5e25f0d 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -25,10 +25,7 @@ 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): @@ -815,7 +812,5 @@ 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) diff --git a/sanic/mixins/signals.py b/sanic/mixins/signals.py index f1419dc4..6f1247cf 100644 --- a/sanic/mixins/signals.py +++ b/sanic/mixins/signals.py @@ -1,7 +1,7 @@ from __future__ import annotations from enum import Enum -from typing import Any, Callable, Coroutine, Dict, Optional, Set, Union +from typing import Any, Callable, Coroutine 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: Union[str, Enum], + event: str | Enum, *, apply: bool = True, - condition: Optional[Dict[str, Any]] = None, + condition: dict[str, Any] | None = None, exclusive: bool = True, ) -> Callable[[SignalHandler], SignalHandler]: """ @@ -64,9 +64,9 @@ class SignalMixin(metaclass=SanicMeta): def add_signal( self, - handler: Optional[Callable[..., Any]], + handler: Callable[..., Any] | None, event: str, - condition: Optional[Dict[str, Any]] = None, + condition: dict[str, Any] | None = None, exclusive: bool = True, ) -> Callable[..., Any]: """Registers a signal handler for a specific event. @@ -92,9 +92,7 @@ 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): diff --git a/sanic/mixins/startup.py b/sanic/mixins/startup.py index 54428e7d..7fe8b51b 100644 --- a/sanic/mixins/startup.py +++ b/sanic/mixins/startup.py @@ -2,8 +2,6 @@ from __future__ import annotations import os import platform -import sys - from asyncio import ( AbstractEventLoop, CancelledError, @@ -32,13 +30,7 @@ from typing import ( Any, Callable, ClassVar, - Dict, - List, Mapping, - Optional, - Set, - Tuple, - Type, Union, cast, ) @@ -71,7 +63,6 @@ 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 @@ -79,20 +70,17 @@ if TYPE_CHECKING: SANIC_PACKAGES = ("sanic-routing", "sanic-testing", "sanic-ext") -if sys.version_info < (3, 8): # no cov - HTTPVersion = Union[HTTP, int] -else: # no cov - from typing import Literal +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 @@ -112,8 +100,7 @@ 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: @@ -159,28 +146,28 @@ class StartupMixin(metaclass=SanicMeta): def run( self, - host: Optional[str] = None, - port: Optional[int] = None, + host: str | None = None, + port: int | None = None, *, dev: bool = False, debug: bool = False, - auto_reload: Optional[bool] = None, + auto_reload: bool | None = None, version: HTTPVersion = HTTP.VERSION_1, - ssl: Union[None, SSLContext, dict, str, list, tuple] = None, - sock: Optional[socket] = None, + ssl: None | SSLContext | dict | str | list | tuple = None, + sock: socket | None = None, workers: int = 1, - protocol: Optional[Type[Protocol]] = None, + protocol: type[Protocol] | None = None, backlog: int = 100, register_sys_signals: bool = True, - 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, + 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, motd: bool = True, fast: bool = False, verbosity: int = 0, - motd_display: Optional[Dict[str, str]] = None, + motd_display: dict[str, str] | None = None, auto_tls: bool = False, single_process: bool = False, ) -> None: @@ -289,28 +276,28 @@ class StartupMixin(metaclass=SanicMeta): def prepare( self, - host: Optional[str] = None, - port: Optional[int] = None, + host: str | None = None, + port: int | None = None, *, dev: bool = False, debug: bool = False, - auto_reload: Optional[bool] = None, + auto_reload: bool | None = None, version: HTTPVersion = HTTP.VERSION_1, - ssl: Union[None, SSLContext, dict, str, list, tuple] = None, - sock: Optional[socket] = None, + ssl: None | SSLContext | dict | str | list | tuple = None, + sock: socket | None = None, workers: int = 1, - protocol: Optional[Type[Protocol]] = None, + protocol: type[Protocol] | None = None, backlog: int = 100, register_sys_signals: bool = True, - 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, + 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, motd: bool = True, fast: bool = False, verbosity: int = 0, - motd_display: Optional[Dict[str, str]] = None, + motd_display: dict[str, str] | None = None, coffee: bool = False, auto_tls: bool = False, single_process: bool = False, @@ -385,8 +372,7 @@ 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: @@ -405,9 +391,7 @@ 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: @@ -422,9 +406,7 @@ 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 { @@ -460,9 +442,7 @@ 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 @@ -471,20 +451,20 @@ class StartupMixin(metaclass=SanicMeta): async def create_server( self, - host: Optional[str] = None, - port: Optional[int] = None, + host: str | None = None, + port: int | None = None, *, debug: bool = False, - ssl: Union[None, SSLContext, dict, str, list, tuple] = None, - sock: Optional[socket] = None, - protocol: Optional[Type[Protocol]] = None, + ssl: None | SSLContext | dict | str | list | tuple = None, + sock: socket | None = None, + protocol: type[Protocol] | None = None, backlog: int = 100, - access_log: Optional[bool] = None, - unix: Optional[str] = None, + access_log: bool | None = None, + unix: str | None = None, return_asyncio_server: bool = True, - asyncio_server_kwargs: Optional[Dict[str, Any]] = None, - noisy_exceptions: Optional[bool] = None, - ) -> Optional[AsyncioServer]: + asyncio_server_kwargs: dict[str, Any] | None = None, + noisy_exceptions: bool | None = None, + ) -> AsyncioServer | None: """ Low level API for creating a Sanic Server instance. @@ -558,9 +538,7 @@ 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 { @@ -637,21 +615,21 @@ class StartupMixin(metaclass=SanicMeta): def _helper( self, - host: Optional[str] = None, - port: Optional[int] = None, + host: str | None = None, + port: int | None = None, debug: bool = False, version: HTTPVersion = HTTP.VERSION_1, - ssl: Union[None, SSLContext, dict, str, list, tuple] = None, - sock: Optional[socket] = None, - unix: Optional[str] = None, + ssl: None | SSLContext | dict | str | list | tuple = None, + sock: socket | None = None, + unix: str | None = None, workers: int = 1, - loop: Optional[AbstractEventLoop] = None, - protocol: Type[Protocol] = HttpProtocol, + loop: AbstractEventLoop | None = 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( @@ -726,7 +704,7 @@ class StartupMixin(metaclass=SanicMeta): def motd( self, - server_settings: Optional[Dict[str, Any]] = None, + server_settings: dict[str, Any] | None = None, ) -> None: """Outputs the message of the day (MOTD). @@ -755,8 +733,8 @@ class StartupMixin(metaclass=SanicMeta): MOTD.output(logo, serve_location, display, extra) def get_motd_data( - self, server_settings: Optional[Dict[str, Any]] = None - ) -> Tuple[Dict[str, Any], Dict[str, Any]]: + self, server_settings: dict[str, Any] | None = None + ) -> tuple[dict[str, Any], dict[str, Any]]: """Retrieves the message of the day (MOTD) data. Args: @@ -802,10 +780,7 @@ 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 @@ -844,9 +819,7 @@ class StartupMixin(metaclass=SanicMeta): return f"http://<{location}>" @staticmethod - def get_server_location( - server_settings: Optional[Dict[str, Any]] = None - ) -> str: + def get_server_location(server_settings: dict[str, Any] | None = None) -> str: """Using the server settings, retrieve the server location. Args: @@ -880,11 +853,11 @@ class StartupMixin(metaclass=SanicMeta): @staticmethod def get_address( - host: Optional[str], - port: Optional[int], + host: str | None, + port: int | None, 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: @@ -913,9 +886,7 @@ 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 @@ -942,10 +913,10 @@ class StartupMixin(metaclass=SanicMeta): @classmethod def serve( cls, - primary: Optional[Sanic] = None, + primary: Sanic | None = None, *, - app_loader: Optional[AppLoader] = None, - factory: Optional[Callable[[], Sanic]] = None, + app_loader: AppLoader | None = None, + factory: Callable[[], Sanic] | None = None, ) -> None: """Serve one or more Sanic applications. @@ -996,9 +967,7 @@ 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 @@ -1040,7 +1009,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, @@ -1092,7 +1061,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) @@ -1101,9 +1070,7 @@ 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 = { @@ -1134,9 +1101,7 @@ 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") @@ -1164,7 +1129,7 @@ class StartupMixin(metaclass=SanicMeta): os._exit(exit_code) @classmethod - def serve_single(cls, primary: Optional[Sanic] = None) -> None: + def serve_single(cls, primary: Sanic | None = None) -> None: """Serve a single process of a Sanic application. Similar to `serve`, but only serves a single process. When used, @@ -1242,9 +1207,7 @@ 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") @@ -1263,7 +1226,7 @@ class StartupMixin(metaclass=SanicMeta): self, primary: Sanic, _, - apps: List[Sanic], + apps: list[Sanic], ) -> None: for app in apps: if ( @@ -1308,7 +1271,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), diff --git a/sanic/mixins/static.py b/sanic/mixins/static.py index 88f7ab26..291399bf 100644 --- a/sanic/mixins/static.py +++ b/sanic/mixins/static.py @@ -205,17 +205,12 @@ class StaticHandleMixin(metaclass=SanicMeta): ) uri = uri.rstrip("/") uri += "/<__file_uri__:path>" - elif static.resource_type == "file" and not path.isfile( - file_or_directory - ): + elif static.resource_type == "file" and not path.isfile(file_or_directory): raise TypeError( - "Resource type improperly identified as file. " - f"'{file_or_directory}'" + "Resource type improperly identified as file. " f"'{file_or_directory}'" ) elif static.resource_type != "file": - raise ValueError( - "The resource_type should be set to 'file' or 'dir'" - ) + raise ValueError("The resource_type should be set to 'file' or 'dir'") # special prefix for static files # if not static.name.startswith("_static_"): @@ -278,9 +273,7 @@ class StaticHandleMixin(metaclass=SanicMeta): response = await validate_file(request.headers, modified_since) if response: return response - headers["Last-Modified"] = formatdate( - modified_since, usegmt=True - ) + headers["Last-Modified"] = formatdate(modified_since, usegmt=True) _range = None if use_content_range: _range = None diff --git a/sanic/models/asgi.py b/sanic/models/asgi.py index 074f39bc..48133b61 100644 --- a/sanic/models/asgi.py +++ b/sanic/models/asgi.py @@ -1,12 +1,10 @@ import asyncio - from typing import Any, Awaitable, Callable, MutableMapping, Optional, Union from sanic.exceptions import BadRequest from sanic.models.protocol_types import TransportProtocol from sanic.server.websockets.connection import WebSocketConnection - ASGIScope = MutableMapping[str, Any] ASGIMessage = MutableMapping[str, Any] ASGISend = Callable[[ASGIMessage], Awaitable[None]] @@ -49,9 +47,7 @@ class MockProtocol: # no cov class MockTransport(TransportProtocol): # no cov _protocol: Optional[MockProtocol] - def __init__( - self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend - ) -> None: + def __init__(self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend) -> None: self.scope = scope self._receive = receive self._send = send @@ -63,9 +59,7 @@ class MockTransport(TransportProtocol): # no cov self._protocol = MockProtocol(self, self.loop) return self._protocol - def get_extra_info( - self, info: str, default=None - ) -> Optional[Union[str, bool]]: + def get_extra_info(self, info: str, default=None) -> Optional[Union[str, bool]]: if info == "peername": return self.scope.get("client") elif info == "sslcontext": diff --git a/sanic/models/handler_types.py b/sanic/models/handler_types.py index 7d123d0b..b9b5bfc1 100644 --- a/sanic/models/handler_types.py +++ b/sanic/models/handler_types.py @@ -2,11 +2,9 @@ from asyncio.events import AbstractEventLoop from typing import Any, Callable, Coroutine, Optional, TypeVar, Union import sanic - from sanic import request from sanic.response import BaseHTTPResponse, HTTPResponse - Sanic = TypeVar("Sanic", bound="sanic.Sanic") Request = TypeVar("Request", bound="request.Request") @@ -14,9 +12,7 @@ MiddlewareResponse = Union[ Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]] ] RequestMiddlewareType = Callable[[Request], MiddlewareResponse] -ResponseMiddlewareType = Callable[ - [Request, BaseHTTPResponse], MiddlewareResponse -] +ResponseMiddlewareType = Callable[[Request, BaseHTTPResponse], MiddlewareResponse] ErrorMiddlewareType = Callable[ [Request, BaseException], Optional[Coroutine[Any, Any, None]] ] diff --git a/sanic/models/http_types.py b/sanic/models/http_types.py index 595eaf0e..fc86b6ca 100644 --- a/sanic/models/http_types.py +++ b/sanic/models/http_types.py @@ -2,15 +2,14 @@ from __future__ import annotations from base64 import b64decode from dataclasses import dataclass, field -from typing import Optional @dataclass() class Credentials: - auth_type: Optional[str] - token: Optional[str] - _username: Optional[str] = field(default=None) - _password: Optional[str] = field(default=None) + auth_type: str | None + token: str | None + _username: str | None = field(default=None) + _password: str | None = field(default=None) def __post_init__(self): if self._auth_is_basic: diff --git a/sanic/models/protocol_types.py b/sanic/models/protocol_types.py index 61924a6c..7d40a44e 100644 --- a/sanic/models/protocol_types.py +++ b/sanic/models/protocol_types.py @@ -1,36 +1,30 @@ from __future__ import annotations -import sys - from asyncio import BaseTransport -from typing import TYPE_CHECKING, Any, AnyStr, Optional - +from typing import TYPE_CHECKING if TYPE_CHECKING: from sanic.http.constants import HTTP from sanic.models.asgi import ASGIScope -if sys.version_info < (3, 8): - Range = Any - HTMLProtocol = Any -else: - # Protocol is a 3.8+ feature - from typing import Protocol +from typing import Protocol - class HTMLProtocol(Protocol): - def __html__(self) -> AnyStr: - ... - def _repr_html_(self) -> AnyStr: - ... +class HTMLProtocol(Protocol): + def __html__(self) -> str: + ... - class Range(Protocol): - start: Optional[int] - end: Optional[int] - size: Optional[int] - total: Optional[int] - __slots__ = () + def _repr_html_(self) -> str: + ... + + +class Range(Protocol): + start: int | None + end: int | None + size: int | None + total: int | None + __slots__ = () class TransportProtocol(BaseTransport): diff --git a/sanic/models/server_types.py b/sanic/models/server_types.py index fe9d3d1f..d5d1c30d 100644 --- a/sanic/models/server_types.py +++ b/sanic/models/server_types.py @@ -2,7 +2,7 @@ from __future__ import annotations from ssl import SSLContext, SSLObject from types import SimpleNamespace -from typing import Any, Dict, List, Optional, Tuple +from typing import Any from sanic.models.protocol_types import TransportProtocol @@ -35,21 +35,17 @@ class ConnInfo: def __init__(self, transport: TransportProtocol, unix=None): self.ctx = SimpleNamespace() self.lost = False - self.peername: Optional[Tuple[str, int]] = None + self.peername: tuple[str, int] | None = None self.server = self.client = "" self.server_port = self.client_port = 0 self.client_ip = "" self.sockname = addr = transport.get_extra_info("sockname") self.ssl = False self.server_name = "" - self.cert: Dict[str, Any] = {} - self.network_paths: List[Any] = [] - sslobj: Optional[SSLObject] = transport.get_extra_info( - "ssl_object" - ) # type: ignore - sslctx: Optional[SSLContext] = transport.get_extra_info( - "ssl_context" - ) # type: ignore + self.cert: dict[str, Any] = {} + self.network_paths: list[Any] = [] + sslobj: SSLObject | None = transport.get_extra_info("ssl_object") # type: ignore + sslctx: SSLContext | None = transport.get_extra_info("ssl_context") # type: ignore if sslobj: self.ssl = True self.server_name = getattr(sslobj, "sanic_server_name", None) or "" diff --git a/sanic/pages/css.py b/sanic/pages/css.py index 8852d31a..b74e3733 100644 --- a/sanic/pages/css.py +++ b/sanic/pages/css.py @@ -2,7 +2,6 @@ from abc import ABCMeta from pathlib import Path from typing import Optional - CURRENT_DIR = Path(__file__).parent @@ -28,8 +27,6 @@ class CSS(ABCMeta): Page.STYLE += attrs.get("STYLE_APPEND", "") # Combine with all ancestor styles Page.CSS = "".join( - Class.STYLE - for Class in reversed(Page.__mro__) - if type(Class) is CSS + Class.STYLE for Class in reversed(Page.__mro__) if type(Class) is CSS ) return Page diff --git a/sanic/pages/directory_page.py b/sanic/pages/directory_page.py index 7d0faf5a..376b5595 100644 --- a/sanic/pages/directory_page.py +++ b/sanic/pages/directory_page.py @@ -1,25 +1,17 @@ -import sys - -from typing import Dict, Iterable +from typing import Iterable, TypedDict from html5tagger import E from .base import BasePage -if sys.version_info < (3, 8): # no cov - FileInfo = Dict +class FileInfo(TypedDict): + """Type for file info.""" -else: - from typing import TypedDict - - class FileInfo(TypedDict): - """Type for file info.""" - - icon: str - file_name: str - file_access: str - file_size: str + icon: str + file_name: str + file_access: str + file_size: str class DirectoryPage(BasePage): # no cov @@ -27,9 +19,7 @@ class DirectoryPage(BasePage): # no cov TITLE = "Directory Viewer" - def __init__( - self, files: Iterable[FileInfo], url: str, debug: bool - ) -> None: + def __init__(self, files: Iterable[FileInfo], url: str, debug: bool) -> None: super().__init__(debug) self.files = files self.url = url diff --git a/sanic/pages/error.py b/sanic/pages/error.py index 11580db4..75b6cc89 100644 --- a/sanic/pages/error.py +++ b/sanic/pages/error.py @@ -1,7 +1,6 @@ from typing import Any, Mapping import tracerite.html - from html5tagger import E from tracerite import html_traceback, inspector @@ -9,7 +8,6 @@ from sanic.request import Request from .base import BasePage - # Avoid showing the request in the traceback variable inspectors inspector.blacklist_types += (Request,) @@ -39,9 +37,7 @@ class ErrorPage(BasePage): if name.islower(): name = name.title() self.TITLE = f"Application {name} cannot handle your request" - self.HEADING = E("Application ").strong(name)( - " cannot handle your request" - ) + self.HEADING = E("Application ").strong(name)(" cannot handle your request") self.title = title self.text = text self.request = request @@ -60,9 +56,7 @@ class ErrorPage(BasePage): # Show context details if available on the exception context = getattr(self.exc, "context", None) if context: - self._key_value_table( - "Issue context", "exception-context", context - ) + self._key_value_table("Issue context", "exception-context", context) if not debug: with self.doc.div(id="enduser"): @@ -74,19 +68,13 @@ class ErrorPage(BasePage): # Show extra details if available on the exception extra = getattr(self.exc, "extra", None) if extra: - self._key_value_table( - "Issue extra data", "exception-extra", extra - ) + self._key_value_table("Issue extra data", "exception-extra", extra) - self.doc.summary( - "Details for developers (Sanic debug mode only)" - ) + self.doc.summary("Details for developers (Sanic debug mode only)") if self.exc: with self.doc.div(class_="exception-wrapper"): self.doc.h2(f"Exception in {route_name}:") - self.doc( - html_traceback(self.exc, include_js_css=False) - ) + self.doc(html_traceback(self.exc, include_js_css=False)) self._key_value_table( f"{self.request.method} {self.request.path}", @@ -106,6 +94,4 @@ class ErrorPage(BasePage): value = str(value) except Exception: value = E.em("Unable to display value") - self.doc.dt.span(key, class_="nobr key").span(": ").dd( - value - ) + self.doc.dt.span(key, class_="nobr key").span(": ").dd(value) diff --git a/sanic/request/__init__.py b/sanic/request/__init__.py index 3321f234..37037937 100644 --- a/sanic/request/__init__.py +++ b/sanic/request/__init__.py @@ -2,7 +2,6 @@ from .form import File, parse_multipart_form from .parameters import RequestParameters from .types import Request - __all__ = ( "File", "parse_multipart_form", diff --git a/sanic/request/form.py b/sanic/request/form.py index 3f3a4166..805f3ad9 100644 --- a/sanic/request/form.py +++ b/sanic/request/form.py @@ -2,7 +2,6 @@ from __future__ import annotations import email.utils import unicodedata - from typing import NamedTuple from urllib.parse import unquote @@ -61,9 +60,7 @@ def parse_multipart_form(body, boundary): colon_index = form_line.index(":") idx = colon_index + 2 form_header_field = form_line[0:colon_index].lower() - form_header_value, form_parameters = parse_content_header( - form_line[idx:] - ) + form_header_value, form_parameters = parse_content_header(form_line[idx:]) if form_header_field == "content-disposition": field_name = form_parameters.get("name") @@ -98,9 +95,7 @@ def parse_multipart_form(body, boundary): else: fields[field_name] = [value] else: - form_file = File( - type=content_type, name=file_name, body=post_data - ) + form_file = File(type=content_type, name=file_name, body=post_data) if field_name in files: files[field_name].append(form_file) else: diff --git a/sanic/request/parameters.py b/sanic/request/parameters.py index 0ae6b087..9e9e7ad7 100644 --- a/sanic/request/parameters.py +++ b/sanic/request/parameters.py @@ -1,12 +1,12 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any class RequestParameters(dict): """Hosts a dict with lists as values where get returns the first value of the list and getlist returns the whole shebang""" # noqa: E501 - def get(self, name: str, default: Optional[Any] = None) -> Optional[Any]: + def get(self, name: str, default: Any = None) -> Any: """Return the first value, either the default or actual Args: @@ -18,9 +18,7 @@ class RequestParameters(dict): """ # noqa: E501 return super().get(name, [default])[0] - def getlist( - self, name: str, default: Optional[Any] = None - ) -> Optional[Any]: + def getlist(self, name: str, default: Any = None) -> Any: """Return the entire list Args: diff --git a/sanic/request/types.py b/sanic/request/types.py index 45fbd208..294a46dc 100644 --- a/sanic/request/types.py +++ b/sanic/request/types.py @@ -7,13 +7,7 @@ from types import SimpleNamespace from typing import ( TYPE_CHECKING, Any, - DefaultDict, - Dict, Generic, - List, - Optional, - Tuple, - Union, cast, ) @@ -25,14 +19,12 @@ from sanic.http.stream import Stream from sanic.models.asgi import ASGIScope from sanic.models.http_types import Credentials - if TYPE_CHECKING: from sanic.app import Sanic from sanic.config import Config from sanic.server import ConnInfo import uuid - from collections import defaultdict from urllib.parse import parse_qs, parse_qsl, urlunparse @@ -66,7 +58,6 @@ from sanic.response import BaseHTTPResponse, HTTPResponse from .form import parse_multipart_form from .parameters import RequestParameters - try: from ujson import loads as json_loads # type: ignore except ImportError: @@ -161,8 +152,8 @@ class Request(Generic[sanic_type, ctx_type]): except HttpParserInvalidURLError: url = url_bytes.decode(errors="backslashreplace") raise BadURL(f"Bad URL: {url}") - self._id: Optional[Union[uuid.UUID, str, int]] = None - self._name: Optional[str] = None + self._id: uuid.UUID | str | int | None = None + self._name: str | None = None self._stream_id = stream_id self.app = app @@ -174,29 +165,29 @@ class Request(Generic[sanic_type, ctx_type]): # Init but do not inhale self.body = b"" - self.conn_info: Optional[ConnInfo] = None - self._ctx: Optional[ctx_type] = None - self.parsed_accept: Optional[AcceptList] = None - self.parsed_args: DefaultDict[ - Tuple[bool, bool, str, str], RequestParameters + self.conn_info: ConnInfo | None = None + self._ctx: ctx_type | None = None + self.parsed_accept: AcceptList | None = None + self.parsed_args: defaultdict[ + tuple[bool, bool, str, str], RequestParameters ] = defaultdict(RequestParameters) - self.parsed_cookies: Optional[RequestParameters] = None - self.parsed_credentials: Optional[Credentials] = None - self.parsed_files: Optional[RequestParameters] = None - self.parsed_form: Optional[RequestParameters] = None - self.parsed_forwarded: Optional[Options] = None + self.parsed_cookies: RequestParameters | None = None + self.parsed_credentials: Credentials | None = None + self.parsed_files: RequestParameters | None = None + self.parsed_form: RequestParameters | None = None + self.parsed_forwarded: Options | None = None self.parsed_json = None - self.parsed_not_grouped_args: DefaultDict[ - Tuple[bool, bool, str, str], List[Tuple[str, str]] + self.parsed_not_grouped_args: defaultdict[ + tuple[bool, bool, str, str], list[tuple[str, str]] ] = defaultdict(list) - self.parsed_token: Optional[str] = None + self.parsed_token: str | None = None self._request_middleware_started = False self._response_middleware_started = False self.responded: bool = False - self.route: Optional[Route] = None - self.stream: Optional[Stream] = None - self._match_info: Dict[str, Any] = {} - self._protocol: Optional[BaseProtocol] = None + self.route: Route | None = None + self.stream: Stream | None = None + self._match_info: dict[str, Any] = {} + self._protocol: BaseProtocol | None = None def __repr__(self): class_name = self.__class__.__name__ @@ -251,7 +242,7 @@ class Request(Generic[sanic_type, ctx_type]): return request @classmethod - def generate_id(*_) -> Union[uuid.UUID, str, int]: + def generate_id(*_) -> uuid.UUID | str | int: """Generate a unique ID for the request. This method is called to generate a unique ID for each request. @@ -288,9 +279,7 @@ class Request(Generic[sanic_type, ctx_type]): int: The HTTP/3 stream ID. """ if self.protocol.version is not HTTP.VERSION_3: - raise ServerError( - "Stream ID is only a property of a HTTP/3 request" - ) + raise ServerError("Stream ID is only a property of a HTTP/3 request") return self._stream_id def reset_response(self) -> None: @@ -305,10 +294,7 @@ class Request(Generic[sanic_type, ctx_type]): sent. """ try: - if ( - self.stream is not None - and self.stream.stage is not Stage.HANDLER - ): + if self.stream is not None and self.stream.stage is not Stage.HANDLER: raise ServerError( "Cannot reset response because previous response was sent." ) @@ -320,11 +306,11 @@ class Request(Generic[sanic_type, ctx_type]): async def respond( self, - response: Optional[BaseHTTPResponse] = None, + response: BaseHTTPResponse | None = None, *, status: int = 200, - headers: Optional[Union[Header, Dict[str, str]]] = None, - content_type: Optional[str] = None, + headers: Header | dict[str, str] | None = None, + content_type: str | None = None, ): """Respond to the request without returning. @@ -424,7 +410,7 @@ class Request(Generic[sanic_type, ctx_type]): self.body = b"".join([data async for data in self.stream]) @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """The route name In the following pattern: @@ -443,7 +429,7 @@ class Request(Generic[sanic_type, ctx_type]): return None @property - def endpoint(self) -> Optional[str]: + def endpoint(self) -> str | None: """Alias of `sanic.request.Request.name` Returns: @@ -452,7 +438,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.name @property - def uri_template(self) -> Optional[str]: + def uri_template(self) -> str | None: """The defined URI template Returns: @@ -494,7 +480,7 @@ class Request(Generic[sanic_type, ctx_type]): return bytes(reqline) @property - def id(self) -> Optional[Union[uuid.UUID, str, int]]: + def id(self) -> uuid.UUID | str | int | None: """A request ID passed from the client, or generated from the backend. By default, this will look in a request header defined at: @@ -593,7 +579,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.parsed_accept @property - def token(self) -> Optional[str]: + def token(self) -> str | None: """Attempt to return the auth header token. Returns: @@ -608,7 +594,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.parsed_token @property - def credentials(self) -> Optional[Credentials]: + def credentials(self) -> Credentials | None: """Attempt to return the auth header value. Covers NoAuth, Basic Auth, Bearer Token, Api Token authentication @@ -631,9 +617,7 @@ class Request(Generic[sanic_type, ctx_type]): pass return self.parsed_credentials - def get_form( - self, keep_blank_values: bool = False - ) -> Optional[RequestParameters]: + def get_form(self, keep_blank_values: bool = False) -> RequestParameters | None: """Method to extract and parse the form data from a request. Args: @@ -644,9 +628,7 @@ class Request(Generic[sanic_type, ctx_type]): """ # noqa: E501 self.parsed_form = RequestParameters() self.parsed_files = RequestParameters() - content_type = self.headers.getone( - "content-type", DEFAULT_HTTP_CONTENT_TYPE - ) + content_type = self.headers.getone("content-type", DEFAULT_HTTP_CONTENT_TYPE) content_type, parameters = parse_content_header(content_type) try: if content_type == "application/x-www-form-urlencoded": @@ -670,7 +652,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.parsed_form @property - def form(self) -> Optional[RequestParameters]: + def form(self) -> RequestParameters | None: """The request body parsed as form data Returns: @@ -682,7 +664,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.parsed_form @property - def files(self) -> Optional[RequestParameters]: + def files(self) -> RequestParameters | None: """The request body parsed as uploaded files Returns: @@ -744,9 +726,7 @@ class Request(Generic[sanic_type, ctx_type]): ) ) - return self.parsed_args[ - (keep_blank_values, strict_parsing, encoding, errors) - ] + return self.parsed_args[(keep_blank_values, strict_parsing, encoding, errors)] args = property(get_args) """Convenience property to access `Request.get_args` with default values. @@ -836,7 +816,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.headers.getone("content-type", DEFAULT_HTTP_CONTENT_TYPE) @property - def match_info(self) -> Dict[str, Any]: + def match_info(self) -> dict[str, Any]: """Matched path parameters after resolving route Returns: @@ -867,7 +847,7 @@ class Request(Generic[sanic_type, ctx_type]): return self.conn_info.client_port if self.conn_info else 0 @property - def socket(self) -> Union[Tuple[str, int], Tuple[None, None]]: + def socket(self) -> tuple[str, int] | tuple[None, None]: """Information about the connected socket if available Returns: @@ -891,7 +871,7 @@ class Request(Generic[sanic_type, ctx_type]): return self._parsed_url.path.decode("utf-8") @property - def network_paths(self) -> Optional[List[Any]]: + def network_paths(self) -> list[Any] | None: """Access the network paths if available Returns: @@ -999,9 +979,7 @@ class Request(Generic[sanic_type, ctx_type]): server_name = self.app.config.get("SERVER_NAME") if server_name: return server_name.split("//", 1)[-1].split("/", 1)[0] - return str( - self.forwarded.get("host") or self.headers.getone("host", "") - ) + return str(self.forwarded.get("host") or self.headers.getone("host", "")) @property def server_name(self) -> str: diff --git a/sanic/response/__init__.py b/sanic/response/__init__.py index 99b93075..2a597cab 100644 --- a/sanic/response/__init__.py +++ b/sanic/response/__init__.py @@ -17,7 +17,6 @@ from .types import ( json_dumps, ) - __all__ = ( "BaseHTTPResponse", "HTTPResponse", diff --git a/sanic/response/convenience.py b/sanic/response/convenience.py index 13f7ee65..cbb68305 100644 --- a/sanic/response/convenience.py +++ b/sanic/response/convenience.py @@ -6,7 +6,7 @@ from mimetypes import guess_type from os import path from pathlib import PurePath from time import time -from typing import Any, AnyStr, Callable, Dict, Optional, Union +from typing import Any, AnyStr, Callable from urllib.parse import quote_plus from sanic.compat import Header, open_async, stat_async @@ -18,9 +18,7 @@ from sanic.models.protocol_types import HTMLProtocol, Range from .types import HTTPResponse, JSONResponse, ResponseStream -def empty( - status: int = 204, headers: Optional[Dict[str, str]] = None -) -> HTTPResponse: +def empty(status: int = 204, headers: dict[str, str] | None = None) -> HTTPResponse: """Returns an empty response to the client. Args: @@ -36,9 +34,9 @@ def empty( def json( body: Any, status: int = 200, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, content_type: str = "application/json", - dumps: Optional[Callable[..., str]] = None, + dumps: Callable[..., str] | None = None, **kwargs: Any, ) -> JSONResponse: """Returns response object with body in json format. @@ -67,7 +65,7 @@ def json( def text( body: str, status: int = 200, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, content_type: str = "text/plain; charset=utf-8", ) -> HTTPResponse: """Returns response object with body in text format. @@ -85,19 +83,15 @@ def text( TypeError: If the body is not a string. """ # noqa: E501 if not isinstance(body, str): - raise TypeError( - f"Bad body type. Expected str, got {type(body).__name__})" - ) + raise TypeError(f"Bad body type. Expected str, got {type(body).__name__})") - return HTTPResponse( - body, status=status, headers=headers, content_type=content_type - ) + return HTTPResponse(body, status=status, headers=headers, content_type=content_type) def raw( - body: Optional[AnyStr], + body: AnyStr | None, status: int = 200, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, content_type: str = DEFAULT_HTTP_CONTENT_TYPE, ) -> HTTPResponse: """Returns response object without encoding the body. @@ -120,9 +114,9 @@ def raw( def html( - body: Union[str, bytes, HTMLProtocol], + body: str | bytes | HTMLProtocol, status: int = 200, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, ) -> HTTPResponse: """Returns response object with body in html format. @@ -151,8 +145,8 @@ def html( async def validate_file( - request_headers: Header, last_modified: Union[datetime, float, int] -) -> Optional[HTTPResponse]: + request_headers: Header, last_modified: datetime | float | int +) -> HTTPResponse | None: """Validate file based on request headers. Args: @@ -170,8 +164,7 @@ async def validate_file( if_modified_since = parsedate_to_datetime(if_modified_since) except (TypeError, ValueError): logger.warning( - "Ignorning invalid If-Modified-Since header received: " "'%s'", - if_modified_since, + f"Ignoring invalid If-Modified-Since header received: '{if_modified_since}'" ) return None if not isinstance(last_modified, datetime): @@ -179,18 +172,14 @@ async def validate_file( float(last_modified), tz=timezone.utc ).replace(microsecond=0) - if ( - last_modified.utcoffset() is None - and if_modified_since.utcoffset() is not None - ): + if last_modified.utcoffset() is None and if_modified_since.utcoffset() is not None: logger.warning( "Cannot compare tz-aware and tz-naive datetimes. To avoid " "this conflict Sanic is converting last_modified to UTC." ) last_modified.replace(tzinfo=timezone.utc) elif ( - last_modified.utcoffset() is not None - and if_modified_since.utcoffset() is None + last_modified.utcoffset() is not None and if_modified_since.utcoffset() is None ): logger.warning( "Cannot compare tz-aware and tz-naive datetimes. To avoid " @@ -204,17 +193,17 @@ async def validate_file( async def file( - location: Union[str, PurePath], + location: str | PurePath, status: int = 200, - request_headers: Optional[Header] = None, + request_headers: Header | None = None, validate_when_requested: bool = True, - mime_type: Optional[str] = None, - headers: Optional[Dict[str, str]] = None, - filename: Optional[str] = None, - last_modified: Optional[Union[datetime, float, int, Default]] = _default, - max_age: Optional[Union[float, int]] = None, - no_store: Optional[bool] = None, - _range: Optional[Range] = None, + mime_type: str | None = None, + headers: dict[str, str] | None = None, + filename: str | None = None, + last_modified: datetime | float | int | Default | None = _default, + max_age: float | int | None = None, + no_store: bool | None = None, + _range: Range | None = None, ) -> HTTPResponse: """Return a response object with file data. @@ -241,25 +230,17 @@ async def file( stat = await stat_async(location) last_modified = stat.st_mtime - if ( - validate_when_requested - and request_headers is not None - and last_modified - ): + if validate_when_requested and request_headers is not None and last_modified: response = await validate_file(request_headers, last_modified) if response: return response headers = headers or {} if last_modified: - headers.setdefault( - "Last-Modified", formatdate(last_modified, usegmt=True) - ) + headers.setdefault("Last-Modified", formatdate(last_modified, usegmt=True)) if filename: - headers.setdefault( - "Content-Disposition", f'attachment; filename="{filename}"' - ) + headers.setdefault("Content-Disposition", f'attachment; filename="{filename}"') if no_store: cache_control = "no-store" @@ -301,7 +282,7 @@ async def file( def redirect( to: str, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, status: int = 302, content_type: str = "text/html; charset=utf-8", ) -> HTTPResponse: @@ -324,19 +305,17 @@ def redirect( # According to RFC 7231, a relative URI is now permitted. headers["Location"] = safe_to - return HTTPResponse( - status=status, headers=headers, content_type=content_type - ) + return HTTPResponse(status=status, headers=headers, content_type=content_type) async def file_stream( - location: Union[str, PurePath], + location: str | PurePath, status: int = 200, chunk_size: int = 4096, - mime_type: Optional[str] = None, - headers: Optional[Dict[str, str]] = None, - filename: Optional[str] = None, - _range: Optional[Range] = None, + mime_type: str | None = None, + headers: dict[str, str] | None = None, + filename: str | None = None, + _range: Range | None = None, ) -> ResponseStream: """Return a streaming response object with file data. @@ -358,9 +337,7 @@ async def file_stream( """ # noqa: E501 headers = headers or {} if filename: - headers.setdefault( - "Content-Disposition", f'attachment; filename="{filename}"' - ) + headers.setdefault("Content-Disposition", f'attachment; filename="{filename}"') filename = filename or path.split(location)[-1] mime_type = mime_type or guess_type(filename)[0] or "text/plain" if _range: diff --git a/sanic/response/types.py b/sanic/response/types.py index 0a8af725..558fda6e 100644 --- a/sanic/response/types.py +++ b/sanic/response/types.py @@ -8,12 +8,8 @@ from typing import ( AnyStr, Callable, Coroutine, - Dict, Iterator, - Optional, - Tuple, TypeVar, - Union, ) from sanic.compat import Header @@ -28,7 +24,6 @@ from sanic.helpers import ( ) from sanic.http import Http - if TYPE_CHECKING: from sanic.asgi import ASGIApp from sanic.http.http3 import HTTPReceiver @@ -66,18 +61,18 @@ class BaseHTTPResponse: def __init__(self): self.asgi: bool = False - self.body: Optional[bytes] = None - self.content_type: Optional[str] = None - self.stream: Optional[Union[Http, ASGIApp, HTTPReceiver]] = None + self.body: bytes | None = None + self.content_type: str | None = None + self.stream: Http | ASGIApp | HTTPReceiver | None = None self.status: int = None self.headers = Header({}) - self._cookies: Optional[CookieJar] = None + self._cookies: CookieJar | None = None def __repr__(self): class_name = self.__class__.__name__ return f"<{class_name}: {self.status} {self.content_type}>" - def _encode_body(self, data: Optional[AnyStr]): + def _encode_body(self, data: AnyStr | None): if data is None: return b"" return ( @@ -98,7 +93,7 @@ class BaseHTTPResponse: return self._cookies @property - def processed_headers(self) -> Iterator[Tuple[bytes, bytes]]: + def processed_headers(self) -> Iterator[tuple[bytes, bytes]]: """Obtain a list of header tuples encoded in bytes for sending. Add and remove headers based on status and content_type. @@ -119,8 +114,8 @@ class BaseHTTPResponse: async def send( self, - data: Optional[AnyStr] = None, - end_stream: Optional[bool] = None, + data: AnyStr | None = None, + end_stream: bool | None = None, ) -> None: """Send any pending response headers and the given data as body. @@ -157,14 +152,14 @@ class BaseHTTPResponse: value: str, *, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, secure: bool = True, - max_age: Optional[int] = None, - expires: Optional[datetime] = None, + max_age: int | None = None, + expires: datetime | None = None, httponly: bool = False, - samesite: Optional[SameSite] = "Lax", + samesite: SameSite | None = "Lax", partitioned: bool = False, - comment: Optional[str] = None, + comment: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, ) -> Cookie: @@ -211,7 +206,7 @@ class BaseHTTPResponse: key: str, *, path: str = "/", - domain: Optional[str] = None, + domain: str | None = None, host_prefix: bool = False, secure_prefix: bool = False, ) -> None: @@ -255,14 +250,14 @@ class HTTPResponse(BaseHTTPResponse): def __init__( self, - body: Optional[Any] = None, + body: Any | None = None, status: int = 200, - headers: Optional[Union[Header, Dict[str, str]]] = None, - content_type: Optional[str] = None, + headers: Header | dict[str, str] | None = None, + content_type: str | None = None, ): super().__init__() - self.content_type: Optional[str] = content_type + self.content_type: str | None = content_type self.body = self._encode_body(body) self.status = status self.headers = Header(headers or {}) @@ -306,11 +301,11 @@ class JSONResponse(HTTPResponse): def __init__( self, - body: Optional[Any] = None, + body: Any | None = None, status: int = 200, - headers: Optional[Union[Header, Dict[str, str]]] = None, + headers: Header | dict[str, str] | None = None, content_type: str = "application/json", - dumps: Optional[Callable[..., str]] = None, + dumps: Callable[..., str] | None = None, **kwargs: Any, ): self._initialized = False @@ -337,7 +332,7 @@ class JSONResponse(HTTPResponse): ) @property - def raw_body(self) -> Optional[Any]: + def raw_body(self) -> Any | None: """Returns the raw body, as long as body has not been manually set previously. NOTE: This object should not be mutated, as it will not be @@ -355,13 +350,11 @@ class JSONResponse(HTTPResponse): @raw_body.setter def raw_body(self, value: Any): self._body_manually_set = False - self._body = self._encode_body( - self._use_dumps(value, **self._use_dumps_kwargs) - ) + self._body = self._encode_body(self._use_dumps(value, **self._use_dumps_kwargs)) self._raw_body = value @property # type: ignore - def body(self) -> Optional[bytes]: # type: ignore + def body(self) -> bytes | None: # type: ignore """Returns the response body. Returns: @@ -370,7 +363,7 @@ class JSONResponse(HTTPResponse): return self._body @body.setter - def body(self, value: Optional[bytes]): + def body(self, value: bytes | None): self._body = value if not self._initialized: return @@ -379,7 +372,7 @@ class JSONResponse(HTTPResponse): def set_body( self, body: Any, - dumps: Optional[Callable[..., str]] = None, + dumps: Callable[..., str] | None = None, **dumps_kwargs: Any, ) -> None: """Set the response body to the given value, using the given dumps function @@ -489,9 +482,7 @@ class JSONResponse(HTTPResponse): self._check_body_not_manually_set() if not isinstance(self._raw_body, (list, dict)): - raise SanicException( - "Cannot pop from a non-list and non-dict object." - ) + raise SanicException("Cannot pop from a non-list and non-dict object.") if isinstance(default, Default): value = self._raw_body.pop(key) @@ -526,12 +517,12 @@ class ResponseStream: def __init__( self, streaming_fn: Callable[ - [Union[BaseHTTPResponse, ResponseStream]], + [BaseHTTPResponse | ResponseStream], Coroutine[Any, Any, None], ], status: int = 200, - headers: Optional[Union[Header, Dict[str, str]]] = None, - content_type: Optional[str] = None, + headers: Header | dict[str, str] | None = None, + content_type: str | None = None, ): if headers is None: headers = Header() @@ -541,8 +532,8 @@ class ResponseStream: self.status = status self.headers = headers or Header() self.content_type = content_type - self.request: Optional[Request] = None - self._cookies: Optional[CookieJar] = None + self.request: Request | None = None + self._cookies: CookieJar | None = None async def write(self, message: str): await self.response.send(message) diff --git a/sanic/router.py b/sanic/router.py index 4cf66fe2..5dc330bb 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -2,7 +2,7 @@ from __future__ import annotations from functools import lru_cache from inspect import signature -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Iterable from uuid import UUID from sanic_routing import BaseRouter @@ -15,7 +15,6 @@ from sanic.errorpages import check_error_format from sanic.exceptions import MethodNotAllowed, NotFound, SanicException from sanic.models.handler_types import RouteHandler - ROUTER_CACHE_SIZE = 1024 ALLOWED_LABELS = ("__file_uri__",) @@ -27,8 +26,8 @@ class Router(BaseRouter): ALLOWED_METHODS = HTTP_METHODS def _get( - self, path: str, method: str, host: Optional[str] - ) -> Tuple[Route, RouteHandler, Dict[str, Any]]: + self, path: str, method: str, host: str | None + ) -> tuple[Route, RouteHandler, dict[str, Any]]: try: return self.resolve( path=path, @@ -41,15 +40,13 @@ class Router(BaseRouter): raise MethodNotAllowed( f"Method {method} not allowed for URL {path}", method=method, - allowed_methods=tuple(e.allowed_methods) - if e.allowed_methods - else None, + allowed_methods=tuple(e.allowed_methods) if e.allowed_methods else None, ) from None @lru_cache(maxsize=ROUTER_CACHE_SIZE) def get( # type: ignore - self, path: str, method: str, host: Optional[str] - ) -> Tuple[Route, RouteHandler, Dict[str, Any]]: + self, path: str, method: str, host: str | None + ) -> tuple[Route, RouteHandler, dict[str, Any]]: """Retrieve a `Route` object containing the details about how to handle a response for a given request :param request: the incoming request object @@ -78,18 +75,18 @@ class Router(BaseRouter): uri: str, methods: Iterable[str], handler: RouteHandler, - host: Optional[Union[str, Iterable[str]]] = None, + host: str | Iterable[str] | None = None, strict_slashes: bool = False, stream: bool = False, ignore_body: bool = False, - version: Optional[Union[str, float, int]] = None, - name: Optional[str] = None, + version: str | float | int | None = None, + name: str | None = None, unquote: bool = False, static: bool = False, version_prefix: str = "/v", overwrite: bool = False, - error_format: Optional[str] = None, - ) -> Union[Route, List[Route]]: + error_format: str | None = None, + ) -> Route | list[Route]: """Add a handler to the router Args: @@ -115,15 +112,15 @@ class Router(BaseRouter): uri = self._normalize(uri, handler) - params = dict( - path=uri, - handler=handler, - methods=frozenset(map(str, methods)) if methods else None, - name=name, - strict=strict_slashes, - unquote=unquote, - overwrite=overwrite, - ) + params = { + "path": uri, + "handler": handler, + "methods": frozenset(map(str, methods)) if methods else None, + "name": name, + "strict": strict_slashes, + "unquote": unquote, + "overwrite": overwrite, + } if isinstance(host, str): hosts = [host] @@ -138,11 +135,7 @@ class Router(BaseRouter): ident = name if len(hosts) > 1: - ident = ( - f"{name}_{host.replace('.', '_')}" - if name - else "__unnamed__" - ) + ident = f"{name}_{host.replace('.', '_')}" if name else "__unnamed__" route = super().add(**params) # type: ignore route.extra.ident = ident @@ -163,8 +156,8 @@ class Router(BaseRouter): @lru_cache(maxsize=ROUTER_CACHE_SIZE) def find_route_by_view_name( - self, view_name: str, name: Optional[str] = None - ) -> Optional[Route]: + self, view_name: str, name: str | None = None + ) -> Route | None: """Find a route in the router based on the specified view name. Args: @@ -188,7 +181,7 @@ class Router(BaseRouter): return route @property - def routes_all(self) -> Dict[Tuple[str, ...], Route]: + def routes_all(self) -> dict[tuple[str, ...], Route]: """Return all routes in the router. Returns: @@ -197,7 +190,7 @@ class Router(BaseRouter): return {route.parts: route for route in self.routes} @property - def routes_static(self) -> Dict[Tuple[str, ...], Route]: + def routes_static(self) -> dict[tuple[str, ...], Route]: """Return all static routes in the router. _In this context "static" routes do not refer to the `app.static()` @@ -210,7 +203,7 @@ class Router(BaseRouter): return self.static_routes @property - def routes_dynamic(self) -> Dict[Tuple[str, ...], Route]: + def routes_dynamic(self) -> dict[tuple[str, ...], Route]: """Return all dynamic routes in the router. _Dynamic routes are routes that contain path parameters._ @@ -221,7 +214,7 @@ class Router(BaseRouter): return self.dynamic_routes @property - def routes_regex(self) -> Dict[Tuple[str, ...], Route]: + def routes_regex(self) -> dict[tuple[str, ...], Route]: """Return all regex routes in the router. _Regex routes are routes that contain path parameters with regex diff --git a/sanic/server/__init__.py b/sanic/server/__init__.py index a6b1a98c..5ae81909 100644 --- a/sanic/server/__init__.py +++ b/sanic/server/__init__.py @@ -4,7 +4,6 @@ from sanic.server.loop import try_use_uvloop from sanic.server.protocols.http_protocol import HttpProtocol from sanic.server.runners import serve - __all__ = ( "AsyncioServer", "ConnInfo", diff --git a/sanic/server/async_server.py b/sanic/server/async_server.py index 754a6e4e..4796d5a9 100644 --- a/sanic/server/async_server.py +++ b/sanic/server/async_server.py @@ -1,12 +1,10 @@ from __future__ import annotations import asyncio - from typing import TYPE_CHECKING from sanic.exceptions import SanicException - if TYPE_CHECKING: from sanic import Sanic diff --git a/sanic/server/events.py b/sanic/server/events.py index 9f4be84a..cacab407 100644 --- a/sanic/server/events.py +++ b/sanic/server/events.py @@ -1,17 +1,16 @@ from __future__ import annotations from inspect import isawaitable -from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional - +from typing import TYPE_CHECKING, Any, Callable, Iterable if TYPE_CHECKING: from sanic import Sanic def trigger_events( - events: Optional[Iterable[Callable[..., Any]]], + events: Iterable[Callable[..., Any]] | None, loop, - app: Optional[Sanic] = None, + app: Sanic | None = None, ): """Trigger event callbacks (functions or async) diff --git a/sanic/server/loop.py b/sanic/server/loop.py index 97cd8fdf..6fbc04e4 100644 --- a/sanic/server/loop.py +++ b/sanic/server/loop.py @@ -1,6 +1,5 @@ import asyncio import sys - from os import getenv from sanic.compat import OS_IS_WINDOWS diff --git a/sanic/server/protocols/base_protocol.py b/sanic/server/protocols/base_protocol.py index bf1b3c15..087694a1 100644 --- a/sanic/server/protocols/base_protocol.py +++ b/sanic/server/protocols/base_protocol.py @@ -1,15 +1,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from sanic.exceptions import RequestCancelled - if TYPE_CHECKING: from sanic.app import Sanic import asyncio - from asyncio.transports import Transport from time import monotonic as current_time @@ -47,14 +45,14 @@ class SanicProtocol(asyncio.Protocol): self.loop = loop self.app: Sanic = app self.signal = signal or Signal() - self.transport: Optional[Transport] = None + self.transport: Transport | None = None self.connections = connections if connections is not None else set() - self.conn_info: Optional[ConnInfo] = None + self.conn_info: ConnInfo | None = None self._can_write = asyncio.Event() self._can_write.set() self._unix = unix - self._time = 0.0 # type: float - self._task = None # type: Optional[asyncio.Task] + self._time = 0.0 + self._task: asyncio.Task | None = None self._data_received = asyncio.Event() @property @@ -82,7 +80,7 @@ class SanicProtocol(asyncio.Protocol): self._data_received.clear() await self._data_received.wait() - def close(self, timeout: Optional[float] = None): + def close(self, timeout: float | None = None): """ Attempt close the connection. """ diff --git a/sanic/server/protocols/http_protocol.py b/sanic/server/protocols/http_protocol.py index 6e30fc0f..bef3a2fc 100644 --- a/sanic/server/protocols/http_protocol.py +++ b/sanic/server/protocols/http_protocol.py @@ -1,17 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from sanic.http.constants import HTTP from sanic.http.http3 import Http3 from sanic.touchup.meta import TouchUpMeta - if TYPE_CHECKING: from sanic.app import Sanic import sys - from asyncio import CancelledError from time import monotonic as current_time @@ -26,7 +24,6 @@ from sanic.models.server_types import ConnInfo from sanic.request import Request from sanic.server.protocols.base_protocol import SanicProtocol - ConnectionProtocol = type("ConnectionProtocol", (), {}) try: from aioquic.asyncio import QuicConnectionProtocol @@ -55,7 +52,7 @@ class HttpProtocolMixin: ... def _setup(self): - self.request: Optional[Request] = None + self.request: Request | None = None self.access_log = self.app.config.ACCESS_LOG self.request_handler = self.app.handle_request self.error_handler = self.app.error_handler @@ -295,7 +292,7 @@ class Http3Protocol(HttpProtocolMixin, ConnectionProtocol): # type: ignore self.app = app super().__init__(*args, **kwargs) self._setup() - self._connection: Optional[H3Connection] = None + self._connection: H3Connection | None = None def quic_event_received(self, event: QuicEvent) -> None: logger.debug( @@ -306,9 +303,7 @@ class Http3Protocol(HttpProtocolMixin, ConnectionProtocol): # type: ignore if isinstance(event, ProtocolNegotiated): self._setup_connection(transmit=self.transmit) if event.alpn_protocol in H3_ALPN: - self._connection = H3Connection( - self._quic, enable_webtransport=True - ) + self._connection = H3Connection(self._quic, enable_webtransport=True) elif isinstance(event, DatagramFrameReceived): if event.data == b"quack": self._quic.send_datagram_frame(b"quack-ack") @@ -319,5 +314,5 @@ class Http3Protocol(HttpProtocolMixin, ConnectionProtocol): # type: ignore self._http.http_event_received(http_event) @property - def connection(self) -> Optional[H3Connection]: + def connection(self) -> H3Connection | None: return self._connection diff --git a/sanic/server/protocols/websocket_protocol.py b/sanic/server/protocols/websocket_protocol.py index 52a33bf8..7d23d3a0 100644 --- a/sanic/server/protocols/websocket_protocol.py +++ b/sanic/server/protocols/websocket_protocol.py @@ -1,6 +1,5 @@ from typing import TYPE_CHECKING, Optional, Sequence, cast - try: # websockets < 11.0 from websockets.connection import State from websockets.server import ServerConnection as ServerProtocol @@ -16,7 +15,6 @@ from sanic.server import HttpProtocol from ..websockets.impl import WebsocketImplProtocol - if TYPE_CHECKING: from websockets import http11 @@ -104,12 +102,7 @@ class WebSocketProtocol(HttpProtocol): # but ServerProtocol needs a list subprotocols = cast( Optional[Sequence[Subprotocol]], - list( - [ - Subprotocol(subprotocol) - for subprotocol in subprotocols - ] - ), + [Subprotocol(subprotocol) for subprotocol in subprotocols], ) ws_proto = ServerProtocol( max_size=self.websocket_max_size, @@ -147,8 +140,7 @@ class WebSocketProtocol(HttpProtocol): ) loop = ( request.transport.loop - if hasattr(request, "transport") - and hasattr(request.transport, "loop") + if hasattr(request, "transport") and hasattr(request.transport, "loop") else None ) await self.websocket.connection_made(self, loop=loop) diff --git a/sanic/server/runners.py b/sanic/server/runners.py index 72c7a035..3346b350 100644 --- a/sanic/server/runners.py +++ b/sanic/server/runners.py @@ -1,21 +1,19 @@ from __future__ import annotations from ssl import SSLContext -from typing import TYPE_CHECKING, Dict, Optional, Type, Union +from typing import TYPE_CHECKING from sanic.config import Config from sanic.exceptions import ServerError from sanic.http.constants import HTTP from sanic.http.tls import get_ssl_context - if TYPE_CHECKING: from sanic.app import Sanic import asyncio import os import socket - from functools import partial from signal import SIG_IGN, SIGINT, SIGTERM from signal import signal as signal_func @@ -29,7 +27,6 @@ from sanic.server.async_server import AsyncioServer from sanic.server.protocols.http_protocol import Http3Protocol, HttpProtocol from sanic.server.socket import bind_unix_socket, remove_unix_socket - try: from aioquic.asyncio import serve as quic_serve @@ -42,12 +39,12 @@ def serve( host, port, app: Sanic, - ssl: Optional[SSLContext] = None, - sock: Optional[socket.socket] = None, - unix: Optional[str] = None, + ssl: SSLContext | None = None, + sock: socket.socket | None = None, + unix: str | None = None, reuse_port: bool = False, loop=None, - protocol: Type[asyncio.Protocol] = HttpProtocol, + protocol: type[asyncio.Protocol] = HttpProtocol, backlog: int = 100, register_sys_signals: bool = True, run_multiple: bool = False, @@ -170,9 +167,7 @@ def _setup_system_signals( ctrlc_workaround_for_windows(app) else: for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]: - loop.add_signal_handler( - _signal, partial(app.stop, terminate=False) - ) + loop.add_signal_handler(_signal, partial(app.stop, terminate=False)) def _run_server_forever(loop, before_stop, after_stop, cleanup, unix): @@ -226,9 +221,7 @@ def _serve_http_1( unix=unix, **protocol_kwargs, ) - asyncio_server_kwargs = ( - asyncio_server_kwargs if asyncio_server_kwargs else {} - ) + asyncio_server_kwargs = asyncio_server_kwargs if asyncio_server_kwargs else {} if OS_IS_WINDOWS and sock: pid = os.getpid() sock = sock.share(pid) @@ -317,9 +310,7 @@ def _serve_http_3( run_multiple: bool = False, ): if not HTTP3_AVAILABLE: - raise ServerError( - "Cannot run HTTP/3 server without aioquic installed. " - ) + raise ServerError("Cannot run HTTP/3 server without aioquic installed. ") protocol = partial(Http3Protocol, app=app) ticket_store = SessionTicketStore() ssl_context = get_ssl_context(app, ssl) @@ -342,14 +333,12 @@ def _serve_http_3( # TODO: Create connection cleanup and graceful shutdown cleanup = None - _run_server_forever( - loop, server.before_stop, server.after_stop, cleanup, None - ) + _run_server_forever(loop, server.before_stop, server.after_stop, cleanup, None) def _build_protocol_kwargs( - protocol: Type[asyncio.Protocol], config: Config -) -> Dict[str, Union[int, float]]: + protocol: type[asyncio.Protocol], config: Config +) -> dict[str, int | float]: if hasattr(protocol, "websocket_handshake"): return { "websocket_max_size": config.WEBSOCKET_MAX_SIZE, diff --git a/sanic/server/socket.py b/sanic/server/socket.py index a77e80a0..6b006ad0 100644 --- a/sanic/server/socket.py +++ b/sanic/server/socket.py @@ -4,9 +4,8 @@ import os import secrets import socket import stat - from ipaddress import ip_address -from typing import Any, Dict, Optional +from typing import Any from sanic.exceptions import ServerError from sanic.http.constants import HTTP @@ -24,9 +23,7 @@ def bind_socket(host: str, port: int, *, backlog=100) -> socket.socket: try: # IP address: family must be specified for IPv6 at least ip = ip_address(host) host = str(ip) - sock = socket.socket( - socket.AF_INET6 if ip.version == 6 else socket.AF_INET - ) + sock = socket.socket(socket.AF_INET6 if ip.version == 6 else socket.AF_INET) except ValueError: # Hostname, may become AF_INET or AF_INET6 sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -77,7 +74,7 @@ def bind_unix_socket(path: str, *, mode=0o666, backlog=100) -> socket.socket: return sock -def remove_unix_socket(path: Optional[str]) -> None: +def remove_unix_socket(path: str | None) -> None: """Remove dead unix socket during server exit.""" if not path: return @@ -93,9 +90,7 @@ def remove_unix_socket(path: Optional[str]) -> None: pass -def configure_socket( - server_settings: Dict[str, Any] -) -> Optional[socket.SocketType]: +def configure_socket(server_settings: dict[str, Any]) -> socket.SocketType | None: # Create a listening socket or use the one in settings if server_settings.get("version") is HTTP.VERSION_3: return None diff --git a/sanic/server/websockets/connection.py b/sanic/server/websockets/connection.py index 8ff19da6..41176694 100644 --- a/sanic/server/websockets/connection.py +++ b/sanic/server/websockets/connection.py @@ -11,7 +11,6 @@ from typing import ( from sanic.exceptions import InvalidUsage - ASGIMessage = MutableMapping[str, Any] diff --git a/sanic/server/websockets/frame.py b/sanic/server/websockets/frame.py index dacc187c..40dfbe4b 100644 --- a/sanic/server/websockets/frame.py +++ b/sanic/server/websockets/frame.py @@ -1,6 +1,5 @@ import asyncio import codecs - from typing import TYPE_CHECKING, AsyncIterator, List, Optional from websockets.frames import Frame, Opcode @@ -8,7 +7,6 @@ from websockets.typing import Data from sanic.exceptions import ServerError - if TYPE_CHECKING: from .impl import WebsocketImplProtocol @@ -57,9 +55,7 @@ class WebsocketFrameAssembler: self.read_mutex = asyncio.Lock() self.write_mutex = asyncio.Lock() - self.completed_queue = asyncio.Queue( - maxsize=1 - ) # type: asyncio.Queue[Data] + self.completed_queue = asyncio.Queue(maxsize=1) # type: asyncio.Queue[Data] # put() sets this event to tell get() that a message can be fetched. self.message_complete = asyncio.Event() @@ -156,8 +152,7 @@ class WebsocketFrameAssembler: # and get_in_progress check, this exception is here # as a failsafe raise ServerError( - "Websocket get() found a message when " - "state was already fetched." + "Websocket get() found a message when " "state was already fetched." ) self.message_fetched.set() self.chunks = [] diff --git a/sanic/server/websockets/impl.py b/sanic/server/websockets/impl.py index e08a41b7..d36c2972 100644 --- a/sanic/server/websockets/impl.py +++ b/sanic/server/websockets/impl.py @@ -1,7 +1,6 @@ import asyncio import random import struct - from typing import ( AsyncIterator, Dict, @@ -19,7 +18,6 @@ from websockets.exceptions import ( ) from websockets.frames import Frame, Opcode - try: # websockets < 11.0 from websockets.connection import Event, State # type: ignore from websockets.server import ServerConnection as ServerProtocol @@ -35,7 +33,6 @@ from sanic.server.protocols.base_protocol import SanicProtocol from ...exceptions import ServerError, WebsocketClosed from .frame import WebsocketFrameAssembler - OPEN = State.OPEN CLOSING = State.CLOSING CLOSED = State.CLOSED @@ -155,12 +152,8 @@ class WebsocketImplProtocol: self.data_finished_fut = asyncio.shield(self.loop.create_future()) if self.ping_interval: - self.keepalive_ping_task = asyncio.create_task( - self.keepalive_ping() - ) - self.auto_closer_task = asyncio.create_task( - self.auto_close_connection() - ) + self.keepalive_ping_task = asyncio.create_task(self.keepalive_ping()) + self.auto_closer_task = asyncio.create_task(self.auto_close_connection()) async def wait_for_connection_lost(self, timeout=None) -> bool: """ @@ -249,9 +242,7 @@ class WebsocketImplProtocol: try: await asyncio.wait_for(ping_waiter, self.ping_timeout) except asyncio.TimeoutError: - error_logger.warning( - "Websocket timed out waiting for pong" - ) + error_logger.warning("Websocket timed out waiting for pong") self.fail_connection(1011) break except asyncio.CancelledError: @@ -281,9 +272,7 @@ class WebsocketImplProtocol: self.keepalive_ping_task = None if self.loop and self.io_proto and self.io_proto.transport: self.io_proto.transport.close() - self.loop.call_later( - self.close_timeout, self.io_proto.transport.abort - ) + self.loop.call_later(self.close_timeout, self.io_proto.transport.abort) # We were never open, or already closed return True @@ -321,9 +310,7 @@ class WebsocketImplProtocol: try: data_to_send = self.ws_proto.data_to_send() while ( - len(data_to_send) - and self.io_proto - and self.io_proto.transport + len(data_to_send) and self.io_proto and self.io_proto.transport ): frame_data = data_to_send.pop(0) self.io_proto.transport.write(frame_data) @@ -359,11 +346,7 @@ class WebsocketImplProtocol: self.ws_proto.send_close(code, reason) data_to_send.extend(self.ws_proto.data_to_send()) try: - while ( - len(data_to_send) - and self.io_proto - and self.io_proto.transport - ): + while len(data_to_send) and self.io_proto and self.io_proto.transport: frame_data = data_to_send.pop(0) self.io_proto.transport.write(frame_data) except Exception: @@ -395,15 +378,11 @@ class WebsocketImplProtocol: if self.data_finished_fut: try: await self.data_finished_fut - logger.debug( - "Websocket task finished. Closing the connection." - ) + logger.debug("Websocket task finished. Closing the connection.") except asyncio.CancelledError: # Cancelled error is called when data phase is cancelled # if an error occurred or the client closed the connection - logger.debug( - "Websocket handler cancelled. Closing the connection." - ) + logger.debug("Websocket handler cancelled. Closing the connection.") # Cancel the keepalive ping task. if self.keepalive_ping_task: @@ -429,10 +408,7 @@ class WebsocketImplProtocol: if (not self.io_proto) or (not self.io_proto.transport): # we were never open, or done. Can't do any finalization. return - elif ( - self.connection_lost_waiter - and self.connection_lost_waiter.done() - ): + elif self.connection_lost_waiter and self.connection_lost_waiter.done(): # connection confirmed closed already, proceed to abort waiter ... elif self.io_proto.transport.is_closing(): @@ -450,9 +426,7 @@ class WebsocketImplProtocol: if self.io_proto and self.io_proto.transport: self.io_proto.transport.abort() else: - if await self.wait_for_connection_lost( - timeout=self.close_timeout - ): + if await self.wait_for_connection_lost(timeout=self.close_timeout): # Connection aborted before the timeout expired. return error_logger.warning( @@ -734,8 +708,7 @@ class WebsocketImplProtocol: async with self.conn_mutex: if self.ws_proto.state in (CLOSED, CLOSING): raise WebsocketClosed( - "Cannot send a ping when the websocket interface " - "is closed." + "Cannot send a ping when the websocket interface " "is closed." ) if (not self.io_proto) or (not self.io_proto.loop): raise ServerError( @@ -750,9 +723,7 @@ class WebsocketImplProtocol: # Protect against duplicates if a payload is explicitly set. if data in self.pings: - raise ValueError( - "already waiting for a pong with the same data" - ) + raise ValueError("already waiting for a pong with the same data") # Generate a unique random payload otherwise. while data is None or data in self.pings: @@ -845,9 +816,7 @@ class WebsocketImplProtocol: self.ws_proto.receive_eof() data_to_send = self.ws_proto.data_to_send() events_to_process = self.ws_proto.events_received() - asyncio.create_task( - self.async_eof_received(data_to_send, events_to_process) - ) + asyncio.create_task(self.async_eof_received(data_to_send, events_to_process)) return False def connection_lost(self, exc): diff --git a/sanic/signals.py b/sanic/signals.py index fe252c12..999954dc 100644 --- a/sanic/signals.py +++ b/sanic/signals.py @@ -1,10 +1,9 @@ from __future__ import annotations import asyncio - from enum import Enum from inspect import isawaitable -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Any, cast from sanic_routing import BaseRouter, Route, RouteGroup from sanic_routing.exceptions import NotFound @@ -96,7 +95,7 @@ class SignalRouter(BaseRouter): def get( # type: ignore self, event: str, - condition: Optional[Dict[str, str]] = None, + condition: dict[str, str] | None = None, ): """Get the handlers for a signal @@ -121,7 +120,7 @@ class SignalRouter(BaseRouter): ) except NotFound: message = "Could not find signal %s" - terms: List[Union[str, Optional[Dict[str, str]]]] = [event] + terms: list[str | dict[str, str] | None] = [event] if extra: message += " with %s" terms.append(extra) @@ -144,8 +143,8 @@ class SignalRouter(BaseRouter): async def _dispatch( self, event: str, - context: Optional[Dict[str, Any]] = None, - condition: Optional[Dict[str, str]] = None, + context: dict[str, Any] | None = None, + condition: dict[str, str] | None = None, fail_not_found: bool = True, reverse: bool = False, ) -> Any: @@ -205,12 +204,12 @@ class SignalRouter(BaseRouter): self, event: str, *, - context: Optional[Dict[str, Any]] = None, - condition: Optional[Dict[str, str]] = None, + context: dict[str, Any] | None = None, + condition: dict[str, str] | None = None, fail_not_found: bool = True, inline: bool = False, reverse: bool = False, - ) -> Union[asyncio.Task, Any]: + ) -> asyncio.Task | Any: """Dispatch a signal to all handlers that match the event Args: @@ -248,7 +247,7 @@ class SignalRouter(BaseRouter): self, handler: SignalHandler, event: str, - condition: Optional[Dict[str, Any]] = None, + condition: dict[str, Any] | None = None, exclusive: bool = True, ) -> Signal: event_definition = event @@ -302,13 +301,9 @@ class SignalRouter(BaseRouter): return super().finalize(do_compile=do_compile, do_optimize=do_optimize) - def _build_event_parts(self, event: str) -> Tuple[str, str, str]: + def _build_event_parts(self, event: str) -> tuple[str, str, str]: parts = path_to_parts(event, self.delimiter) - if ( - len(parts) != 3 - or parts[0].startswith("<") - or parts[1].startswith("<") - ): + if len(parts) != 3 or parts[0].startswith("<") or parts[1].startswith("<"): raise InvalidSignal("Invalid signal event: %s" % event) if ( @@ -316,9 +311,7 @@ class SignalRouter(BaseRouter): and event not in RESERVED_NAMESPACES[parts[0]] and not (parts[2].startswith("<") and parts[2].endswith(">")) ): - raise InvalidSignal( - "Cannot declare reserved signal event: %s" % event - ) + raise InvalidSignal("Cannot declare reserved signal event: %s" % event) return parts def _clean_trigger(self, trigger: str) -> str: diff --git a/sanic/simple.py b/sanic/simple.py index 523f11a1..364104c5 100644 --- a/sanic/simple.py +++ b/sanic/simple.py @@ -11,8 +11,6 @@ def create_simple_server(directory: Path): ) app = Sanic("SimpleServer") - app.static( - "/", directory, name="main", directory_view=True, index="index.html" - ) + app.static("/", directory, name="main", directory_view=True, index="index.html") return app diff --git a/sanic/touchup/__init__.py b/sanic/touchup/__init__.py index 6fe208ab..4b2d73eb 100644 --- a/sanic/touchup/__init__.py +++ b/sanic/touchup/__init__.py @@ -1,7 +1,6 @@ from .meta import TouchUpMeta from .service import TouchUp - __all__ = ( "TouchUp", "TouchUpMeta", diff --git a/sanic/touchup/schemes/altsvc.py b/sanic/touchup/schemes/altsvc.py index 05e7269b..5ea2c400 100644 --- a/sanic/touchup/schemes/altsvc.py +++ b/sanic/touchup/schemes/altsvc.py @@ -1,13 +1,12 @@ from __future__ import annotations from ast import Assign, Constant, NodeTransformer, Subscript -from typing import TYPE_CHECKING, Any, List +from typing import TYPE_CHECKING, Any from sanic.http.constants import HTTP from .base import BaseScheme - if TYPE_CHECKING: from sanic import Sanic @@ -15,7 +14,7 @@ if TYPE_CHECKING: class AltSvcCheck(BaseScheme): ident = "ALTSVC" - def visitors(self) -> List[NodeTransformer]: + def visitors(self) -> list[NodeTransformer]: return [RemoveAltSvc(self.app, self.app.state.verbosity)] @@ -23,9 +22,7 @@ class RemoveAltSvc(NodeTransformer): def __init__(self, app: Sanic, verbosity: int = 0) -> None: self._app = app self._verbosity = verbosity - self._versions = { - info.settings["version"] for info in app.state.server_info - } + self._versions = {info.settings["version"] for info in app.state.server_info} def visit_Assign(self, node: Assign) -> Any: if any(self._matches(target) for target in node.targets): diff --git a/sanic/touchup/schemes/ode.py b/sanic/touchup/schemes/ode.py index ae51df72..15ef82e4 100644 --- a/sanic/touchup/schemes/ode.py +++ b/sanic/touchup/schemes/ode.py @@ -14,9 +14,7 @@ class OptionalDispatchEvent(BaseScheme): super().__init__(app) self._sync_events() - self._registered_events = [ - signal.name for signal in app.signal_router.routes - ] + self._registered_events = [signal.name for signal in app.signal_router.routes] def visitors(self) -> List[NodeTransformer]: return [RemoveDispatch(self._registered_events)] @@ -26,9 +24,7 @@ class OptionalDispatchEvent(BaseScheme): app_events = {} for app in self.app.__class__._app_registry.values(): if app.state.server_info: - app_events[app] = { - signal.name for signal in app.signal_router.routes - } + app_events[app] = {signal.name for signal in app.signal_router.routes} all_events.update(app_events[app]) for app, events in app_events.items(): diff --git a/sanic/types/__init__.py b/sanic/types/__init__.py index 043fffb4..feb53ab5 100644 --- a/sanic/types/__init__.py +++ b/sanic/types/__init__.py @@ -1,4 +1,3 @@ from .hashable_dict import HashableDict - __all__ = ("HashableDict",) diff --git a/sanic/types/shared_ctx.py b/sanic/types/shared_ctx.py index e0c069b2..77cd02a4 100644 --- a/sanic/types/shared_ctx.py +++ b/sanic/types/shared_ctx.py @@ -1,5 +1,4 @@ import os - from types import SimpleNamespace from typing import Any, Iterable @@ -15,9 +14,7 @@ class SharedContext(SimpleNamespace): def __setattr__(self, name: str, value: Any) -> None: if self.is_locked: - raise RuntimeError( - f"Cannot set {name} on locked SharedContext object" - ) + raise RuntimeError(f"Cannot set {name} on locked SharedContext object") if not os.environ.get("SANIC_WORKER_NAME"): to_check: Iterable[Any] if not isinstance(value, (tuple, frozenset)): @@ -36,8 +33,7 @@ class SharedContext(SimpleNamespace): except AttributeError: module = "" if not any( - module.startswith(prefix) - for prefix in ("multiprocessing", "ctypes") + module.startswith(prefix) for prefix in ("multiprocessing", "ctypes") ): error_logger.warning( f"{Colors.YELLOW}Unsafe object {Colors.PURPLE}{name} " diff --git a/sanic/utils.py b/sanic/utils.py index 76bf003c..4917fc92 100644 --- a/sanic/utils.py +++ b/sanic/utils.py @@ -1,5 +1,4 @@ import types - from importlib.util import module_from_spec, spec_from_file_location from os import environ as os_environ from pathlib import Path @@ -81,9 +80,7 @@ def load_module_from_file_location( env_vars_in_location = set(re_findall(r"\${(.+?)}", location)) # B) Check these variables exists in environment. - not_defined_env_vars = env_vars_in_location.difference( - os_environ.keys() - ) + not_defined_env_vars = env_vars_in_location.difference(os_environ.keys()) if not_defined_env_vars: raise LoadFileException( "The following environment variables are not set: " @@ -92,18 +89,14 @@ def load_module_from_file_location( # C) Substitute them in location. for env_var in env_vars_in_location: - location = location.replace( - "${" + env_var + "}", os_environ[env_var] - ) + location = location.replace("${" + env_var + "}", os_environ[env_var]) location = str(location) if ".py" in location: name = location.split("/")[-1].split(".")[ 0 ] # get just the file name without path and .py extension - _mod_spec = spec_from_file_location( - name, location, *args, **kwargs - ) + _mod_spec = spec_from_file_location(name, location, *args, **kwargs) assert _mod_spec is not None # type assertion for mypy module = module_from_spec(_mod_spec) _mod_spec.loader.exec_module(module) # type: ignore @@ -117,7 +110,7 @@ def load_module_from_file_location( compile(config_file.read(), location, "exec"), module.__dict__, ) - except IOError as e: + except OSError as e: e.strerror = "Unable to load configuration file (e.strerror)" raise except Exception as e: @@ -128,4 +121,4 @@ def load_module_from_file_location( try: return import_string(location) except ValueError: - raise IOError("Unable to load configuration %s" % str(location)) + raise OSError(f"Unable to load configuration {location!s}") diff --git a/sanic/views.py b/sanic/views.py index bdd48dc4..93e25401 100644 --- a/sanic/views.py +++ b/sanic/views.py @@ -5,15 +5,11 @@ from typing import ( Any, Callable, Iterable, - List, - Optional, - Union, ) from sanic.models.handler_types import RouteHandler from sanic.request.types import Request - if TYPE_CHECKING: from sanic import Sanic from sanic.blueprints import Blueprint @@ -115,19 +111,19 @@ class HTTPMethodView: to `"/v"`. """ - get: Optional[Callable[..., Any]] + get: Callable[..., Any] | None - decorators: List[Callable[[Callable[..., Any]], Callable[..., Any]]] = [] + decorators: list[Callable[[Callable[..., Any]], Callable[..., Any]]] = [] def __init_subclass__( cls, - attach: Optional[Union[Sanic, Blueprint]] = None, + attach: Sanic | Blueprint | None = None, uri: str = "", methods: Iterable[str] = frozenset({"GET"}), - host: Optional[str] = None, - strict_slashes: Optional[bool] = None, - version: Optional[int] = None, - name: Optional[str] = None, + host: str | None = None, + strict_slashes: bool | None = None, + version: int | None = None, + name: str | None = None, stream: bool = False, version_prefix: str = "/v", ) -> None: @@ -203,13 +199,13 @@ class HTTPMethodView: @classmethod def attach( cls, - to: Union[Sanic, Blueprint], + to: Sanic | Blueprint, uri: str, methods: Iterable[str] = frozenset({"GET"}), - host: Optional[str] = None, - strict_slashes: Optional[bool] = None, - version: Optional[int] = None, - name: Optional[str] = None, + host: str | None = None, + strict_slashes: bool | None = None, + version: int | None = None, + name: str | None = None, stream: bool = False, version_prefix: str = "/v", ) -> None: diff --git a/sanic/worker/inspector.py b/sanic/worker/inspector.py index 524c7bbe..b22dabea 100644 --- a/sanic/worker/inspector.py +++ b/sanic/worker/inspector.py @@ -5,7 +5,7 @@ from inspect import isawaitable from multiprocessing.connection import Connection from os import environ from pathlib import Path -from typing import Any, Dict, Mapping, Union +from typing import Any, Mapping from sanic.exceptions import Unauthorized from sanic.helpers import Default @@ -39,13 +39,13 @@ class Inspector: def __init__( self, publisher: Connection, - app_info: Dict[str, Any], + app_info: dict[str, Any], worker_state: Mapping[str, Any], host: str, port: int, api_key: str, - tls_key: Union[Path, str, Default], - tls_cert: Union[Path, str, Default], + tls_key: Path | str | Default, + tls_cert: Path | str | Default, ): self._publisher = publisher self.app_info = app_info @@ -106,13 +106,13 @@ class Inspector: name = request.match_info.get("action", "info") return json({"meta": {"action": name}, "result": output}) - def _state_to_json(self) -> Dict[str, Any]: + def _state_to_json(self) -> dict[str, Any]: output = {"info": self.app_info} output["workers"] = self._make_safe(dict(self.worker_state)) return output @staticmethod - def _make_safe(obj: Dict[str, Any]) -> Dict[str, Any]: + def _make_safe(obj: dict[str, Any]) -> dict[str, Any]: for key, value in obj.items(): if isinstance(value, dict): obj[key] = Inspector._make_safe(value) @@ -132,7 +132,7 @@ class Inspector: message += ":STARTUP_FIRST" self._publisher.send(message) - def scale(self, replicas: Union[str, int]) -> str: + def scale(self, replicas: str | int) -> str: """Scale the number of workers Args: diff --git a/sanic/worker/loader.py b/sanic/worker/loader.py index d76f75bd..944346ba 100644 --- a/sanic/worker/loader.py +++ b/sanic/worker/loader.py @@ -2,18 +2,16 @@ from __future__ import annotations import os import sys - from contextlib import suppress from importlib import import_module from inspect import isfunction from pathlib import Path from ssl import SSLContext -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Callable, cast from sanic.http.tls.context import process_to_context from sanic.http.tls.creators import MkcertCreator, TrustmeCreator - if TYPE_CHECKING: from sanic import Sanic as SanicApp @@ -41,7 +39,7 @@ class AppLoader: as_factory: bool = False, as_simple: bool = False, args: Any = None, - factory: Optional[Callable[[], SanicApp]] = None, + factory: Callable[[], SanicApp] | None = None, ) -> None: self.module_input = module_input self.module_name = "" @@ -134,9 +132,7 @@ class CertLoader: def __init__( self, - ssl_data: Optional[ - Union[SSLContext, Dict[str, Union[str, os.PathLike]]] - ], + ssl_data: SSLContext | dict[str, str | os.PathLike] | None, ): self._ssl_data = ssl_data self._creator_class = None diff --git a/sanic/worker/manager.py b/sanic/worker/manager.py index 1a0da96c..47142f30 100644 --- a/sanic/worker/manager.py +++ b/sanic/worker/manager.py @@ -1,5 +1,4 @@ import os - from contextlib import suppress from itertools import count from random import choice @@ -13,7 +12,6 @@ from sanic.log import error_logger, logger from sanic.worker.constants import RestartOrder from sanic.worker.process import ProcessState, Worker, WorkerProcess - if not OS_IS_WINDOWS: from signal import SIGKILL else: @@ -101,9 +99,7 @@ class WorkerManager: Worker: The Worker instance """ # noqa: E501 container = self.transient if transient else self.durable - worker = Worker( - ident, func, kwargs, self.context, self.worker_state, workers - ) + worker = Worker(ident, func, kwargs, self.context, self.worker_state, workers) container[worker.ident] = worker return worker @@ -206,9 +202,7 @@ class WorkerManager: change = num_worker - self.num_server if change == 0: - logger.info( - f"No change needed. There are already {num_worker} workers." - ) + logger.info(f"No change needed. There are already {num_worker} workers.") return logger.info(f"Scaling from {self.num_server} to {num_worker} workers") @@ -239,9 +233,7 @@ class WorkerManager: try: if self.monitor_subscriber.poll(0.1): message = self.monitor_subscriber.recv() - logger.debug( - f"Monitor message: {message}", extra={"verbosity": 2} - ) + logger.debug(f"Monitor message: {message}", extra={"verbosity": 2}) if not message: break elif message == "__TERMINATE__": @@ -260,9 +252,7 @@ class WorkerManager: reloaded_files = ( split_message[1] if len(split_message) > 1 else None ) - process_names = [ - name.strip() for name in processes.split(",") - ] + process_names = [name.strip() for name in processes.split(",")] if "__ALL_PROCESSES__" in process_names: process_names = None order = ( @@ -324,15 +314,13 @@ class WorkerManager: def processes(self): """Get all of the processes.""" for worker in self.workers: - for process in worker.processes: - yield process + yield from worker.processes @property def transient_processes(self): """Get all of the transient processes.""" for worker in self.transient.values(): - for process in worker.processes: - yield process + yield from worker.processes def kill(self): """Kill all of the processes.""" diff --git a/sanic/worker/multiplexer.py b/sanic/worker/multiplexer.py index b877ccc1..c52a8d3d 100644 --- a/sanic/worker/multiplexer.py +++ b/sanic/worker/multiplexer.py @@ -54,8 +54,7 @@ class WorkerMultiplexer: """ if name and all_workers: raise ValueError( - "Ambiguous restart with both a named process and" - " all_workers=True" + "Ambiguous restart with both a named process and" " all_workers=True" ) if not name: name = "__ALL_PROCESSES__:" if all_workers else self.name diff --git a/sanic/worker/process.py b/sanic/worker/process.py index 83b3556b..2de9da96 100644 --- a/sanic/worker/process.py +++ b/sanic/worker/process.py @@ -1,5 +1,4 @@ import os - from datetime import datetime, timezone from multiprocessing.context import BaseContext from signal import SIGINT @@ -30,9 +29,7 @@ class WorkerProcess: self.kwargs = kwargs self.worker_state = worker_state if self.name not in self.worker_state: - self.worker_state[self.name] = { - "server": self.SERVER_LABEL in self.name - } + self.worker_state[self.name] = {"server": self.SERVER_LABEL in self.name} self.spawn() def set_state(self, state: ProcessState, force=False): @@ -95,9 +92,7 @@ class WorkerProcess: self._terminate_now() else: self._old_process = self._current_process - self.kwargs.update( - {"config": {k.upper(): v for k, v in kwargs.items()}} - ) + self.kwargs.update({"config": {k.upper(): v for k, v in kwargs.items()}}) try: self.spawn() self.start() diff --git a/sanic/worker/reloader.py b/sanic/worker/reloader.py index b490c5b3..ef7c50dd 100644 --- a/sanic/worker/reloader.py +++ b/sanic/worker/reloader.py @@ -2,7 +2,6 @@ from __future__ import annotations import os import sys - from asyncio import new_event_loop from itertools import chain from multiprocessing.connection import Connection @@ -10,7 +9,6 @@ from pathlib import Path from signal import SIGINT, SIGTERM from signal import signal as signal_func from time import sleep -from typing import Dict, Set from sanic.server.events import trigger_events from sanic.worker.loader import AppLoader @@ -23,7 +21,7 @@ class Reloader: self, publisher: Connection, interval: float, - reload_dirs: Set[Path], + reload_dirs: set[Path], app_loader: AppLoader, ): self._publisher = publisher @@ -36,7 +34,7 @@ class Reloader: app = self.app_loader.load() signal_func(SIGINT, self.stop) signal_func(SIGTERM, self.stop) - mtimes: Dict[str, float] = {} + mtimes: dict[str, float] = {} reloader_start = app.listeners.get("reload_process_start") reloader_stop = app.listeners.get("reload_process_stop") diff --git a/sanic/worker/serve.py b/sanic/worker/serve.py index 583d3eaf..0844f36b 100644 --- a/sanic/worker/serve.py +++ b/sanic/worker/serve.py @@ -2,7 +2,6 @@ import asyncio import os import socket import warnings - from functools import partial from multiprocessing.connection import Connection from ssl import SSLContext @@ -28,9 +27,7 @@ def worker_serve( app_loader: AppLoader, worker_state: Optional[Dict[str, Any]] = None, server_info: Optional[Dict[str, List[ApplicationServerInfo]]] = None, - ssl: Optional[ - Union[SSLContext, Dict[str, Union[str, os.PathLike]]] - ] = None, + ssl: Optional[Union[SSLContext, Dict[str, Union[str, os.PathLike]]]] = None, sock: Optional[socket.socket] = None, unix: Optional[str] = None, reuse_port: bool = False, @@ -87,9 +84,7 @@ def worker_serve( # Hydrate apps with any passed server info if monitor_publisher is None: - raise RuntimeError( - "No restart publisher found in worker process" - ) + raise RuntimeError("No restart publisher found in worker process") if worker_state is None: raise RuntimeError("No worker state found in worker process") @@ -97,9 +92,7 @@ def worker_serve( apps = list(Sanic._app_registry.values()) app.before_server_start(partial(app._start_servers, apps=apps)) for a in apps: - a.multiplexer = WorkerMultiplexer( - monitor_publisher, worker_state - ) + a.multiplexer = WorkerMultiplexer(monitor_publisher, worker_state) if app.debug: loop.set_debug(app.debug) diff --git a/sanic/worker/state.py b/sanic/worker/state.py index c233c19a..ae14ead8 100644 --- a/sanic/worker/state.py +++ b/sanic/worker/state.py @@ -1,8 +1,6 @@ from collections.abc import Mapping -from typing import Any, Dict, ItemsView, Iterator, KeysView, List +from typing import Any, Dict, ItemsView, Iterator, KeysView, List, ValuesView from typing import Mapping as MappingType -from typing import ValuesView - dict @@ -64,9 +62,7 @@ class WorkerState(Mapping): def update(self, mapping: MappingType[str, Any]) -> None: if any(k in self.RESTRICTED for k in mapping.keys()): - self._write_error( - [k for k in mapping.keys() if k in self.RESTRICTED] - ) + self._write_error([k for k in mapping.keys() if k in self.RESTRICTED]) self._state[self._name] = { **self._state[self._name], **mapping, diff --git a/scripts/changelog.py b/scripts/changelog.py index 8848c426..88059d60 100755 --- a/scripts/changelog.py +++ b/scripts/changelog.py @@ -1,12 +1,12 @@ #!/usr/bin/env python -from os import path import sys +from os import path if __name__ == "__main__": try: - import towncrier import click + import towncrier except ImportError: print( "Please make sure you have a installed towncrier and click before using this tool" @@ -22,9 +22,7 @@ if __name__ == "__main__": help="Render the news fragments, don't write to files, " "don't check versions.", ) - @click.option( - "--dir", "directory", default=path.dirname(path.abspath(__file__)) - ) + @click.option("--dir", "directory", default=path.dirname(path.abspath(__file__))) @click.option("--name", "project_name", default=None) @click.option( "--version", diff --git a/scripts/release.py b/scripts/release.py index e2b9b887..9664c73a 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,17 +1,17 @@ #!/usr/bin/env python +import sys from argparse import ArgumentParser, Namespace from collections import OrderedDict from configparser import RawConfigParser from datetime import datetime from json import dumps -from os import path, chdir -from subprocess import Popen, PIPE +from os import chdir, path +from subprocess import PIPE, Popen -from jinja2 import Environment, BaseLoader -from requests import patch -import sys import towncrier +from jinja2 import BaseLoader, Environment +from requests import patch GIT_COMMANDS = { "get_tag": ["git describe --tags --abbrev=0"], @@ -25,8 +25,7 @@ GIT_COMMANDS = { ], "push_tag": ["git push origin {new_version}"], "get_change_log": [ - 'git log --no-merges --pretty=format:"%h::: %cn::: %s" ' - "{current_version}.." + 'git log --no-merges --pretty=format:"%h::: %cn::: %s" ' "{current_version}.." ], } @@ -72,13 +71,11 @@ class Directory: def _run_shell_command(command: list): try: - process = Popen( - command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True - ) + process = Popen(command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True) output, error = process.communicate() return_code = process.returncode return output.decode("utf-8"), error, return_code - except: + except Exception: return None, None, -1 @@ -101,7 +98,7 @@ def _fetch_current_version(config_file: str) -> str: def _change_micro_version(current_version: str): version_string = current_version.split(".") - version_string[-1] = str((int(version_string[-1]) + 1)) + version_string[-1] = str(int(version_string[-1]) + 1) return ".".join(version_string) @@ -143,9 +140,9 @@ def _update_release_version_for_sanic( current_version_line = config_parser.get( "version", "current_version_pattern" ).format(current_version=current_version) - new_version_line = config_parser.get( - "version", "new_version_pattern" - ).format(new_version=new_version) + new_version_line = config_parser.get("version", "new_version_pattern").format( + new_version=new_version + ) for version_file in version_files.split(","): with open(version_file) as init_file: @@ -228,7 +225,7 @@ def _tag_release(new_version, current_version, milestone, release_name, token): ) out, error, ret = _run_shell_command(command=command) if int(ret) != 0: - print("Failed to execute the command: {}".format(command[0])) + print(f"Failed to execute the command: {command[0]}") sys.exit(1) change_log = _generate_markdown_document( @@ -253,9 +250,7 @@ def release(args: Namespace): if current_tag and current_version not in current_tag: print( "Tag mismatch between what's in git and what was provided by " - "--current-version. Existing: {}, Give: {}".format( - current_tag, current_version - ) + f"--current-version. Existing: {current_tag}, Give: {current_version}" ) sys.exit(1) new_version = args.release_version or _get_new_version( diff --git a/setup.py b/setup.py index bfa767b5..5a8608c5 100644 --- a/setup.py +++ b/setup.py @@ -59,9 +59,7 @@ def str_to_bool(val: str) -> bool: with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: try: - version = re.findall( - r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M - )[0] + version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0] except IndexError: raise RuntimeError("Unable to determine version.") @@ -96,9 +94,7 @@ setup_kwargs = { "entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]}, } -env_dependency = ( - '; sys_platform != "win32" ' 'and implementation_name == "cpython"' -) +env_dependency = '; sys_platform != "win32" and implementation_name == "cpython"' ujson = "ujson>=1.35" + env_dependency uvloop = "uvloop>=0.15.0" + env_dependency types_ujson = "types-ujson" + env_dependency @@ -123,9 +119,7 @@ tests_require = [ "pytest-sanic", "pytest-benchmark", "chardet==3.*", - "flake8", - "black", - "isort>=5.0.0", + "ruff", "bandit", "mypy", "docutils", diff --git a/tests/asyncmock.py b/tests/asyncmock.py index 835012fd..243e8a65 100644 --- a/tests/asyncmock.py +++ b/tests/asyncmock.py @@ -14,7 +14,7 @@ class AsyncMock(Mock): def __call__(self, *args, **kwargs): self.call_count += 1 - parent = super(AsyncMock, self) + parent = super() async def dummy(): self.await_count += 1 diff --git a/tests/benchmark/test_route_resolution_benchmark.py b/tests/benchmark/test_route_resolution_benchmark.py index a921d063..f4bbd67b 100644 --- a/tests/benchmark/test_route_resolution_benchmark.py +++ b/tests/benchmark/test_route_resolution_benchmark.py @@ -3,10 +3,8 @@ from random import choice, seed from pytest import mark import sanic.router - from sanic.request import Request - seed("Pack my box with five dozen liquor jugs.") # Disable Caching for testing purpose @@ -18,13 +16,11 @@ class TestSanicRouteResolution: async def test_resolve_route_no_arg_string_path( self, sanic_router, route_generator, benchmark ): - simple_routes = route_generator.generate_random_direct_route( - max_route_depth=4 - ) + simple_routes = route_generator.generate_random_direct_route(max_route_depth=4) router, simple_routes = sanic_router(route_details=simple_routes) route_to_call = choice(simple_routes) request = Request( - "/{}".format(route_to_call[-1]).encode(), + f"/{route_to_call[-1]}".encode(), {"host": "localhost"}, "v1", route_to_call[0], @@ -54,13 +50,11 @@ class TestSanicRouteResolution: ) router, typed_routes = sanic_router(route_details=typed_routes) route_to_call = choice(typed_routes) - url = route_generator.generate_url_for_template( - template=route_to_call[-1] - ) + url = route_generator.generate_url_for_template(template=route_to_call[-1]) - print("{} -> {}".format(route_to_call[-1], url)) + print(f"{route_to_call[-1]} -> {url}") request = Request( - "/{}".format(url).encode(), + f"/{url}".encode(), {"host": "localhost"}, "v1", route_to_call[0], diff --git a/tests/certs/createcerts.py b/tests/certs/createcerts.py index 34415961..f1c6a97e 100644 --- a/tests/certs/createcerts.py +++ b/tests/certs/createcerts.py @@ -51,9 +51,7 @@ def selfsigned(key, common_name, san): .not_valid_before(datetime.utcnow()) .not_valid_after(datetime.utcnow() + timedelta(days=365.25 * 8)) .add_extension( - KeyUsage( - True, False, False, False, False, False, False, False, False - ), + KeyUsage(True, False, False, False, False, False, False, False, False), critical=True, ) .add_extension( diff --git a/tests/client.py b/tests/client.py index 4c0b29a0..e73da412 100644 --- a/tests/client.py +++ b/tests/client.py @@ -1,5 +1,4 @@ import asyncio - from textwrap import dedent from typing import AnyStr @@ -14,9 +13,7 @@ class RawClient: self.port = port async def connect(self): - self.reader, self.writer = await asyncio.open_connection( - self.host, self.port - ) + self.reader, self.writer = await asyncio.open_connection(self.host, self.port) async def close(self): self.writer.close() @@ -40,8 +37,4 @@ class RawClient: return await self.reader.read(nbytes) def _clean(self, message: str) -> str: - return ( - dedent(message) - .lstrip("\n") - .replace("\n", self.CRLF.decode("utf-8")) - ) + return dedent(message).lstrip("\n").replace("\n", self.CRLF.decode("utf-8")) diff --git a/tests/conftest.py b/tests/conftest.py index 0e94a0d2..1a6127a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,14 +7,12 @@ import re import string import sys import uuid - from contextlib import suppress from logging import LogRecord from typing import Any, Dict, List, Tuple from unittest.mock import MagicMock, Mock, patch import pytest - from sanic_routing.exceptions import RouteExists from sanic_testing.testing import PORT @@ -23,7 +21,6 @@ from sanic.constants import HTTP_METHODS from sanic.router import Router from sanic.touchup.service import TouchUp - slugify = re.compile(r"[^a-zA-Z0-9_\-]") random.seed("Pack my box with five dozen liquor jugs.") Sanic.test_mode = True @@ -45,14 +42,10 @@ async def _handler(request): TYPE_TO_GENERATOR_MAP = { - "str": lambda: "".join( - [random.choice(string.ascii_lowercase) for _ in range(4)] - ), + "str": lambda: "".join([random.choice(string.ascii_lowercase) for _ in range(4)]), "int": lambda: random.choice(range(1000000)), "float": lambda: random.random(), - "alpha": lambda: "".join( - [random.choice(string.ascii_lowercase) for _ in range(4)] - ), + "alpha": lambda: "".join([random.choice(string.ascii_lowercase) for _ in range(4)]), "uuid": lambda: str(uuid.uuid1()), } diff --git a/tests/fake/server.py b/tests/fake/server.py index 577b70bf..b38654a0 100644 --- a/tests/fake/server.py +++ b/tests/fake/server.py @@ -5,7 +5,6 @@ from sanic.application.constants import Mode from sanic.config import Config from sanic.log import LOGGING_CONFIG_DEFAULTS, logger - LOGGING_CONFIG = {**LOGGING_CONFIG_DEFAULTS} LOGGING_CONFIG["formatters"]["generic"]["format"] = "%(message)s" LOGGING_CONFIG["loggers"]["sanic.root"]["level"] = "DEBUG" diff --git a/tests/http3/test_http_receiver.py b/tests/http3/test_http_receiver.py index d996c484..51b1e959 100644 --- a/tests/http3/test_http_receiver.py +++ b/tests/http3/test_http_receiver.py @@ -1,7 +1,6 @@ from unittest.mock import Mock import pytest - from aioquic.h3.connection import H3Connection from aioquic.h3.events import DataReceived, HeadersReceived from aioquic.quic.configuration import QuicConfiguration @@ -18,7 +17,6 @@ from sanic.models.server_types import ConnInfo from sanic.response import empty, json from sanic.server.protocols.http_protocol import Http3Protocol - try: from unittest.mock import AsyncMock except ImportError: @@ -86,9 +84,7 @@ async def test_http_receiver_run_request(app: Sanic, http_request: Request): app.__class__ = mock_handle receiver = generate_http_receiver(app, http_request) - receiver.protocol.quic_event_received( - ProtocolNegotiated(alpn_protocol="h3") - ) + receiver.protocol.quic_event_received(ProtocolNegotiated(alpn_protocol="h3")) await receiver.run() handler.assert_awaited_once_with(receiver.request) @@ -101,9 +97,7 @@ async def test_http_receiver_run_exception(app: Sanic, http_request: Request): app.__class__ = mock_handle receiver = generate_http_receiver(app, http_request) - receiver.protocol.quic_event_received( - ProtocolNegotiated(alpn_protocol="h3") - ) + receiver.protocol.quic_event_received(ProtocolNegotiated(alpn_protocol="h3")) exception = Exception("Oof") await receiver.run(exception) handler.assert_awaited_once_with(receiver.request, exception) @@ -141,9 +135,7 @@ def test_http_receiver_receive_body(app: Sanic, http_request: Request): receiver.receive_body(b"..") assert receiver.request.body == b"...." - with pytest.raises( - PayloadTooLarge, match="Request body exceeds the size limit" - ): + with pytest.raises(PayloadTooLarge, match="Request body exceeds the size limit"): receiver.receive_body(b"..") @@ -178,17 +170,13 @@ async def test_send_headers(app: Sanic, http_request: Request): send_headers_mock = Mock() existing_send_headers = H3Connection.send_headers receiver = generate_http_receiver(app, http_request) - receiver.protocol.quic_event_received( - ProtocolNegotiated(alpn_protocol="h3") - ) + receiver.protocol.quic_event_received(ProtocolNegotiated(alpn_protocol="h3")) http_request._protocol = receiver.protocol def send_headers(*args, **kwargs): send_headers_mock(*args, **kwargs) - return existing_send_headers( - receiver.protocol.connection, *args, **kwargs - ) + return existing_send_headers(receiver.protocol.connection, *args, **kwargs) receiver.protocol.connection.send_headers = send_headers receiver.head_only = False @@ -312,10 +300,7 @@ def test_request_header_encoding(app): ) ) assert exc_info.value.status_code == 400 - assert ( - str(exc_info.value) - == "Header names may only contain US-ASCII characters." - ) + assert str(exc_info.value) == "Header names may only contain US-ASCII characters." def test_request_url_encoding(app): diff --git a/tests/http3/test_server.py b/tests/http3/test_server.py index 20b45b6d..04836c19 100644 --- a/tests/http3/test_server.py +++ b/tests/http3/test_server.py @@ -1,6 +1,5 @@ import logging import sys - from asyncio import Event from pathlib import Path @@ -10,7 +9,6 @@ from sanic import Sanic from sanic.compat import UVLOOP_INSTALLED from sanic.http.constants import HTTP - parent_dir = Path(__file__).parent.parent localhost_dir = parent_dir / "certs/localhost" diff --git a/tests/performance/aiohttp/simple_server.py b/tests/performance/aiohttp/simple_server.py index 9a57f459..fe076e15 100644 --- a/tests/performance/aiohttp/simple_server.py +++ b/tests/performance/aiohttp/simple_server.py @@ -5,10 +5,8 @@ import sys import ujson as json import uvloop - from aiohttp import web - loop = uvloop.new_event_loop() asyncio.set_event_loop(loop) diff --git a/tests/performance/bottle/simple_server.py b/tests/performance/bottle/simple_server.py index 43a8f019..5857469b 100644 --- a/tests/performance/bottle/simple_server.py +++ b/tests/performance/bottle/simple_server.py @@ -1,8 +1,7 @@ # Run with: gunicorn --workers=1 --worker-class=meinheld.gmeinheld.MeinheldWorker -b :8000 simple_server:app import bottle import ujson - -from bottle import route, run +from bottle import route @route("/") diff --git a/tests/performance/kyoukai/simple_server.py b/tests/performance/kyoukai/simple_server.py index 9fd7d2b0..e6a28650 100644 --- a/tests/performance/kyoukai/simple_server.py +++ b/tests/performance/kyoukai/simple_server.py @@ -4,10 +4,8 @@ import logging import ujson import uvloop - from kyoukai import HTTPRequestContext, Kyoukai - loop = uvloop.new_event_loop() asyncio.set_event_loop(loop) diff --git a/tests/performance/sanic/http_response.py b/tests/performance/sanic/http_response.py index 52596e5e..57046454 100644 --- a/tests/performance/sanic/http_response.py +++ b/tests/performance/sanic/http_response.py @@ -3,14 +3,9 @@ import os import sys import timeit -import asyncpg - from sanic.response import json - -currentdir = os.path.dirname( - os.path.abspath(inspect.getfile(inspect.currentframe())) -) +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) sys.path.insert(0, currentdir + "/../../../") @@ -25,10 +20,10 @@ for n in range(6): setup="from sanic.response import json", number=100000, ) - print("Took {} seconds".format(time)) + print(f"Took {time} seconds") total_time += time times += 1 -print("Average: {}".format(total_time / times)) +print(f"Average: {total_time / times}") print("Running Old 100,000 times") times = 0 @@ -39,7 +34,7 @@ for n in range(6): setup="from sanic.response import json", number=100000, ) - print("Took {} seconds".format(time)) + print(f"Took {time} seconds") total_time += time times += 1 -print("Average: {}".format(total_time / times)) +print(f"Average: {total_time / times}") diff --git a/tests/performance/sanic/simple_server.py b/tests/performance/sanic/simple_server.py index 60ebd819..ffb6566e 100644 --- a/tests/performance/sanic/simple_server.py +++ b/tests/performance/sanic/simple_server.py @@ -5,10 +5,7 @@ import sys from sanic import Sanic from sanic.response import json - -currentdir = os.path.dirname( - os.path.abspath(inspect.getfile(inspect.currentframe())) -) +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) sys.path.insert(0, currentdir + "/../../../") diff --git a/tests/performance/sanic/varied_server.py b/tests/performance/sanic/varied_server.py index 9b3f16da..b5be1c39 100644 --- a/tests/performance/sanic/varied_server.py +++ b/tests/performance/sanic/varied_server.py @@ -6,10 +6,7 @@ from sanic import Sanic from sanic.exceptions import ServerError from sanic.response import json, text - -currentdir = os.path.dirname( - os.path.abspath(inspect.getfile(inspect.currentframe())) -) +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) sys.path.insert(0, currentdir + "/../../../") @@ -28,7 +25,7 @@ def test(request): @app.route("/text//") def rtext(request, name, butt): - return text("yeehaww {} {}".format(name, butt)) + return text(f"yeehaww {name} {butt}") @app.route("/exception") diff --git a/tests/performance/tornado/simple_server.py b/tests/performance/tornado/simple_server.py index 1e69a293..a326eeaf 100644 --- a/tests/performance/tornado/simple_server.py +++ b/tests/performance/tornado/simple_server.py @@ -1,6 +1,5 @@ # Run with: python simple_server.py import ujson - from tornado import ioloop, web diff --git a/tests/performance/wheezy/simple_server.py b/tests/performance/wheezy/simple_server.py index f5db80b4..d892d342 100644 --- a/tests/performance/wheezy/simple_server.py +++ b/tests/performance/wheezy/simple_server.py @@ -2,9 +2,7 @@ """ Minimal helloworld application. """ import ujson - from wheezy.http import HTTPResponse, WSGIApplication -from wheezy.http.response import json_response from wheezy.routing import url from wheezy.web.handlers import BaseHandler from wheezy.web.middleware import ( @@ -38,11 +36,10 @@ main = WSGIApplication( if __name__ == "__main__": import sys - from wsgiref.simple_server import make_server try: - print("Visit http://localhost:{}/".format(sys.argv[-1])) + print(f"Visit http://localhost:{sys.argv[-1]}/") make_server("", int(sys.argv[-1]), main).serve_forever() except KeyboardInterrupt: pass diff --git a/tests/test_app.py b/tests/test_app.py index aefec3e6..5f5fa3c2 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,7 +1,6 @@ import asyncio import logging import re - from collections import Counter from inspect import isawaitable from os import environ @@ -10,7 +9,6 @@ from unittest.mock import Mock, patch import pytest import sanic - from sanic import Sanic from sanic.compat import OS_IS_WINDOWS from sanic.config import Config @@ -51,7 +49,7 @@ def test_asyncio_server_no_start_serving(app: Sanic): asyncio_srv_coro = app.create_server( port=43123, return_asyncio_server=True, - asyncio_server_kwargs=dict(start_serving=False), + asyncio_server_kwargs={"start_serving": False}, ) srv = loop.run_until_complete(asyncio_srv_coro) assert srv.is_serving() is False @@ -63,7 +61,7 @@ def test_asyncio_server_start_serving(app: Sanic): asyncio_srv_coro = app.create_server( port=43124, return_asyncio_server=True, - asyncio_server_kwargs=dict(start_serving=False), + asyncio_server_kwargs={"start_serving": False}, ) srv = loop.run_until_complete(asyncio_srv_coro) assert srv.is_serving() is False @@ -96,12 +94,10 @@ def test_create_server_no_startup(app: Sanic): asyncio_srv_coro = app.create_server( port=43124, return_asyncio_server=True, - asyncio_server_kwargs=dict(start_serving=False), + asyncio_server_kwargs={"start_serving": False}, ) srv = loop.run_until_complete(asyncio_srv_coro) - message = ( - "Cannot run Sanic server without first running await server.startup()" - ) + message = "Cannot run Sanic server without first running await server.startup()" with pytest.raises(SanicException, match=message): loop.run_until_complete(srv.start_serving()) @@ -214,14 +210,8 @@ def test_app_websocket_parameters(websocket_protocol_mock, app: Sanic): websocket_protocol_call_args = websocket_protocol_mock.call_args ws_kwargs = websocket_protocol_call_args[1] assert ws_kwargs["websocket_max_size"] == app.config.WEBSOCKET_MAX_SIZE - assert ( - ws_kwargs["websocket_ping_timeout"] - == app.config.WEBSOCKET_PING_TIMEOUT - ) - assert ( - ws_kwargs["websocket_ping_interval"] - == app.config.WEBSOCKET_PING_INTERVAL - ) + assert ws_kwargs["websocket_ping_timeout"] == app.config.WEBSOCKET_PING_TIMEOUT + assert ws_kwargs["websocket_ping_interval"] == app.config.WEBSOCKET_PING_INTERVAL def test_handle_request_with_nested_exception(app: Sanic, monkeypatch): @@ -230,9 +220,7 @@ def test_handle_request_with_nested_exception(app: Sanic, monkeypatch): def mock_error_handler_response(*args, **kwargs): raise Exception(err_msg) - monkeypatch.setattr( - app.error_handler, "response", mock_error_handler_response - ) + monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) @app.get("/") def handler(request): @@ -249,9 +237,7 @@ def test_handle_request_with_nested_exception_debug(app: Sanic, monkeypatch): def mock_error_handler_response(*args, **kwargs): raise Exception(err_msg) - monkeypatch.setattr( - app.error_handler, "response", mock_error_handler_response - ) + monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) @app.get("/") def handler(request): @@ -265,15 +251,11 @@ def test_handle_request_with_nested_exception_debug(app: Sanic, monkeypatch): ) -def test_handle_request_with_nested_sanic_exception( - app: Sanic, monkeypatch, caplog -): +def test_handle_request_with_nested_sanic_exception(app: Sanic, monkeypatch, caplog): def mock_error_handler_response(*args, **kwargs): raise SanicException("Mock SanicException") - monkeypatch.setattr( - app.error_handler, "response", mock_error_handler_response - ) + monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) @app.get("/") def handler(request): @@ -328,9 +310,7 @@ def test_app_registry_wrong_type(): def test_app_registry_name_reuse(): Sanic("test") Sanic.test_mode = False - with pytest.raises( - SanicException, match='Sanic app name "test" already in use.' - ): + with pytest.raises(SanicException, match='Sanic app name "test" already in use.'): Sanic("test") Sanic.test_mode = True Sanic("test") @@ -363,9 +343,7 @@ def test_get_app_does_not_exist(): def test_get_app_does_not_exist_force_create(): - assert isinstance( - Sanic.get_app("does-not-exist", force_create=True), Sanic - ) + assert isinstance(Sanic.get_app("does-not-exist", force_create=True), Sanic) def test_get_app_default(): @@ -374,9 +352,7 @@ def test_get_app_default(): def test_get_app_no_default(): - with pytest.raises( - SanicException, match="No Sanic apps have been registered." - ): + with pytest.raises(SanicException, match="No Sanic apps have been registered."): Sanic.get_app() @@ -385,9 +361,7 @@ def test_get_app_default_ambiguous(): Sanic("test2") with pytest.raises( SanicException, - match=re.escape( - 'Multiple Sanic apps found, use Sanic.get_app("app_name")' - ), + match=re.escape('Multiple Sanic apps found, use Sanic.get_app("app_name")'), ): Sanic.get_app() @@ -419,8 +393,7 @@ def test_bad_custom_config(): with pytest.raises( SanicException, match=( - "When instantiating Sanic with config, you cannot also pass " - "env_prefix" + "When instantiating Sanic with config, you cannot also pass " "env_prefix" ), ): Sanic("test", config=1, env_prefix=1) @@ -488,7 +461,7 @@ def test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch): for app in apps: srv_coro = app.create_server( return_asyncio_server=True, - asyncio_server_kwargs=dict(start_serving=False), + asyncio_server_kwargs={"start_serving": False}, ) loop.run_until_complete(srv_coro) @@ -502,9 +475,7 @@ def test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch): ) counter = Counter([(r[1], r[2]) for r in caplog.record_tuples]) - modified = sum( - 1 for app in apps if not isinstance(app.config.USE_UVLOOP, Default) - ) + modified = sum(1 for app in apps if not isinstance(app.config.USE_UVLOOP, Default)) assert counter[(logging.WARNING, message)] == modified @@ -526,7 +497,7 @@ def test_multiple_uvloop_configs_display_warning(caplog): for app in (default_uvloop, no_uvloop, yes_uvloop): srv_coro = app.create_server( return_asyncio_server=True, - asyncio_server_kwargs=dict(start_serving=False), + asyncio_server_kwargs={"start_serving": False}, ) srv = loop.run_until_complete(srv_coro) loop.run_until_complete(srv.startup()) @@ -561,12 +532,8 @@ def test_no_workers(app: Sanic): {"auto_reload": True}, ), ) -def test_cannot_run_single_process_and_workers_or_auto_reload( - app: Sanic, extra -): - message = ( - "Single process cannot be run with multiple workers or auto-reload" - ) +def test_cannot_run_single_process_and_workers_or_auto_reload(app: Sanic, extra): + message = "Single process cannot be run with multiple workers or auto-reload" with pytest.raises(RuntimeError, match=message): app.run(single_process=True, **extra) diff --git a/tests/test_asgi.py b/tests/test_asgi.py index 8fce84af..c8134b84 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -1,25 +1,21 @@ -import asyncio import logging - from collections import deque, namedtuple from unittest.mock import call import pytest import uvicorn - from httpx import Headers from pytest import MonkeyPatch from sanic import Sanic from sanic.application.state import Mode -from sanic.asgi import ASGIApp, Lifespan, MockTransport +from sanic.asgi import Lifespan, MockTransport from sanic.exceptions import BadRequest, Forbidden, ServiceUnavailable from sanic.request import Request from sanic.response import json, text from sanic.server.websockets.connection import WebSocketConnection from sanic.signals import RESERVED_NAMESPACES - try: from unittest.mock import AsyncMock except ImportError: @@ -356,9 +352,7 @@ async def test_websocket_bytes_receive(send, receive, message_stack): @pytest.mark.asyncio -async def test_websocket_accept_with_no_subprotocols( - send, receive, message_stack -): +async def test_websocket_accept_with_no_subprotocols(send, receive, message_stack): ws = WebSocketConnection(send, receive) await ws.accept() diff --git a/tests/test_blueprint_copy.py b/tests/test_blueprint_copy.py index 5dd742ea..091a9532 100644 --- a/tests/test_blueprint_copy.py +++ b/tests/test_blueprint_copy.py @@ -1,5 +1,4 @@ import pytest - from sanic_routing.exceptions import RouteExists from sanic import Blueprint, Request, Sanic diff --git a/tests/test_blueprint_group.py b/tests/test_blueprint_group.py index 330d5d9a..1c51d4c5 100644 --- a/tests/test_blueprint_group.py +++ b/tests/test_blueprint_group.py @@ -1,5 +1,4 @@ import pytest - from pytest import raises from sanic.app import Sanic @@ -9,7 +8,6 @@ from sanic.exceptions import BadRequest, Forbidden, SanicException, ServerError from sanic.request import Request from sanic.response import HTTPResponse, text - MIDDLEWARE_INVOKE_COUNTER = {"request": 0, "response": 0} AUTH = "dGVzdDp0ZXN0Cg==" @@ -22,7 +20,7 @@ def test_bp_group_indexing(app: Sanic): group = Blueprint.group(blueprint_1, blueprint_2) assert group[0] == blueprint_1 - with raises(expected_exception=IndexError) as e: + with raises(expected_exception=IndexError): _ = group[3] @@ -30,9 +28,7 @@ def test_bp_group_with_additional_route_params(app: Sanic): blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") - @blueprint_1.route( - "/request_path", methods=frozenset({"PUT", "POST"}), version=2 - ) + @blueprint_1.route("/request_path", methods=frozenset({"PUT", "POST"}), version=2) def blueprint_1_v2_method_with_put_and_post(request: Request): if request.method == "PUT": return text("PUT_OK") @@ -48,9 +44,7 @@ def test_bp_group_with_additional_route_params(app: Sanic): elif request.method == "PATCH": return text(f"PATCH_{param}") - blueprint_group = Blueprint.group( - blueprint_1, blueprint_2, url_prefix="/api" - ) + blueprint_group = Blueprint.group(blueprint_1, blueprint_2, url_prefix="/api") @blueprint_group.middleware("request") def authenticate_request(request: Request): @@ -70,15 +64,11 @@ def test_bp_group_with_additional_route_params(app: Sanic): app.blueprint(blueprint_group) header = {"authorization": " ".join(["Basic", AUTH])} - _, response = app.test_client.put( - "/v2/api/bp1/request_path", headers=header - ) + _, response = app.test_client.put("/v2/api/bp1/request_path", headers=header) assert response.text == "PUT_OK" assert response.headers.get("x-test-middleware") == "value" - _, response = app.test_client.post( - "/v2/api/bp1/request_path", headers=header - ) + _, response = app.test_client.post("/v2/api/bp1/request_path", headers=header) assert response.text == "POST_OK" _, response = app.test_client.delete("/api/bp2/route/bp2", headers=header) @@ -111,9 +101,7 @@ def test_bp_group(app: Sanic): def blueprint_2_error(request: Request): raise ServerError("Error") - blueprint_group_1 = Blueprint.group( - blueprint_1, blueprint_2, url_prefix="/bp" - ) + blueprint_group_1 = Blueprint.group(blueprint_1, blueprint_2, url_prefix="/bp") blueprint_3 = Blueprint("blueprint_3", url_prefix="/bp3") @@ -215,9 +203,7 @@ def test_bp_group_list_operations(app: Sanic): def blueprint_2_default_route(request): return text("BP2_OK") - blueprint_group_1 = Blueprint.group( - blueprint_1, blueprint_2, url_prefix="/bp" - ) + blueprint_group_1 = Blueprint.group(blueprint_1, blueprint_2, url_prefix="/bp") blueprint_3 = Blueprint("blueprint_2", url_prefix="/bp3") @@ -249,9 +235,7 @@ def test_bp_group_as_list(): def test_bp_group_as_nested_group(): blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") - blueprint_group_1 = Blueprint.group( - Blueprint.group(blueprint_1, blueprint_2) - ) + blueprint_group_1 = Blueprint.group(Blueprint.group(blueprint_1, blueprint_2)) assert len(blueprint_group_1) == 1 @@ -261,9 +245,7 @@ def test_blueprint_group_insert(): ) blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") blueprint_3 = Blueprint("blueprint_3", url_prefix=None) - group = BlueprintGroup( - url_prefix="/test", version=1.3, strict_slashes=False - ) + group = BlueprintGroup(url_prefix="/test", version=1.3, strict_slashes=False) group.insert(0, blueprint_1) group.insert(0, blueprint_2) group.insert(0, blueprint_3) @@ -352,12 +334,8 @@ async def test_multiple_nested_bp_group(): bp1.add_route(lambda _: ..., "/", name="route1") bp2.add_route(lambda _: ..., "/", name="route2") - group_a = Blueprint.group( - bp1, bp2, url_prefix="/group-a", name_prefix="group-a" - ) - group_b = Blueprint.group( - bp1, bp2, url_prefix="/group-b", name_prefix="group-b" - ) + group_a = Blueprint.group(bp1, bp2, url_prefix="/group-a", name_prefix="group-a") + group_b = Blueprint.group(bp1, bp2, url_prefix="/group-b", name_prefix="group-b") app = Sanic("PropTest") app.blueprint(group_a) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index be49a50f..7f5bcdab 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -11,7 +11,6 @@ from sanic.exceptions import BadRequest, NotFound, SanicException, ServerError from sanic.request import Request from sanic.response import json, text - # ------------------------------------------------------------ # # GET # ------------------------------------------------------------ # @@ -483,7 +482,7 @@ def test_bp_exception_handler_applied(app: Sanic): @handled.exception(Error) def handle_error(req, e): - return text("handled {}".format(e)) + return text(f"handled {e}") @handled.route("/ok") def ok(request): @@ -513,7 +512,7 @@ def test_bp_exception_handler_not_applied(app: Sanic): @handled.exception(Error) def handle_error(req, e): - return text("handled {}".format(e)) + return text(f"handled {e}") @nothandled.route("/notok") def notok(request): @@ -752,9 +751,7 @@ def test_bp_group_with_default_url_prefix(app: Sanic): def api_v1_info(request): return text("api_version: v1") - bp_api_v1_group = Blueprint.group( - bp_api_v1, bp_resources_group, url_prefix="/v1" - ) + bp_api_v1_group = Blueprint.group(bp_api_v1, bp_resources_group, url_prefix="/v1") bp_api_group = Blueprint.group(bp_api_v1_group, url_prefix="/api") app.blueprint(bp_api_group) @@ -795,9 +792,7 @@ def test_blueprint_middleware_with_args(app: Sanic): ) assert response.text == "value" - _, response = app.test_client.get( - "/wa", headers={"content-type": "plain/text"} - ) + _, response = app.test_client.get("/wa", headers={"content-type": "plain/text"}) assert response.json.get("test") == "value" @@ -986,13 +981,9 @@ def test_blueprint_group_strict_slashes(): app = Sanic(name="blueprint-group-test") bp1 = Blueprint(name="bp1", url_prefix=None, strict_slashes=False) - bp2 = Blueprint( - name="bp2", version=3, url_prefix="/bp2", strict_slashes=None - ) + bp2 = Blueprint(name="bp2", version=3, url_prefix="/bp2", strict_slashes=None) - bp3 = Blueprint( - name="bp3", version=None, url_prefix="/bp3/", strict_slashes=None - ) + bp3 = Blueprint(name="bp3", version=None, url_prefix="/bp3/", strict_slashes=None) @bp1.get("/r1") async def bp1_r1(request): diff --git a/tests/test_cancellederror.py b/tests/test_cancellederror.py index b61fd596..32d689a2 100644 --- a/tests/test_cancellederror.py +++ b/tests/test_cancellederror.py @@ -1,9 +1,5 @@ -import asyncio - from asyncio import CancelledError -import pytest - from sanic import Request, Sanic, json diff --git a/tests/test_cli.py b/tests/test_cli.py index 2e980a2c..d327aa1b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,13 +1,11 @@ import json import os import sys - from pathlib import Path from typing import List, Optional, Tuple from unittest.mock import patch import pytest - from sanic_routing import __version__ as __routing_version__ from sanic import __version__ diff --git a/tests/test_coffee.py b/tests/test_coffee.py index 43864b3d..73237ff0 100644 --- a/tests/test_coffee.py +++ b/tests/test_coffee.py @@ -1,5 +1,4 @@ import logging - from unittest.mock import patch import pytest diff --git a/tests/test_config.py b/tests/test_config.py index a702bc88..bb8780d0 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,5 @@ import logging import os - from contextlib import contextmanager from os import environ from pathlib import Path @@ -9,7 +8,6 @@ from textwrap import dedent from unittest.mock import Mock, call import pytest - from pytest import MonkeyPatch from sanic import Sanic @@ -135,9 +133,7 @@ def test_add_converter_multiple_times(caplog): def converter(): ... - message = ( - "Configuration value converter 'converter' has already been registered" - ) + message = "Configuration value converter 'converter' has already been registered" config = Config() config.register_type(converter) with caplog.at_level(logging.WARNING): @@ -308,14 +304,10 @@ async def test_config_access_log_passing_in_create_server(app: Sanic): async def _request(sanic, loop): app.stop() - await app.create_server( - port=1341, access_log=False, return_asyncio_server=True - ) + await app.create_server(port=1341, access_log=False, return_asyncio_server=True) assert app.config.ACCESS_LOG is False - await app.create_server( - port=1342, access_log=True, return_asyncio_server=True - ) + await app.create_server(port=1342, access_log=True, return_asyncio_server=True) assert app.config.ACCESS_LOG is True @@ -336,9 +328,7 @@ def test_config_rewrite_keep_alive(): _test_setting_as_dict = {"TEST_SETTING_VALUE": 1} _test_setting_as_class = type("C", (), {"TEST_SETTING_VALUE": 1}) -_test_setting_as_module = str( - Path(__file__).parent / "static/app_test_config.py" -) +_test_setting_as_module = str(Path(__file__).parent / "static/app_test_config.py") @pytest.mark.parametrize( diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 547cdd42..4caa13af 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -12,7 +12,6 @@ from sanic.exceptions import ServerError from sanic.response import text from sanic.response.convenience import json - # ------------------------------------------------------------ # # GET # ------------------------------------------------------------ # @@ -43,9 +42,7 @@ async def test_cookies_asgi(app): response.cookies["right_back"] = "at you" return response - request, response = await app.asgi_client.get( - "/", cookies={"test": "working!"} - ) + request, response = await app.asgi_client.get("/", cookies={"test": "working!"}) response_cookies = SimpleCookie() response_cookies.load(response.headers.get("set-cookie", {})) @@ -102,9 +99,7 @@ def test_cookie_options(app): response = text("OK") response.cookies["test"] = "at you" response.cookies["test"]["httponly"] = True - response.cookies["test"]["expires"] = datetime.now() + timedelta( - seconds=10 - ) + response.cookies["test"]["expires"] = datetime.now() + timedelta(seconds=10) return response request, response = app.test_client.get("/") @@ -180,17 +175,11 @@ def test_cookie_max_age(app, max_age): response.cookies["test"]["max-age"] = max_age return response - request, response = app.test_client.get( - "/", cookies=cookies, raw_cookies=True - ) + request, response = app.test_client.get("/", cookies=cookies, raw_cookies=True) assert response.status == 200 cookie = response.cookies.get("test") - if ( - str(max_age).isdigit() - and int(max_age) == float(max_age) - and int(max_age) != 0 - ): + if str(max_age).isdigit() and int(max_age) == float(max_age) and int(max_age) != 0: cookie_expires = datetime.utcfromtimestamp( response.raw_cookies["test"].expires ).replace(microsecond=0) @@ -203,9 +192,8 @@ def test_cookie_max_age(app, max_age): ) assert cookie == "pass" - assert ( - cookie_expires == expires - or cookie_expires == expires + timedelta(seconds=-1) + assert cookie_expires == expires or cookie_expires == expires + timedelta( + seconds=-1 ) else: assert cookie is None @@ -222,9 +210,7 @@ def test_cookie_bad_max_age(app, max_age): response.cookies["test"]["max-age"] = max_age return response - request, response = app.test_client.get( - "/", cookies=cookies, raw_cookies=True - ) + request, response = app.test_client.get("/", cookies=cookies, raw_cookies=True) assert response.status == 500 @@ -240,9 +226,7 @@ def test_cookie_expires(app: Sanic, expires: timedelta): response.cookies["test"]["expires"] = expires_time return response - request, response = app.test_client.get( - "/", cookies=cookies, raw_cookies=True - ) + request, response = app.test_client.get("/", cookies=cookies, raw_cookies=True) cookie_expires = datetime.utcfromtimestamp( response.raw_cookies["test"].expires @@ -400,17 +384,13 @@ def test_bad_cookie_prarms(): ServerError, match="Cannot set host_prefix on a cookie unless path='/'", ): - jar.add_cookie( - "foo", "bar", host_prefix=True, secure=True, path="/foo" - ) + jar.add_cookie("foo", "bar", host_prefix=True, secure=True, path="/foo") with pytest.raises( ServerError, match="Cannot set host_prefix on a cookie with a defined domain", ): - jar.add_cookie( - "foo", "bar", host_prefix=True, secure=True, domain="foo.bar" - ) + jar.add_cookie("foo", "bar", host_prefix=True, secure=True, domain="foo.bar") with pytest.raises( ServerError, diff --git a/tests/test_create_task.py b/tests/test_create_task.py index 946c7aaa..cdd9f711 100644 --- a/tests/test_create_task.py +++ b/tests/test_create_task.py @@ -1,6 +1,5 @@ import asyncio import sys - from threading import Event import pytest diff --git a/tests/test_dynamic_routes.py b/tests/test_dynamic_routes.py index 0147f755..6a3cfa37 100644 --- a/tests/test_dynamic_routes.py +++ b/tests/test_dynamic_routes.py @@ -1,5 +1,4 @@ import pytest - from sanic_routing.exceptions import RouteExists from sanic.response import text diff --git a/tests/test_errorpages.py b/tests/test_errorpages.py index 00f45937..1953162d 100644 --- a/tests/test_errorpages.py +++ b/tests/test_errorpages.py @@ -3,7 +3,6 @@ import logging import pytest import sanic - from sanic import Sanic from sanic.config import Config from sanic.errorpages import TextRenderer, exception_response, guess_mime @@ -545,9 +544,7 @@ def test_guess_mime_logging( with caplog.at_level(logging.DEBUG, logger="sanic.root"): guess_mime(fake_request, fallback) - (logmsg,) = [ - r.message for r in caplog.records if r.funcName == "guess_mime" - ] + (logmsg,) = (r.message for r in caplog.records if r.funcName == "guess_mime") assert logmsg == expected @@ -565,9 +562,7 @@ def test_exception_header_on_renderers(app: Sanic, format, expected): @app.get("/test") def test(request): - raise SanicException( - "test", status_code=400, headers={"exception": "test"} - ) + raise SanicException("test", status_code=400, headers={"exception": "test"}) _, response = app.test_client.get("/test") assert response.status == 400 diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 29c4e45c..eb27d143 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,7 +1,6 @@ import logging import pytest - from bs4 import BeautifulSoup from sanic import Sanic @@ -199,9 +198,7 @@ def test_handled_unhandled_exception(exception_app): def test_exception_in_exception_handler(exception_app): """Test that an exception thrown in an error handler is handled""" - request, response = exception_app.test_client.get( - "/error_in_error_handler_handler" - ) + request, response = exception_app.test_client.get("/error_in_error_handler_handler") assert response.status == 500 assert response.body == b"An error occurred while handling an error" @@ -285,15 +282,9 @@ def test_contextual_exception_context(debug): def fail(): raise TeapotError(context={"foo": "bar"}) - app.post("/coffee/json", error_format="json", name="json")( - lambda _: fail() - ) - app.post("/coffee/html", error_format="html", name="html")( - lambda _: fail() - ) - app.post("/coffee/text", error_format="text", name="text")( - lambda _: fail() - ) + app.post("/coffee/json", error_format="json", name="json")(lambda _: fail()) + app.post("/coffee/html", error_format="html", name="html")(lambda _: fail()) + app.post("/coffee/text", error_format="text", name="text")(lambda _: fail()) _, response = app.test_client.post("/coffee/json", debug=debug) assert response.status == 418 @@ -308,7 +299,7 @@ def test_contextual_exception_context(debug): assert dl == {"foo": "bar"} _, response = app.test_client.post("/coffee/text", debug=debug) - lines = list(map(lambda x: x.decode(), response.body.split(b"\n"))) + lines = [x.decode() for x in response.body.split(b"\n")] idx = lines.index("Context") + 1 assert response.status == 418 assert lines[2] == "Sorry, I cannot brew coffee" @@ -329,15 +320,9 @@ def test_contextual_exception_extra(debug): def fail(): raise TeapotError(extra={"foo": "bar"}) - app.post("/coffee/json", error_format="json", name="json")( - lambda _: fail() - ) - app.post("/coffee/html", error_format="html", name="html")( - lambda _: fail() - ) - app.post("/coffee/text", error_format="text", name="text")( - lambda _: fail() - ) + app.post("/coffee/json", error_format="json", name="json")(lambda _: fail()) + app.post("/coffee/html", error_format="html", name="html")(lambda _: fail()) + app.post("/coffee/text", error_format="text", name="text")(lambda _: fail()) _, response = app.test_client.post("/coffee/json", debug=debug) assert response.status == 418 @@ -358,7 +343,7 @@ def test_contextual_exception_extra(debug): assert not dl _, response = app.test_client.post("/coffee/text", debug=debug) - lines = list(map(lambda x: x.decode(), response.body.split(b"\n"))) + lines = [x.decode() for x in response.body.split(b"\n")] assert response.status == 418 assert lines[2] == "Found bar" if debug: diff --git a/tests/test_exceptions_handler.py b/tests/test_exceptions_handler.py index 9c211287..1adff20f 100644 --- a/tests/test_exceptions_handler.py +++ b/tests/test_exceptions_handler.py @@ -1,13 +1,11 @@ import asyncio import logging - from typing import Callable, List from unittest.mock import Mock import pytest - from bs4 import BeautifulSoup -from pytest import LogCaptureFixture, MonkeyPatch, WarningsRecorder +from pytest import LogCaptureFixture, MonkeyPatch from sanic import Sanic, handlers from sanic.exceptions import BadRequest, Forbidden, NotFound, ServerError @@ -135,9 +133,7 @@ def test_inherited_exception_handler(exception_handler_app: Sanic): def test_chained_exception_handler(exception_handler_app: Sanic): - request, response = exception_handler_app.test_client.get( - "/6/0", debug=True - ) + request, response = exception_handler_app.test_client.get("/6/0", debug=True) assert response.status == 500 soup = BeautifulSoup(response.body, "html.parser") @@ -169,7 +165,7 @@ def test_exception_handler_lookup(exception_handler_app: Sanic): pass try: - ModuleNotFoundError + ModuleNotFoundError # noqa: F823 except Exception: class ModuleNotFoundError(ImportError): @@ -184,20 +180,14 @@ def test_exception_handler_lookup(exception_handler_app: Sanic): assert handler.lookup(ModuleNotFoundError(), None) == import_error_handler assert handler.lookup(CustomError(), None) == custom_error_handler assert handler.lookup(ServerError("Error"), None) == server_error_handler - assert ( - handler.lookup(CustomServerError("Error"), None) - == server_error_handler - ) + assert handler.lookup(CustomServerError("Error"), None) == server_error_handler # once again to ensure there is no caching bug assert handler.lookup(ImportError(), None) == import_error_handler assert handler.lookup(ModuleNotFoundError(), None) == import_error_handler assert handler.lookup(CustomError(), None) == custom_error_handler assert handler.lookup(ServerError("Error"), None) == server_error_handler - assert ( - handler.lookup(CustomServerError("Error"), None) - == server_error_handler - ) + assert handler.lookup(CustomServerError("Error"), None) == server_error_handler def test_exception_handler_processed_request_middleware( diff --git a/tests/test_ext_integration.py b/tests/test_ext_integration.py index 228d7624..e2340e25 100644 --- a/tests/test_ext_integration.py +++ b/tests/test_ext_integration.py @@ -1,14 +1,11 @@ import sys -from unittest.mock import MagicMock - import pytest from sanic import Sanic - try: - import sanic_ext + import sanic_ext # noqa: F401 SANIC_EXT_IN_ENV = True except ImportError: @@ -65,9 +62,7 @@ def test_extend_cannot_be_called_multiple_times(app: Sanic, sanic_ext): ) def test_fail_if_not_loaded(app: Sanic): del sys.modules["sanic_ext"] - with pytest.raises( - RuntimeError, match="Sanic Extensions is not installed.*" - ): + with pytest.raises(RuntimeError, match="Sanic Extensions is not installed.*"): app.extend(built_in_extensions=False) diff --git a/tests/test_graceful_shutdown.py b/tests/test_graceful_shutdown.py index d125ba3d..fcc9958f 100644 --- a/tests/test_graceful_shutdown.py +++ b/tests/test_graceful_shutdown.py @@ -5,13 +5,10 @@ from pytest import LogCaptureFixture from sanic.response import empty - PORT = 42101 -def test_no_exceptions_when_cancel_pending_request( - app, caplog: LogCaptureFixture -): +def test_no_exceptions_when_cancel_pending_request(app, caplog: LogCaptureFixture): app.config.GRACEFUL_SHUTDOWN_TIMEOUT = 1 @app.get("/") diff --git a/tests/test_headers.py b/tests/test_headers.py index fbf13d25..1f74cfb0 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -57,7 +57,7 @@ def raised_ceiling(): # Chrome, Firefox: # Content-Disposition: form-data; name="foo%22;bar\"; filename="😀" 'form-data; name="foo%22;bar\\"; filename="😀"', - ("form-data", {"name": 'foo";bar\\', "filename": "😀"}) + ("form-data", {"name": 'foo";bar\\', "filename": "😀"}), # cgi: ('form-data', {'name': 'foo%22;bar"; filename="😀'}) # werkzeug (pre 2.3.0): ('form-data', {'name': 'foo%22;bar"; filename='}) ), @@ -243,9 +243,7 @@ def test_accept_parsed_against_str(): def test_media_type_matching(): - assert headers.MediaType("foo", "bar").match( - headers.MediaType("foo", "bar") - ) + assert headers.MediaType("foo", "bar").match(headers.MediaType("foo", "bar")) assert headers.MediaType("foo", "bar").match("foo/bar") @@ -365,31 +363,23 @@ def test_accept_ordering(raw): def test_not_accept_wildcard(): accept = headers.parse_accept("*/*, foo/*, */bar, foo/bar;q=0.1") - assert not accept.match( - "text/html", "foo/foo", "bar/bar", accept_wildcards=False - ) + assert not accept.match("text/html", "foo/foo", "bar/bar", accept_wildcards=False) # Should ignore wildcards in accept but still matches them from mimes m = accept.match("text/plain", "*/*", accept_wildcards=False) assert m.mime == "*/*" assert m.match("*/*") assert m.header == "foo/bar" - assert not accept.match( - "text/html", "foo/foo", "bar/bar", accept_wildcards=False - ) + assert not accept.match("text/html", "foo/foo", "bar/bar", accept_wildcards=False) def test_accept_misc(): - header = ( - "foo/bar;q=0.0, */plain;param=123, text/plain, text/*, foo/bar;q=0.5" - ) + header = "foo/bar;q=0.0, */plain;param=123, text/plain, text/*, foo/bar;q=0.5" a = headers.parse_accept(header) assert repr(a) == ( - "[*/plain;param=123, text/plain, text/*, " - "foo/bar;q=0.5, foo/bar;q=0.0]" + "[*/plain;param=123, text/plain, text/*, " "foo/bar;q=0.5, foo/bar;q=0.0]" ) # noqa: E501 assert str(a) == ( - "*/plain;param=123, text/plain, text/*, " - "foo/bar;q=0.5, foo/bar;q=0.0" + "*/plain;param=123, text/plain, text/*, " "foo/bar;q=0.5, foo/bar;q=0.0" ) # noqa: E501 # q=1 types don't match foo/bar but match the two others, # text/* comes first and matches */plain because it @@ -402,7 +392,7 @@ def test_accept_misc(): assert m.header.type == "*" assert m.header.subtype == "plain" assert m.header.q == 1.0 - assert m.header.params == dict(param="123") + assert m.header.params == {"param": "123"} # Matches object against another Matched object (by mime and header) assert m == a.match("text/*") # Against unsupported type falls back to object id matching diff --git a/tests/test_http.py b/tests/test_http.py index ca2e86c0..11554663 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1,18 +1,15 @@ import json as stdjson - from collections import namedtuple from pathlib import Path from sys import version_info import pytest - from sanic_testing.reusable import ReusableClient from sanic import json, text from sanic.app import Sanic from tests.client import RawClient - parent_dir = Path(__file__).parent localhost_dir = parent_dir / "certs/localhost" diff --git a/tests/test_http_alt_svc.py b/tests/test_http_alt_svc.py index 1184a8dd..a7c74a77 100644 --- a/tests/test_http_alt_svc.py +++ b/tests/test_http_alt_svc.py @@ -1,5 +1,4 @@ import sys - from pathlib import Path import pytest @@ -8,7 +7,6 @@ from sanic.app import Sanic from sanic.response import empty from tests.client import RawClient - parent_dir = Path(__file__).parent localhost_dir = parent_dir / "certs/localhost" diff --git a/tests/test_json_decoding.py b/tests/test_json_decoding.py index 57fdbc6b..0c871a4c 100644 --- a/tests/test_json_decoding.py +++ b/tests/test_json_decoding.py @@ -2,7 +2,6 @@ from json import loads as sloads import pytest - try: from ujson import loads as uloads diff --git a/tests/test_json_encoding.py b/tests/test_json_encoding.py index f581b3e3..1d652205 100644 --- a/tests/test_json_encoding.py +++ b/tests/test_json_encoding.py @@ -1,5 +1,4 @@ import sys - from dataclasses import asdict, dataclass from functools import partial from json import dumps as sdumps @@ -8,15 +7,11 @@ from typing import Dict import pytest - try: import ujson - from ujson import dumps as udumps - ujson_version = tuple( - map(int, ujson.__version__.strip(ascii_lowercase).split(".")) - ) + ujson_version = tuple(map(int, ujson.__version__.strip(ascii_lowercase).split("."))) NO_UJSON = False DEFAULT_DUMPS = udumps @@ -72,15 +67,11 @@ def test_json_response_ujson(payload: Dict[str, Foo]): response = json(payload) assert response.body == b'{"foo":{"bar":"bar"}}' - with pytest.raises( - TypeError, match="Object of type Foo is not JSON serializable" - ): + with pytest.raises(TypeError, match="Object of type Foo is not JSON serializable"): json(payload, dumps=sdumps) Sanic("Test", dumps=sdumps) - with pytest.raises( - TypeError, match="Object of type Foo is not JSON serializable" - ): + with pytest.raises(TypeError, match="Object of type Foo is not JSON serializable"): json(payload) diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index a69ae49b..7505294e 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -1,19 +1,16 @@ import asyncio import platform - from asyncio import sleep as aio_sleep from itertools import count from os import environ import pytest - from sanic_testing.reusable import ReusableClient from sanic import Sanic from sanic.compat import OS_IS_WINDOWS from sanic.response import text - CONFIG_FOR_TESTS = {"KEEP_ALIVE_TIMEOUT": 2, "KEEP_ALIVE": True} PORT = 42001 # test_keep_alive_timeout_reuse doesn't work with random port @@ -75,9 +72,7 @@ def test_keep_alive_timeout_reuse(): port = get_port() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - client = ReusableClient( - keep_alive_timeout_app_reuse, loop=loop, port=port - ) + client = ReusableClient(keep_alive_timeout_app_reuse, loop=loop, port=port) try: with client: headers = {"Connection": "keep-alive"} @@ -116,14 +111,10 @@ def test_keep_alive_client_timeout(): port = get_port() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - client = ReusableClient( - keep_alive_app_client_timeout, loop=loop, port=port - ) + client = ReusableClient(keep_alive_app_client_timeout, loop=loop, port=port) with client: headers = {"Connection": "keep-alive"} - request, response = client.get( - "/1", headers=headers, timeout=1 - ) + request, response = client.get("/1", headers=headers, timeout=1) assert response.status == 200 assert response.text == "OK" @@ -156,14 +147,10 @@ def test_keep_alive_server_timeout(): port = get_port() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - client = ReusableClient( - keep_alive_app_server_timeout, loop=loop, port=port - ) + client = ReusableClient(keep_alive_app_server_timeout, loop=loop, port=port) with client: headers = {"Connection": "keep-alive"} - request, response = client.get( - "/1", headers=headers, timeout=60 - ) + request, response = client.get("/1", headers=headers, timeout=60) assert response.status == 200 assert response.text == "OK" @@ -193,9 +180,7 @@ def test_keep_alive_connection_context(): port = get_port() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - client = ReusableClient( - keep_alive_app_context, loop=loop, port=port - ) + client = ReusableClient(keep_alive_app_context, loop=loop, port=port) with client: headers = {"Connection": "keep-alive"} request1, _ = client.post("/ctx", headers=headers) @@ -206,9 +191,7 @@ def test_keep_alive_connection_context(): assert response.text == "hello" assert id(request1.conn_info.ctx) == id(request2.conn_info.ctx) assert ( - request1.conn_info.ctx.foo - == request2.conn_info.ctx.foo - == "hello" + request1.conn_info.ctx.foo == request2.conn_info.ctx.foo == "hello" ) assert request2.protocol.state["requests_count"] == 2 except OSError: diff --git a/tests/test_logging.py b/tests/test_logging.py index 3a7ba4f5..84502cad 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,6 +1,5 @@ import logging import uuid - from importlib import reload from io import StringIO from unittest.mock import Mock @@ -8,12 +7,10 @@ from unittest.mock import Mock import pytest import sanic - from sanic import Sanic from sanic.log import LOGGING_CONFIG_DEFAULTS, Colors, logger from sanic.response import text - logging_format = """module: %(module)s; \ function: %(funcName)s(); \ message: %(message)s""" @@ -28,9 +25,7 @@ def test_log(app): log_stream = StringIO() for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) - logging.basicConfig( - format=logging_format, level=logging.DEBUG, stream=log_stream - ) + logging.basicConfig(format=logging_format, level=logging.DEBUG, stream=log_stream) logging.getLogger("asyncio").setLevel(logging.WARNING) log = logging.getLogger() rand_string = str(uuid.uuid4()) @@ -50,26 +45,13 @@ def test_logging_defaults(): Sanic("test_logging") for fmt in [h.formatter for h in logging.getLogger("sanic.root").handlers]: - assert ( - fmt._fmt - == LOGGING_CONFIG_DEFAULTS["formatters"]["generic"]["format"] - ) + assert fmt._fmt == LOGGING_CONFIG_DEFAULTS["formatters"]["generic"]["format"] - for fmt in [ - h.formatter for h in logging.getLogger("sanic.error").handlers - ]: - assert ( - fmt._fmt - == LOGGING_CONFIG_DEFAULTS["formatters"]["generic"]["format"] - ) + for fmt in [h.formatter for h in logging.getLogger("sanic.error").handlers]: + assert fmt._fmt == LOGGING_CONFIG_DEFAULTS["formatters"]["generic"]["format"] - for fmt in [ - h.formatter for h in logging.getLogger("sanic.access").handlers - ]: - assert ( - fmt._fmt - == LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] - ) + for fmt in [h.formatter for h in logging.getLogger("sanic.access").handlers]: + assert fmt._fmt == LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] def test_logging_pass_customer_logconfig(): @@ -88,14 +70,10 @@ def test_logging_pass_customer_logconfig(): for fmt in [h.formatter for h in logging.getLogger("sanic.root").handlers]: assert fmt._fmt == modified_config["formatters"]["generic"]["format"] - for fmt in [ - h.formatter for h in logging.getLogger("sanic.error").handlers - ]: + for fmt in [h.formatter for h in logging.getLogger("sanic.error").handlers]: assert fmt._fmt == modified_config["formatters"]["generic"]["format"] - for fmt in [ - h.formatter for h in logging.getLogger("sanic.access").handlers - ]: + for fmt in [h.formatter for h in logging.getLogger("sanic.access").handlers]: assert fmt._fmt == modified_config["formatters"]["access"]["format"] @@ -111,9 +89,7 @@ def test_log_connection_lost(app, debug, monkeypatch): stream = StringIO() error = logging.getLogger("sanic.error") error.addHandler(logging.StreamHandler(stream)) - monkeypatch.setattr( - sanic.server.protocols.http_protocol, "error_logger", error - ) + monkeypatch.setattr(sanic.server.protocols.http_protocol, "error_logger", error) @app.route("/conn_lost") async def conn_lost(request): @@ -237,9 +213,7 @@ def test_verbosity(app, caplog, app_verbosity, log_verbosity, exists): return text("hello") with caplog.at_level(logging.INFO): - _ = app.test_client.get( - "/", server_kwargs={"verbosity": app_verbosity} - ) + _ = app.test_client.get("/", server_kwargs={"verbosity": app_verbosity}) record = ("sanic.root", logging.INFO, rand_string) diff --git a/tests/test_logo.py b/tests/test_logo.py index 9f3eea2a..72837277 100644 --- a/tests/test_logo.py +++ b/tests/test_logo.py @@ -1,6 +1,5 @@ import os import sys - from unittest.mock import patch import pytest diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 6589f4a4..85336ad9 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -1,5 +1,4 @@ import logging - from asyncio import CancelledError, sleep from itertools import count @@ -7,7 +6,6 @@ from sanic.exceptions import NotFound from sanic.request import Request from sanic.response import HTTPResponse, json, text - # ------------------------------------------------------------ # # GET # ------------------------------------------------------------ # diff --git a/tests/test_middleware_priority.py b/tests/test_middleware_priority.py index c16f658b..abb0189f 100644 --- a/tests/test_middleware_priority.py +++ b/tests/test_middleware_priority.py @@ -6,7 +6,6 @@ from sanic import Sanic from sanic.middleware import Middleware, MiddlewareLocation from sanic.response import json - PRIORITY_TEST_CASES = ( ([0, 1, 2], [1, 1, 1]), ([0, 1, 2], [1, 1, None]), diff --git a/tests/test_motd.py b/tests/test_motd.py index 2e9eeab7..f9d76326 100644 --- a/tests/test_motd.py +++ b/tests/test_motd.py @@ -2,7 +2,6 @@ import logging import os import platform import sys - from unittest.mock import Mock, patch import pytest @@ -75,9 +74,7 @@ def test_reload_dirs(app): app.config.AUTO_RELOAD = True with patch.object(MOTD, "output") as mock: - app.prepare( - reload_dir="./", auto_reload=True, motd_display={"foo": "bar"} - ) + app.prepare(reload_dir="./", auto_reload=True, motd_display={"foo": "bar"}) mock.assert_called() assert mock.call_args.args[2]["auto-reload"] == f"enabled, {os.getcwd()}" assert mock.call_args.args[3] == {"foo": "bar"} diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py index 6e5569d1..d7e3bd0b 100644 --- a/tests/test_multiprocessing.py +++ b/tests/test_multiprocessing.py @@ -4,17 +4,14 @@ import pickle import random import signal import sys - from asyncio import sleep import pytest - from sanic_testing.testing import HOST, PORT from sanic import Blueprint, text from sanic.compat import use_context from sanic.log import logger -from sanic.server.socket import configure_socket @pytest.mark.skipif( @@ -165,11 +162,5 @@ def test_main_process_event(app, caplog): with caplog.at_level(logging.INFO): app.run(HOST, PORT, workers=num_workers) - assert ( - caplog.record_tuples.count(("sanic.root", 20, "main_process_start")) - == 2 - ) - assert ( - caplog.record_tuples.count(("sanic.root", 20, "main_process_stop")) - == 2 - ) + assert caplog.record_tuples.count(("sanic.root", 20, "main_process_start")) == 2 + assert caplog.record_tuples.count(("sanic.root", 20, "main_process_stop")) == 2 diff --git a/tests/test_named_routes.py b/tests/test_named_routes.py index b4ab249b..ac48cc8f 100644 --- a/tests/test_named_routes.py +++ b/tests/test_named_routes.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import asyncio @@ -11,7 +10,6 @@ from sanic.constants import HTTP_METHODS from sanic.exceptions import URLBuildError from sanic.response import text - # ------------------------------------------------------------ # # UTF-8 # ------------------------------------------------------------ # @@ -285,9 +283,7 @@ def test_dynamic_named_route_path(): def test_dynamic_named_route_unhashable(): app = Sanic("app") - @app.route( - "/folder//end/", name="route_unhashable" - ) + @app.route("/folder//end/", name="route_unhashable") async def handler(request, unhashable): return text("OK") @@ -368,10 +364,7 @@ def test_dynamic_add_named_route(): return text("OK") app.add_route(handler, "/folder/", name="route_dynamic") - assert ( - app.router.routes_all[("folder", "")].name - == "app.route_dynamic" - ) + assert app.router.routes_all[("folder", "")].name == "app.route_dynamic" assert app.url_for("route_dynamic", name="test") == "/folder/test" with pytest.raises(URLBuildError): app.url_for("handler") diff --git a/tests/test_pipelining.py b/tests/test_pipelining.py index 49d113c9..d3a4f7a5 100644 --- a/tests/test_pipelining.py +++ b/tests/test_pipelining.py @@ -95,6 +95,4 @@ def test_bad_headers(app): assert response1.status == 200 assert response2.status == 413 - assert ( - response1.headers["x-request-id"] != response2.headers["x-request-id"] - ) + assert response1.headers["x-request-id"] != response2.headers["x-request-id"] diff --git a/tests/test_prepare.py b/tests/test_prepare.py index 79786964..5fd55410 100644 --- a/tests/test_prepare.py +++ b/tests/test_prepare.py @@ -1,6 +1,5 @@ import logging import os - from pathlib import Path from unittest.mock import Mock diff --git a/tests/test_reloader.py b/tests/test_reloader.py index ad8c5665..1e5726a4 100644 --- a/tests/test_reloader.py +++ b/tests/test_reloader.py @@ -1,7 +1,6 @@ import os import secrets import sys - from contextlib import suppress from subprocess import PIPE, Popen, TimeoutExpired from tempfile import TemporaryDirectory @@ -11,7 +10,6 @@ from time import sleep import pytest - # We need to interrupt the autoreloader without killing it, so that the server gets terminated # https://stefan.sofa-rockers.org/2013/08/15/handling-sub-process-hierarchies-python-linux-os-x/ @@ -129,10 +127,10 @@ def scanner(proc, trigger="complete"): yield line -argv = dict( - script=[sys.executable, "reloader.py"], - module=[sys.executable, "-m", "reloader"], - sanic=[ +argv = { + "script": [sys.executable, "reloader.py"], + "module": [sys.executable, "-m", "reloader"], + "sanic": [ sys.executable, "-m", "sanic", @@ -141,14 +139,14 @@ argv = dict( "--auto-reload", "reloader.app", ], -) +} @pytest.mark.parametrize( "runargs, mode", [ - (dict(port=42202, auto_reload=True), "script"), - (dict(port=42203, auto_reload=True), "module"), + ({"port": 42202, "auto_reload": True}, "script"), + ({"port": 42203, "auto_reload": True}, "module"), ({}, "sanic"), ], ) @@ -180,8 +178,8 @@ async def test_reloader_live(runargs, mode): @pytest.mark.parametrize( "runargs, mode", [ - (dict(port=42302, auto_reload=True), "script"), - (dict(port=42303, auto_reload=True), "module"), + ({"port": 42302, "auto_reload": True}, "script"), + ({"port": 42303, "auto_reload": True}, "module"), ({}, "sanic"), ], ) @@ -222,9 +220,7 @@ def test_reload_listeners(): filename, port=42305, auto_reload=True ) - proc = Popen( - argv["script"], cwd=tmpdir, stdout=PIPE, creationflags=flags - ) + proc = Popen(argv["script"], cwd=tmpdir, stdout=PIPE, creationflags=flags) try: timeout = Timer(TIMER_DELAY, terminate, [proc]) timeout.start() diff --git a/tests/test_request.py b/tests/test_request.py index 2a527043..5158ed23 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,5 +1,4 @@ import uuid - from unittest.mock import Mock from uuid import UUID, uuid4 @@ -76,9 +75,7 @@ def test_request_id(request_id, expected_type): async def get(request): return response.empty() - request, _ = app.test_client.get( - "/", headers={"X-REQUEST-ID": f"{request_id}"} - ) + request, _ = app.test_client.get("/", headers={"X-REQUEST-ID": f"{request_id}"}) assert request.id == request_id assert type(request.id) == expected_type @@ -169,9 +166,7 @@ def test_request_accept(): assert match.header.mime == "text/plain" assert match.header.params == {"format": "flowed"} - header_value = ( - "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c" - ) + header_value = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c" request, _ = app.test_client.get( "/", headers={"Accept": header_value}, diff --git a/tests/test_request_data.py b/tests/test_request_data.py index 51f2b230..3e37f239 100644 --- a/tests/test_request_data.py +++ b/tests/test_request_data.py @@ -2,7 +2,6 @@ import random from sanic.response import json - try: from ujson import loads except ImportError: diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py index 1513f878..0d7bd1d8 100644 --- a/tests/test_request_stream.py +++ b/tests/test_request_stream.py @@ -8,7 +8,6 @@ from sanic.response import json, text from sanic.views import HTTPMethodView from sanic.views import stream as stream_decorator - data = "abc" * 1_000_000 @@ -370,9 +369,7 @@ def test_request_stream_blueprint(app): result += body.decode("utf-8") return text(result) - bp.add_route( - post_add_route, "/post/add_route", methods=["POST"], stream=True - ) + bp.add_route(post_add_route, "/post/add_route", methods=["POST"], stream=True) app.blueprint(bp) request, response = app.test_client.get("/get") @@ -573,7 +570,7 @@ def test_streaming_echo(): async def client(app, reader, writer): # Unfortunately httpx does not support 2-way streaming, so do it by hand. - host = f"host: localhost:8000\r\n".encode() + host = b"host: localhost:8000\r\n" writer.write( b"POST /echo HTTP/1.1\r\n" + host + b"content-length: 2\r\n" b"content-type: text/plain; charset=utf-8\r\n" @@ -581,7 +578,7 @@ def test_streaming_echo(): ) # Read response res = b"" - while not b"\r\n\r\n" in res: + while b"\r\n\r\n" not in res: res += await reader.read(4096) assert res.startswith(b"HTTP/1.1 200 OK\r\n") assert res.endswith(b"\r\n\r\n") @@ -589,7 +586,7 @@ def test_streaming_echo(): async def read_chunk(): nonlocal buffer - while not b"\r\n" in buffer: + while b"\r\n" not in buffer: data = await reader.read(4096) assert data buffer += data @@ -618,6 +615,6 @@ def test_streaming_echo(): assert res == b"-" res = await read_chunk() - assert res == None + assert res is None app.run(access_log=False, single_process=True) diff --git a/tests/test_requests.py b/tests/test_requests.py index 6719025b..ea6c998a 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1,12 +1,10 @@ import base64 import logging - from json import dumps as json_dumps from json import loads as json_loads from urllib.parse import urlparse import pytest - from sanic_testing.testing import ( ASGI_BASE_URL, ASGI_PORT, @@ -70,9 +68,7 @@ async def test_url_asgi(app): request, response = await app.asgi_client.get("/") - if response.body.decode().endswith("/") and not ASGI_BASE_URL.endswith( - "/" - ): + if response.body.decode().endswith("/") and not ASGI_BASE_URL.endswith("/"): response.body[:-1] == ASGI_BASE_URL.encode() else: assert response.body == ASGI_BASE_URL.encode() @@ -389,9 +385,7 @@ def test_token(app, auth_type, token): if token: headers = { "content-type": "application/json", - "Authorization": f"{auth_type} {token}" - if auth_type - else f"{token}", + "Authorization": f"{auth_type} {token}" if auth_type else f"{token}", } else: headers = {"content-type": "application/json"} @@ -428,9 +422,7 @@ def test_credentials(app, capfd, auth_type, token, username, password): if token: headers = { "content-type": "application/json", - "Authorization": f"{auth_type} {token}" - if auth_type - else f"{token}", + "Authorization": f"{auth_type} {token}" if auth_type else f"{token}", } else: headers = {"content-type": "application/json"} @@ -651,9 +643,7 @@ async def test_standard_forwarded_asgi(app): assert response.json == {"for": "127.0.0.2", "proto": "ws"} # Header present but not matching anything - request, response = await app.asgi_client.get( - "/", headers={"Forwarded": "."} - ) + request, response = await app.asgi_client.get("/", headers={"Forwarded": "."}) assert response.json == {} # Forwarded header present but no matching secret -> use X-headers @@ -755,9 +745,7 @@ def test_remote_addr_with_two_proxies(app): assert request.remote_addr == "127.0.0.1" assert response.body == b"127.0.0.1" - headers = { - "X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2" - } + headers = {"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"} request, response = app.test_client.get("/", headers=headers) assert request.remote_addr == "127.0.0.1" assert response.body == b"127.0.0.1" @@ -796,9 +784,7 @@ async def test_remote_addr_with_two_proxies_asgi(app): assert request.remote_addr == "127.0.0.1" assert response.body == b"127.0.0.1" - headers = { - "X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2" - } + headers = {"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"} request, response = await app.asgi_client.get("/", headers=headers) assert request.remote_addr == "127.0.0.1" assert response.body == b"127.0.0.1" @@ -994,9 +980,7 @@ def test_post_form_urlencoded(app): payload = "test=OK" headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = app.test_client.post( - "/", data=payload, headers=headers - ) + request, response = app.test_client.post("/", data=payload, headers=headers) assert request.form.get("test") == "OK" assert request.form.get("test") == "OK" # For request.parsed_form @@ -1011,9 +995,7 @@ async def test_post_form_urlencoded_asgi(app): payload = "test=OK" headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = await app.asgi_client.post( - "/", data=payload, headers=headers - ) + request, response = await app.asgi_client.post("/", data=payload, headers=headers) assert request.form.get("test") == "OK" assert request.form.get("test") == "OK" # For request.parsed_form @@ -1028,9 +1010,7 @@ def test_post_form_urlencoded_keep_blanks(app): payload = "test=" headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = app.test_client.post( - "/", data=payload, headers=headers - ) + request, response = app.test_client.post("/", data=payload, headers=headers) assert request.form.get("test") == "" assert request.form.get("test") == "" # For request.parsed_form @@ -1046,9 +1026,7 @@ async def test_post_form_urlencoded_keep_blanks_asgi(app): payload = "test=" headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = await app.asgi_client.post( - "/", data=payload, headers=headers - ) + request, response = await app.asgi_client.post("/", data=payload, headers=headers) assert request.form.get("test") == "" assert request.form.get("test") == "" # For request.parsed_form @@ -1062,9 +1040,7 @@ def test_post_form_urlencoded_drop_blanks(app): payload = "test=" headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = app.test_client.post( - "/", data=payload, headers=headers - ) + request, response = app.test_client.post("/", data=payload, headers=headers) assert "test" not in request.form.keys() @@ -1078,9 +1054,7 @@ async def test_post_form_urlencoded_drop_blanks_asgi(app): payload = "test=" headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = await app.asgi_client.post( - "/", data=payload, headers=headers - ) + request, response = await app.asgi_client.post("/", data=payload, headers=headers) assert "test" not in request.form.keys() @@ -1135,9 +1109,7 @@ async def test_post_form_multipart_form_data_asgi(app, payload): headers = {"content-type": "multipart/form-data; boundary=----sanic"} - request, response = await app.asgi_client.post( - "/", data=payload, headers=headers - ) + request, response = await app.asgi_client.post("/", data=payload, headers=headers) assert request.form.get("test") == "OK" @@ -1202,9 +1174,7 @@ def test_form_with_multiple_values(app): headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = app.test_client.post( - "/", data=payload, headers=headers - ) + request, response = app.test_client.post("/", data=payload, headers=headers) assert request.form.getlist("selectedItems") == ["v1", "v2", "v3"] @@ -1219,9 +1189,7 @@ async def test_form_with_multiple_values_asgi(app): headers = {"content-type": "application/x-www-form-urlencoded"} - request, response = await app.asgi_client.post( - "/", data=payload, headers=headers - ) + request, response = await app.asgi_client.post("/", data=payload, headers=headers) assert request.form.getlist("selectedItems") == ["v1", "v2", "v3"] @@ -1447,9 +1415,7 @@ def test_request_multipart_file_without_field_name(app, caplog): headers = {"content-type": "multipart/form-data; boundary=------sanic"} - request, _ = app.test_client.post( - data=payload, headers=headers, debug=True - ) + request, _ = app.test_client.post(data=payload, headers=headers, debug=True) with caplog.at_level(logging.DEBUG): request.form @@ -1486,9 +1452,7 @@ def test_request_multipart_file_duplicate_filed_name(app): "Content-Type": "multipart/form-data; boundary=e73ffaa8b1b2472b8ec848de833cb05b" } - request, _ = app.test_client.post( - data=payload, headers=headers, debug=True - ) + request, _ = app.test_client.post(data=payload, headers=headers, debug=True) assert request.form.getlist("file") == [ '{"test":"json"}', '{"test":"json2"}', @@ -1609,9 +1573,7 @@ def test_request_parsing_form_failed(app, caplog): payload = "test=OK" headers = {"content-type": "multipart/form-data"} - request, response = app.test_client.post( - "/", data=payload, headers=headers - ) + request, response = app.test_client.post("/", data=payload, headers=headers) with caplog.at_level(logging.ERROR): request.form @@ -1632,9 +1594,7 @@ async def test_request_parsing_form_failed_asgi(app, caplog): payload = "test=OK" headers = {"content-type": "multipart/form-data"} - request, response = await app.asgi_client.post( - "/", data=payload, headers=headers - ) + request, response = await app.asgi_client.post("/", data=payload, headers=headers) with caplog.at_level(logging.ERROR): request.form @@ -1745,9 +1705,7 @@ def test_request_query_args_custom_parsing(app): def handler(request): return text("pass") - request, response = app.test_client.get( - "/?test1=value1&test2=&test3=value3" - ) + request, response = app.test_client.get("/?test1=value1&test2=&test3=value3") assert request.get_query_args(keep_blank_values=True) == [ ("test1", "value1"), @@ -1764,9 +1722,7 @@ def test_request_query_args_custom_parsing(app): {"test1": ["value1"], "test2": [""], "test3": ["value3"]} ) - assert request.args == RequestParameters( - {"test1": ["value1"], "test3": ["value3"]} - ) + assert request.args == RequestParameters({"test1": ["value1"], "test3": ["value3"]}) assert request.get_args(keep_blank_values=False) == RequestParameters( {"test1": ["value1"], "test3": ["value3"]} @@ -1779,9 +1735,7 @@ async def test_request_query_args_custom_parsing_asgi(app): def handler(request): return text("pass") - request, response = await app.asgi_client.get( - "/?test1=value1&test2=&test3=value3" - ) + request, response = await app.asgi_client.get("/?test1=value1&test2=&test3=value3") assert request.get_query_args(keep_blank_values=True) == [ ("test1", "value1"), @@ -1798,9 +1752,7 @@ async def test_request_query_args_custom_parsing_asgi(app): {"test1": ["value1"], "test2": [""], "test3": ["value3"]} ) - assert request.args == RequestParameters( - {"test1": ["value1"], "test3": ["value3"]} - ) + assert request.args == RequestParameters({"test1": ["value1"], "test3": ["value3"]}) assert request.get_args(keep_blank_values=False) == RequestParameters( {"test1": ["value1"], "test3": ["value3"]} @@ -1909,9 +1861,7 @@ def test_request_server_name_in_host_header(app): def handler(request): return text("OK") - request, response = app.test_client.get( - "/", headers={"Host": "my-server:5555"} - ) + request, response = app.test_client.get("/", headers={"Host": "my-server:5555"}) assert request.server_name == "my-server" request, response = app.test_client.get( @@ -1919,9 +1869,7 @@ def test_request_server_name_in_host_header(app): ) assert request.server_name == "[2a00:1450:400f:80c::200e]" - request, response = app.test_client.get( - "/", headers={"Host": "mal_formed"} - ) + request, response = app.test_client.get("/", headers={"Host": "mal_formed"}) assert request.server_name == "" @@ -1957,9 +1905,7 @@ def test_request_server_port_in_host_header(app): def handler(request): return text("OK") - request, response = app.test_client.get( - "/", headers={"Host": "my-server:5555"} - ) + request, response = app.test_client.get("/", headers={"Host": "my-server:5555"}) assert request.server_port == 5555 request, response = app.test_client.get( @@ -1967,9 +1913,7 @@ def test_request_server_port_in_host_header(app): ) assert request.server_port == 5555 - request, response = app.test_client.get( - "/", headers={"Host": "mal_formed:5555"} - ) + request, response = app.test_client.get("/", headers={"Host": "mal_formed:5555"}) if PORT is None: assert request.server_port != 5555 else: @@ -2011,11 +1955,11 @@ def test_server_name_and_url_for(app): app.config.SERVER_NAME = "my-server" # This means default port assert app.url_for("handler", _external=True) == "http://my-server/foo" request, response = app.test_client.get("/foo") - assert request.url_for("handler") == f"http://my-server/foo" + assert request.url_for("handler") == "http://my-server/foo" app.config.SERVER_NAME = "https://my-server/path" request, response = app.test_client.get("/foo") - url = f"https://my-server/path/foo" + url = "https://my-server/path/foo" assert app.url_for("handler", _external=True) == url assert request.url_for("handler") == url @@ -2040,13 +1984,8 @@ def test_url_for_with_forwarded_request(app): }, ) assert app.url_for("view_name") == "/another_view" - assert ( - app.url_for("view_name", _external=True) - == "http://my-server/another_view" - ) - assert ( - request.url_for("view_name") == "https://my-server:6789/another_view" - ) + assert app.url_for("view_name", _external=True) == "http://my-server/another_view" + assert request.url_for("view_name") == "https://my-server:6789/another_view" request, response = app.test_client.get( "/", @@ -2161,10 +2100,7 @@ def test_url_for_without_server_name(app): return text("url-for") request, response = app.test_client.get("/sample") - assert ( - response.json["url"] - == f"http://127.0.0.1:{request.server_port}/url-for" - ) + assert response.json["url"] == f"http://127.0.0.1:{request.server_port}/url-for" def test_safe_method_with_body_ignored(app): @@ -2180,7 +2116,7 @@ def test_safe_method_with_body_ignored(app): ) assert request.body == b"" - assert request.json == None + assert request.json is None assert response.body == b"OK" @@ -2211,7 +2147,7 @@ async def test_conflicting_body_methods_overload_error(app: Sanic): with pytest.raises( ServerError, - match="Duplicate route names detected: test_conflicting_body_methods_overload_error\.put.*", + match=r"Duplicate route names detected: test_conflicting_body_methods_overload_error\.put.*", ): await app._startup() @@ -2221,15 +2157,11 @@ def test_conflicting_body_methods_overload(app: Sanic): @app.put("/p/", name="two") @app.put("/p/", name="three") async def put(request, foo=None): - return json( - {"name": request.route.name, "body": str(request.body), "foo": foo} - ) + return json({"name": request.route.name, "body": str(request.body), "foo": foo}) @app.delete("/p/") async def delete(request, foo): - return json( - {"name": request.route.name, "body": str(request.body), "foo": foo} - ) + return json({"name": request.route.name, "body": str(request.body), "foo": foo}) payload = {"test": "OK"} data = str(json_dumps(payload).encode()) @@ -2260,7 +2192,7 @@ def test_conflicting_body_methods_overload(app: Sanic): assert response.json == { "name": "test_conflicting_body_methods_overload.delete", "foo": "test", - "body": str("".encode()), + "body": str(b""), } @@ -2273,7 +2205,7 @@ async def test_handler_overload_error(app: Sanic): with pytest.raises( ServerError, - match="Duplicate route names detected: test_handler_overload_error\.handler.*", + match=r"Duplicate route names detected: test_handler_overload_error\.handler.*", ): await app._startup() @@ -2287,9 +2219,7 @@ def test_handler_overload(app: Sanic): def handler(request, **kwargs): return json(kwargs) - _, response = app.test_client.get( - "/long/sub/route/param_a/foo/param_b/bar" - ) + _, response = app.test_client.get("/long/sub/route/param_a/foo/param_b/bar") assert response.status == 200 assert response.json == { "param_a": "foo", diff --git a/tests/test_response.py b/tests/test_response.py index db3036ea..806e622f 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -2,7 +2,6 @@ import asyncio import inspect import os import time - from collections import namedtuple from datetime import datetime, timedelta from email.utils import formatdate, parsedate_to_datetime @@ -14,7 +13,6 @@ from typing import Callable, List, Union from urllib.parse import unquote import pytest - from aiofiles import os as async_os from pytest import LogCaptureFixture @@ -33,7 +31,6 @@ from sanic.response import ( text, ) - JSON_DATA = {"ok": True} @@ -130,14 +127,10 @@ def test_response_content_length(app): ) _, response = app.test_client.get("/response_with_space") - content_length_for_response_with_space = response.headers.get( - "Content-Length" - ) + content_length_for_response_with_space = response.headers.get("Content-Length") _, response = app.test_client.get("/response_without_space") - content_length_for_response_without_space = response.headers.get( - "Content-Length" - ) + content_length_for_response_without_space = response.headers.get("Content-Length") assert ( content_length_for_response_with_space @@ -292,9 +285,7 @@ def test_stream_response_with_cookies(app): cookies = CookieJar(headers) cookies["test"] = "modified" cookies["test"] = "pass" - response = await request.respond( - content_type="text/csv", headers=headers - ) + response = await request.respond(content_type="text/csv", headers=headers) await response.send("foo,") await asyncio.sleep(0.001) @@ -343,9 +334,7 @@ def get_file_last_modified_timestamp( return (static_file_directory / file_name).stat().st_mtime -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) @pytest.mark.parametrize("status", [200, 401]) def test_file_response(app: Sanic, file_name, static_file_directory, status): @app.route("/files/", methods=["GET"]) @@ -372,9 +361,7 @@ def test_file_response(app: Sanic, file_name, static_file_directory, status): ("python.png", "logo.png"), ], ) -def test_file_response_custom_filename( - app: Sanic, source, dest, static_file_directory -): +def test_file_response_custom_filename(app: Sanic, source, dest, static_file_directory): @app.route("/files/", methods=["GET"]) def file_route(request, filename): file_path = os.path.join(static_file_directory, filename) @@ -384,10 +371,7 @@ def test_file_response_custom_filename( request, response = app.test_client.get(f"/files/{source}") assert response.status == 200 assert response.body == get_file_content(static_file_directory, source) - assert ( - response.headers["Content-Disposition"] - == f'attachment; filename="{dest}"' - ) + assert response.headers["Content-Disposition"] == f'attachment; filename="{dest}"' @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) @@ -421,9 +405,7 @@ def test_file_head_response(app: Sanic, file_name, static_file_directory): ) -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) def test_file_stream_response(app: Sanic, file_name, static_file_directory): @app.route("/files/", methods=["GET"]) def file_route(request, filename): @@ -461,16 +443,11 @@ def test_file_stream_response_custom_filename( request, response = app.test_client.get(f"/files/{source}") assert response.status == 200 assert response.body == get_file_content(static_file_directory, source) - assert ( - response.headers["Content-Disposition"] - == f'attachment; filename="{dest}"' - ) + assert response.headers["Content-Disposition"] == f'attachment; filename="{dest}"' @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) -def test_file_stream_head_response( - app: Sanic, file_name, static_file_directory -): +def test_file_stream_head_response(app: Sanic, file_name, static_file_directory): @app.route("/files/", methods=["GET", "HEAD"]) async def file_route(request, filename): file_path = os.path.join(static_file_directory, filename) @@ -507,12 +484,8 @@ def test_file_stream_head_response( ) -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) -@pytest.mark.parametrize( - "size,start,end", [(1024, 0, 1024), (4096, 1024, 8192)] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) +@pytest.mark.parametrize("size,start,end", [(1024, 0, 1024), (4096, 1024, 8192)]) def test_file_stream_response_range( app: Sanic, file_name, static_file_directory, size, start, end ): @@ -610,7 +583,7 @@ def test_multiple_responses( @app.get("/4") async def handler4(request: Request): - response = await request.respond(headers={"one": "one"}) + await request.respond(headers={"one": "one"}) return json({"foo": "bar"}, headers={"one": "two"}) @app.get("/5") @@ -641,11 +614,6 @@ def test_multiple_responses( "been responded to." ) - error_msg3 = ( - "Response stream was ended, no more " - "response data is allowed to be sent." - ) - with caplog.at_level(ERROR): _, response = app.test_client.get("/1") assert response.status == 200 @@ -708,8 +676,7 @@ def send_response_after_eof_should_fail( ) error_msg2 = ( - "Response stream was ended, no more " - "response data is allowed to be sent." + "Response stream was ended, no more " "response data is allowed to be sent." ) with caplog.at_level(ERROR): @@ -719,48 +686,30 @@ def send_response_after_eof_should_fail( assert message_in_records(caplog.records, error_msg2) -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) -def test_file_response_headers( - app: Sanic, file_name: str, static_file_directory: str -): +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) +def test_file_response_headers(app: Sanic, file_name: str, static_file_directory: str): test_last_modified = datetime.now() test_max_age = 10 test_expires = test_last_modified.timestamp() + test_max_age @app.route("/files/cached/", methods=["GET"]) def file_route_cache(request: Request, filename: str): - file_path = ( - Path(static_file_directory) / unquote(filename) - ).absolute() - return file( - file_path, max_age=test_max_age, last_modified=test_last_modified - ) + file_path = (Path(static_file_directory) / unquote(filename)).absolute() + return file(file_path, max_age=test_max_age, last_modified=test_last_modified) - @app.route( - "/files/cached_default_last_modified/", methods=["GET"] - ) - def file_route_cache_default_last_modified( - request: Request, filename: str - ): - file_path = ( - Path(static_file_directory) / unquote(filename) - ).absolute() + @app.route("/files/cached_default_last_modified/", methods=["GET"]) + def file_route_cache_default_last_modified(request: Request, filename: str): + file_path = (Path(static_file_directory) / unquote(filename)).absolute() return file(file_path, max_age=test_max_age) @app.route("/files/no_cache/", methods=["GET"]) def file_route_no_cache(request: Request, filename: str): - file_path = ( - Path(static_file_directory) / unquote(filename) - ).absolute() + file_path = (Path(static_file_directory) / unquote(filename)).absolute() return file(file_path) @app.route("/files/no_store/", methods=["GET"]) def file_route_no_store(request: Request, filename: str): - file_path = ( - Path(static_file_directory) / unquote(filename) - ).absolute() + file_path = (Path(static_file_directory) / unquote(filename)).absolute() return file(file_path, no_store=True) _, response = app.test_client.get(f"/files/cached/{file_name}") @@ -769,12 +718,11 @@ def test_file_response_headers( assert ( "cache-control" in headers and f"max-age={test_max_age}" in headers.get("cache-control") - and f"public" in headers.get("cache-control") + and "public" in headers.get("cache-control") ) assert ( "expires" in headers - and headers.get("expires")[:-6] - == formatdate(test_expires, usegmt=True)[:-6] + and headers.get("expires")[:-6] == formatdate(test_expires, usegmt=True)[:-6] # [:-6] to allow at most 1 min difference # It's minimal for cases like: # Thu, 26 May 2022 05:36:59 GMT @@ -782,9 +730,9 @@ def test_file_response_headers( # Thu, 26 May 2022 05:37:00 GMT ) assert response.status == 200 - assert "last-modified" in headers and headers.get( - "last-modified" - ) == formatdate(test_last_modified.timestamp(), usegmt=True) + assert "last-modified" in headers and headers.get("last-modified") == formatdate( + test_last_modified.timestamp(), usegmt=True + ) _, response = app.test_client.get( f"/files/cached_default_last_modified/{file_name}" @@ -793,23 +741,19 @@ def test_file_response_headers( static_file_directory, file_name ) headers = response.headers - assert "last-modified" in headers and headers.get( - "last-modified" - ) == formatdate(file_last_modified, usegmt=True) + assert "last-modified" in headers and headers.get("last-modified") == formatdate( + file_last_modified, usegmt=True + ) assert response.status == 200 _, response = app.test_client.get(f"/files/no_cache/{file_name}") headers = response.headers - assert "cache-control" in headers and f"no-cache" == headers.get( - "cache-control" - ) + assert "cache-control" in headers and "no-cache" == headers.get("cache-control") assert response.status == 200 _, response = app.test_client.get(f"/files/no_store/{file_name}") headers = response.headers - assert "cache-control" in headers and f"no-store" == headers.get( - "cache-control" - ) + assert "cache-control" in headers and "no-store" == headers.get("cache-control") assert response.status == 200 @@ -857,17 +801,13 @@ def test_file_validate(app: Sanic, static_file_directory: str): file_path.unlink() -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) def test_file_validating_invalid_header( app: Sanic, file_name: str, static_file_directory: str ): @app.route("/files/", methods=["GET"]) def file_route(request: Request, filename: str): - handler_file_path = ( - Path(static_file_directory) / unquote(filename) - ).absolute() + handler_file_path = (Path(static_file_directory) / unquote(filename)).absolute() return file( handler_file_path, @@ -892,17 +832,13 @@ def test_file_validating_invalid_header( assert response.body == get_file_content(static_file_directory, file_name) -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) def test_file_validating_304_response( app: Sanic, file_name: str, static_file_directory: str ): @app.route("/files/", methods=["GET"]) def file_route(request: Request, filename: str): - handler_file_path = ( - Path(static_file_directory) / unquote(filename) - ).absolute() + handler_file_path = (Path(static_file_directory) / unquote(filename)).absolute() return file( handler_file_path, @@ -922,9 +858,7 @@ def test_file_validating_304_response( assert response.body == b"" -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) def test_file_validating_304_response( app: Sanic, file_name: str, static_file_directory: str ): @@ -938,9 +872,7 @@ def test_file_validating_304_response( _, response = app.test_client.get( "/static", headers={ - "if-modified-since": formatdate( - last_modified.timestamp(), usegmt=True - ) + "if-modified-since": formatdate(last_modified.timestamp(), usegmt=True) }, ) assert response.status == 304 diff --git a/tests/test_response_json.py b/tests/test_response_json.py index aa1c61dd..69179925 100644 --- a/tests/test_response_json.py +++ b/tests/test_response_json.py @@ -1,5 +1,4 @@ import json - from functools import partial from unittest.mock import Mock @@ -10,7 +9,6 @@ from sanic.exceptions import SanicException from sanic.response import json as json_response from sanic.response.types import JSONResponse - JSON_BODY = {"ok": True} json_dumps = partial(json.dumps, separators=(",", ":")) @@ -117,7 +115,7 @@ def test_custom_dumps_and_kwargs(json_app: Sanic): return json_response(JSON_BODY, dumps=custom_dumps, prry="platypus") _, resp = json_app.test_client.get("/json-custom") - assert resp.body == "custom".encode() + assert resp.body == b"custom" custom_dumps.assert_called_once_with(JSON_BODY, prry="platypus") @@ -135,7 +133,7 @@ def test_override_dumps_and_kwargs(json_app: Sanic): _, resp = json_app.test_client.get("/json-custom") - assert resp.body == "custom2".encode() + assert resp.body == b"custom2" custom_dumps_1.assert_called_once_with(JSON_BODY, prry="platypus") custom_dumps_2.assert_called_once_with(JSON_BODY, platypus="prry") diff --git a/tests/test_response_timeout.py b/tests/test_response_timeout.py index bd4ccc9b..84424c9d 100644 --- a/tests/test_response_timeout.py +++ b/tests/test_response_timeout.py @@ -1,6 +1,5 @@ import asyncio import logging - from time import sleep import pytest diff --git a/tests/test_routes.py b/tests/test_routes.py index cd38161b..3877c200 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -2,7 +2,6 @@ import asyncio import re import pytest - from sanic_routing.exceptions import ( InvalidUsage, ParameterNameConflicts, @@ -164,9 +163,7 @@ def test_matching(path, headers, expected): request = Request(path, headers, None, "GET", None, app) try: - app.router.get( - request.path, request.method, request.headers.get("host") - ) + app.router.get(request.path, request.method, request.headers.get("host")) except NotFound: response = 404 except Exception: @@ -687,9 +684,7 @@ def test_websocket_route_with_subprotocols(app, subprotocols, expected): results = ws.subprotocol assert ws.subprotocol is not None - _, response = SanicTestClient(app).websocket( - "/ws", subprotocols=subprotocols - ) + _, response = SanicTestClient(app).websocket("/ws", subprotocols=subprotocols) assert response.opened is True assert results == expected @@ -1124,12 +1119,12 @@ def test_route_invalid_host(app): return text("pass") assert str(excinfo.value) == ( - "Expected either string or Iterable of " "host strings, not {!r}" - ).format(host) + f"Expected either string or Iterable of host strings, not {host!r}" + ) def test_route_with_regex_group(app): - @app.route("/path/to/") + @app.route(r"/path/to/") async def handler(request, ext): return text(ext) @@ -1160,7 +1155,7 @@ def test_route_with_regex_named_group_invalid(app): def test_route_with_regex_group_ambiguous(app): - @app.route("/path/to/") + @app.route(r"/path/to/") async def handler(request, ext): return text(ext) @@ -1169,7 +1164,7 @@ def test_route_with_regex_group_ambiguous(app): assert e.match( re.escape( - "Could not compile pattern file(?:\.)(txt). Try using a named " + r"Could not compile pattern file(?:\.)(txt). Try using a named " "group instead: '(?Pyour_matching_group)'" ) ) @@ -1288,8 +1283,6 @@ async def test_duplicate_route_error(app): async def handler_bar(request): return text("...") - message = ( - "Duplicate route names detected: test_duplicate_route_error.duped." - ) + message = "Duplicate route names detected: test_duplicate_route_error.duped." with pytest.raises(ServerError, match=message): await app._startup() diff --git a/tests/test_server_events.py b/tests/test_server_events.py index ab0475b6..754ac352 100644 --- a/tests/test_server_events.py +++ b/tests/test_server_events.py @@ -1,16 +1,13 @@ import asyncio import signal - from contextlib import closing from socket import socket import pytest - from sanic_testing.testing import HOST, PORT from sanic.exceptions import BadRequest, SanicException - AVAILABLE_LISTENERS = [ "before_server_start", "after_server_start", @@ -111,9 +108,7 @@ async def test_trigger_before_events_create_server(app): async def init_db(app, loop): app.ctx.db = MySanicDb() - srv = await app.create_server( - debug=True, return_asyncio_server=True, port=PORT - ) + srv = await app.create_server(debug=True, return_asyncio_server=True, port=PORT) await srv.startup() await srv.before_start() @@ -170,9 +165,7 @@ def test_create_server_trigger_events(app): with closing(socket()) as sock: sock.bind(("127.0.0.1", 0)) - serv_coro = app.create_server( - return_asyncio_server=True, sock=sock, debug=True - ) + serv_coro = app.create_server(return_asyncio_server=True, sock=sock, debug=True) serv_task = asyncio.ensure_future(serv_coro, loop=loop) server = loop.run_until_complete(serv_task) loop.run_until_complete(server.startup()) @@ -202,9 +195,7 @@ async def test_missing_startup_raises_exception(app): async def init_db(app, loop): ... - srv = await app.create_server( - debug=True, return_asyncio_server=True, port=PORT - ) + srv = await app.create_server(debug=True, return_asyncio_server=True, port=PORT) with pytest.raises(SanicException): await srv.before_start() diff --git a/tests/test_server_loop.py b/tests/test_server_loop.py index 30077178..ebfe190c 100644 --- a/tests/test_server_loop.py +++ b/tests/test_server_loop.py @@ -1,5 +1,4 @@ import logging - from unittest.mock import Mock, patch import pytest @@ -94,9 +93,7 @@ def test_sets_loop_policy_only_when_not_already_set(monkeypatch): # Existing policy is not uvloop.EventLoopPolicy get_event_loop_policy = Mock(return_value=None) - monkeypatch.setattr( - loop.asyncio, "get_event_loop_policy", get_event_loop_policy - ) + monkeypatch.setattr(loop.asyncio, "get_event_loop_policy", get_event_loop_policy) with patch("asyncio.set_event_loop_policy") as set_event_loop_policy: loop.try_use_uvloop() @@ -107,9 +104,7 @@ def test_sets_loop_policy_only_when_not_already_set(monkeypatch): # Existing policy is uvloop.EventLoopPolicy get_event_loop_policy = Mock(return_value=policy) - monkeypatch.setattr( - loop.asyncio, "get_event_loop_policy", get_event_loop_policy - ) + monkeypatch.setattr(loop.asyncio, "get_event_loop_policy", get_event_loop_policy) with patch("asyncio.set_event_loop_policy") as set_event_loop_policy: loop.try_use_uvloop() diff --git a/tests/test_signal_handlers.py b/tests/test_signal_handlers.py index 728c9c17..ee41248e 100644 --- a/tests/test_signal_handlers.py +++ b/tests/test_signal_handlers.py @@ -1,14 +1,12 @@ import asyncio import os import signal - from queue import Queue from types import SimpleNamespace from typing import Optional from unittest.mock import MagicMock import pytest - from sanic_testing.testing import HOST, PORT from sanic import Sanic diff --git a/tests/test_signals.py b/tests/test_signals.py index 8a564a98..fca40d5c 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -1,11 +1,9 @@ import asyncio - from enum import Enum from inspect import isawaitable from itertools import count import pytest - from sanic_routing.exceptions import NotFound from sanic import Blueprint, Sanic, empty @@ -28,9 +26,7 @@ def test_add_signal_method_handler(app): class TestSanic(Sanic): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.add_signal( - self.after_routing_signal_handler, "http.routing.after" - ) + self.add_signal(self.after_routing_signal_handler, "http.routing.after") def after_routing_signal_handler(self, *args, **kwargs): nonlocal counter @@ -445,9 +441,7 @@ async def test_report_exception(app: Sanic): registered_signal_handlers = [ handler - for handler, *_ in app.signal_router.get( - Event.SERVER_EXCEPTION_REPORT.value - ) + for handler, *_ in app.signal_router.get(Event.SERVER_EXCEPTION_REPORT.value) ] assert catch_any_exception in registered_signal_handlers diff --git a/tests/test_static.py b/tests/test_static.py index 925a81ae..4587b4cc 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -1,7 +1,6 @@ import logging import os import sys - from collections import Counter from pathlib import Path from time import gmtime, strftime @@ -80,9 +79,7 @@ def hard_link(static_file_directory): ["test.file", "decode me.txt", "python.png", "symlink", "hard_link"], ) def test_static_file(app, static_file_directory, file_name): - app.static( - "/testing.file", get_file_path(static_file_directory, file_name) - ) + app.static("/testing.file", get_file_path(static_file_directory, file_name)) request, response = app.test_client.get("/testing.file") assert response.status == 200 @@ -240,9 +237,7 @@ def test_static_content_range_correct(app, file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - 12:20 - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[12:20] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content @@ -260,9 +255,7 @@ def test_static_content_range_front(app, file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - 12: - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[12:] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content @@ -280,9 +273,7 @@ def test_static_content_range_back(app, file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - -12: - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[-12:] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content @@ -306,9 +297,7 @@ def test_static_content_range_empty( assert int(response.headers["Content-Length"]) == len( get_file_content(static_file_directory, file_name) ) - assert response.body == bytes( - get_file_content(static_file_directory, file_name) - ) + assert response.body == bytes(get_file_content(static_file_directory, file_name)) @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) @@ -324,15 +313,13 @@ def test_static_content_range_error(app, file_name, static_file_directory): assert response.status == 416 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - assert response.headers["Content-Range"] == "bytes */%s" % ( + assert response.headers["Content-Range"] == "bytes */{}".format( len(get_file_content(static_file_directory, file_name)), ) @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) -def test_static_content_range_invalid_unit( - app, file_name, static_file_directory -): +def test_static_content_range_invalid_unit(app, file_name, static_file_directory): app.static( "/testing.file", get_file_path(static_file_directory, file_name), @@ -348,9 +335,7 @@ def test_static_content_range_invalid_unit( @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) -def test_static_content_range_invalid_start( - app, file_name, static_file_directory -): +def test_static_content_range_invalid_start(app, file_name, static_file_directory): app.static( "/testing.file", get_file_path(static_file_directory, file_name), @@ -366,9 +351,7 @@ def test_static_content_range_invalid_start( @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) -def test_static_content_range_invalid_end( - app, file_name, static_file_directory -): +def test_static_content_range_invalid_end(app, file_name, static_file_directory): app.static( "/testing.file", get_file_path(static_file_directory, file_name), @@ -384,9 +367,7 @@ def test_static_content_range_invalid_end( @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) -def test_static_content_range_invalid_parameters( - app, file_name, static_file_directory -): +def test_static_content_range_invalid_parameters(app, file_name, static_file_directory): app.static( "/testing.file", get_file_path(static_file_directory, file_name), @@ -400,9 +381,7 @@ def test_static_content_range_invalid_parameters( assert "Invalid for Content Range parameters" in response.text -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) def test_static_file_specified_host(app, static_file_directory, file_name): app.static( "/testing.file", @@ -442,14 +421,10 @@ def test_static_stream_large_file( assert response.body == get_file_content(static_file_directory, file_name) -@pytest.mark.parametrize( - "file_name", ["test.file", "decode me.txt", "python.png"] -) +@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt", "python.png"]) def test_use_modified_since(app, static_file_directory, file_name): file_stat = os.stat(get_file_path(static_file_directory, file_name)) - modified_since = strftime( - "%a, %d %b %Y %H:%M:%S GMT", gmtime(file_stat.st_mtime) - ) + modified_since = strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(file_stat.st_mtime)) app.static( "/testing.file", @@ -548,32 +523,22 @@ async def test_multiple_statics_error(app, static_file_directory): app.static("/file", get_file_path(static_file_directory, "test.file")) app.static("/png", get_file_path(static_file_directory, "python.png")) - message = ( - r"Duplicate route names detected: test_multiple_statics_error\.static" - ) + message = r"Duplicate route names detected: test_multiple_statics_error\.static" with pytest.raises(ServerError, match=message): await app._startup() def test_multiple_statics(app, static_file_directory): - app.static( - "/file", get_file_path(static_file_directory, "test.file"), name="file" - ) - app.static( - "/png", get_file_path(static_file_directory, "python.png"), name="png" - ) + app.static("/file", get_file_path(static_file_directory, "test.file"), name="file") + app.static("/png", get_file_path(static_file_directory, "python.png"), name="png") _, response = app.test_client.get("/file") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "test.file" - ) + assert response.body == get_file_content(static_file_directory, "test.file") _, response = app.test_client.get("/png") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "python.png" - ) + assert response.body == get_file_content(static_file_directory, "python.png") @pytest.mark.asyncio @@ -581,25 +546,23 @@ async def test_resource_type_default_error(app, static_file_directory): app.static("/static", static_file_directory) app.static("/file", get_file_path(static_file_directory, "test.file")) - message = r"Duplicate route names detected: test_resource_type_default_error\.static" + message = ( + r"Duplicate route names detected: test_resource_type_default_error\.static" + ) with pytest.raises(ServerError, match=message): await app._startup() def test_resource_type_default(app, static_file_directory): app.static("/static", static_file_directory, name="static") - app.static( - "/file", get_file_path(static_file_directory, "test.file"), name="file" - ) + app.static("/file", get_file_path(static_file_directory, "test.file"), name="file") _, response = app.test_client.get("/static") assert response.status == 404 _, response = app.test_client.get("/file") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "test.file" - ) + assert response.body == get_file_content(static_file_directory, "test.file") def test_resource_type_file(app, static_file_directory): @@ -611,9 +574,7 @@ def test_resource_type_file(app, static_file_directory): _, response = app.test_client.get("/file") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "test.file" - ) + assert response.body == get_file_content(static_file_directory, "test.file") with pytest.raises(TypeError): app.static("/static", static_file_directory, resource_type="file") @@ -624,9 +585,7 @@ def test_resource_type_dir(app, static_file_directory): _, response = app.test_client.get("/static/test.file") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "test.file" - ) + assert response.body == get_file_content(static_file_directory, "test.file") with pytest.raises(TypeError): app.static( @@ -667,12 +626,8 @@ def test_breakout(app: Sanic, static_file_directory: str): assert response.status == 404 -@pytest.mark.skipif( - sys.platform != "win32", reason="Block backslash on Windows only" -) -def test_double_backslash_prohibited_on_win32( - app: Sanic, static_file_directory: str -): +@pytest.mark.skipif(sys.platform != "win32", reason="Block backslash on Windows only") +def test_double_backslash_prohibited_on_win32(app: Sanic, static_file_directory: str): app.static("/foo", static_file_directory) _, response = app.test_client.get("/foo/static/..\\static/test.file") diff --git a/tests/test_static_directory.py b/tests/test_static_directory.py index 5aab3021..08ccb492 100644 --- a/tests/test_static_directory.py +++ b/tests/test_static_directory.py @@ -1,5 +1,4 @@ import os - from pathlib import Path import pytest @@ -32,9 +31,7 @@ def test_static_index_single(app: Sanic, static_file_directory: str): _, response = app.test_client.get("/static/") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "test.html" - ) + assert response.body == get_file_content(static_file_directory, "test.html") assert response.headers["Content-Type"] == "text/html" @@ -54,15 +51,11 @@ def test_static_index_multiple(app: Sanic, static_file_directory: str): _, response = app.test_client.get("/static/") assert response.status == 200 - assert response.body == get_file_content( - static_file_directory, "test.html" - ) + assert response.body == get_file_content(static_file_directory, "test.html") assert response.headers["Content-Type"] == "text/html" -def test_static_directory_view_and_index( - app: Sanic, static_file_directory: str -): +def test_static_directory_view_and_index(app: Sanic, static_file_directory: str): app.static( "/static", static_file_directory, diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 1d528319..98fb6a77 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -1,5 +1,4 @@ import asyncio - from asyncio.tasks import Task from unittest.mock import Mock, call @@ -9,7 +8,6 @@ from sanic.app import Sanic from sanic.application.state import ApplicationServerInfo, ServerStage from sanic.response import empty - try: from unittest.mock import AsyncMock except ImportError: diff --git a/tests/test_test_client_port.py b/tests/test_test_client_port.py index 334edde3..ceef3457 100644 --- a/tests/test_test_client_port.py +++ b/tests/test_test_client_port.py @@ -2,7 +2,6 @@ from sanic_testing.testing import PORT, SanicTestClient from sanic.response import json, text - # ------------------------------------------------------------ # # UTF-8 # ------------------------------------------------------------ # diff --git a/tests/test_timeout_logic.py b/tests/test_timeout_logic.py index 497deda9..e782ea8f 100644 --- a/tests/test_timeout_logic.py +++ b/tests/test_timeout_logic.py @@ -1,5 +1,4 @@ import asyncio - from unittest.mock import Mock import pytest diff --git a/tests/test_tls.py b/tests/test_tls.py index 6d2cb981..4b04c986 100644 --- a/tests/test_tls.py +++ b/tests/test_tls.py @@ -3,7 +3,6 @@ import os import ssl import subprocess import sys - from contextlib import contextmanager from multiprocessing import Event from pathlib import Path @@ -11,11 +10,9 @@ from unittest.mock import Mock, patch from urllib.parse import urlparse import pytest - from sanic_testing.testing import HOST, PORT, SanicTestClient import sanic.http.tls.creators - from sanic import Sanic from sanic.application.constants import Mode from sanic.compat import use_context @@ -31,7 +28,6 @@ from sanic.http.tls.creators import ( from sanic.response import text from sanic.worker.loader import CertLoader - current_dir = os.path.dirname(os.path.realpath(__file__)) localhost_dir = os.path.join(current_dir, "certs/localhost") password_dir = os.path.join(current_dir, "certs/password") @@ -115,9 +111,7 @@ def replace_server_name(hostname): server_hostname=None, session=None, ): - return orig_wrap_bio( - self, incoming, outgoing, server_side, hostname, session - ) + return orig_wrap_bio(self, incoming, outgoing, server_side, hostname, session) orig_wrap_bio, ssl.SSLContext.wrap_bio = ( ssl.SSLContext.wrap_bio, @@ -466,9 +460,7 @@ def test_logger_vhosts(caplog): single_process=True, ) - logmsg = [ - m for s, l, m in caplog.record_tuples if m.startswith("Certificate") - ][0] + logmsg = [m for s, l, m in caplog.record_tuples if m.startswith("Certificate")][0] assert logmsg == ( "Certificate vhosts: localhost, 127.0.0.1, 0:0:0:0:0:0:0:1, " @@ -499,9 +491,7 @@ def test_mk_cert_creator_is_not_supported(app): cert_creator = MkcertCreator(app, _default, _default) with patch("subprocess.run") as run: run.side_effect = Exception("") - with pytest.raises( - SanicException, match="Sanic is attempting to use mkcert" - ): + with pytest.raises(SanicException, match="Sanic is attempting to use mkcert"): cert_creator.check_supported() @@ -539,9 +529,7 @@ def test_trustme_creator_is_supported(app, monkeypatch): def test_trustme_creator_is_not_supported(app, monkeypatch): monkeypatch.setattr(sanic.http.tls.creators, "TRUSTME_INSTALLED", False) cert_creator = TrustmeCreator(app, _default, _default) - with pytest.raises( - SanicException, match="Sanic is attempting to use trustme" - ): + with pytest.raises(SanicException, match="Sanic is attempting to use trustme"): cert_creator.check_supported() @@ -627,12 +615,8 @@ def test_get_ssl_context_only_mkcert( ): app.state.mode = Mode.DEBUG app.config.LOCAL_CERT_CREATOR = requirement - monkeypatch.setattr( - sanic.http.tls.creators, "MkcertCreator", MockMkcertCreator - ) - monkeypatch.setattr( - sanic.http.tls.creators, "TrustmeCreator", MockTrustmeCreator - ) + monkeypatch.setattr(sanic.http.tls.creators, "MkcertCreator", MockMkcertCreator) + monkeypatch.setattr(sanic.http.tls.creators, "TrustmeCreator", MockTrustmeCreator) MockMkcertCreator.SUPPORTED = mk_supported MockTrustmeCreator.SUPPORTED = trustme_supported diff --git a/tests/test_touchup.py b/tests/test_touchup.py index 85142685..2cd0735f 100644 --- a/tests/test_touchup.py +++ b/tests/test_touchup.py @@ -1,7 +1,6 @@ import logging import pytest - from sanic_routing.exceptions import NotFound from sanic.signals import RESERVED_NAMESPACES diff --git a/tests/test_unix_socket.py b/tests/test_unix_socket.py index bd76ca74..c56edb5a 100644 --- a/tests/test_unix_socket.py +++ b/tests/test_unix_socket.py @@ -2,14 +2,12 @@ import logging import os import sys - from asyncio import AbstractEventLoop, sleep from string import ascii_lowercase import httpcore import httpx import pytest - from pytest import LogCaptureFixture from sanic import Sanic @@ -17,7 +15,6 @@ from sanic.compat import use_context from sanic.request import Request from sanic.response import text - # import platform # import subprocess # import sys @@ -26,9 +23,7 @@ from sanic.response import text pytestmark = pytest.mark.skipif(os.name != "posix", reason="UNIX only") SOCKPATH = "/tmp/sanictest.sock" SOCKPATH2 = "/tmp/sanictest2.sock" -httpx_version = tuple( - map(int, httpx.__version__.strip(ascii_lowercase).split(".")) -) +httpx_version = tuple(map(int, httpx.__version__.strip(ascii_lowercase).split("."))) @pytest.fixture(autouse=True) diff --git a/tests/test_url_building.py b/tests/test_url_building.py index 075ee6cd..e3a9c83a 100644 --- a/tests/test_url_building.py +++ b/tests/test_url_building.py @@ -1,9 +1,7 @@ import string - from urllib.parse import parse_qsl, urlsplit import pytest - from sanic_testing.testing import HOST as test_host from sanic_testing.testing import PORT as test_port @@ -13,34 +11,31 @@ from sanic.exceptions import URLBuildError from sanic.response import text from sanic.views import HTTPMethodView - -URL_FOR_ARGS1 = dict(arg1=["v1", "v2"]) +URL_FOR_ARGS1 = {"arg1": ["v1", "v2"]} URL_FOR_VALUE1 = "/myurl?arg1=v1&arg1=v2" -URL_FOR_ARGS2 = dict(arg1=["v1", "v2"], _anchor="anchor") +URL_FOR_ARGS2 = {"arg1": ["v1", "v2"], "_anchor": "anchor"} URL_FOR_VALUE2 = "/myurl?arg1=v1&arg1=v2#anchor" -URL_FOR_ARGS3 = dict( - arg1="v1", - _anchor="anchor", - _scheme="http", - _server=f"{test_host}:{test_port}", - _external=True, -) +URL_FOR_ARGS3 = { + "arg1": "v1", + "_anchor": "anchor", + "_scheme": "http", + "_server": f"{test_host}:{test_port}", + "_external": True, +} URL_FOR_VALUE3 = f"http://{test_host}:{test_port}/myurl?arg1=v1#anchor" -URL_FOR_ARGS4 = dict( - arg1="v1", - _anchor="anchor", - _external=True, - _server=f"http://{test_host}:{test_port}", -) +URL_FOR_ARGS4 = { + "arg1": "v1", + "_anchor": "anchor", + "_external": True, + "_server": f"http://{test_host}:{test_port}", +} URL_FOR_VALUE4 = f"http://{test_host}:{test_port}/myurl?arg1=v1#anchor" def _generate_handlers_from_names(app, l): for name in l: # this is the easiest way to generate functions with dynamic names - exec( - f'@app.route(name)\ndef {name}(request):\n\treturn text("{name}")' - ) + exec(f'@app.route(name)\ndef {name}(request):\n\treturn text("{name}")') @pytest.fixture diff --git a/tests/test_url_for.py b/tests/test_url_for.py index 6ec6a93f..3c44fb5c 100644 --- a/tests/test_url_for.py +++ b/tests/test_url_for.py @@ -1,7 +1,6 @@ import asyncio import pytest - from sanic_testing.testing import SanicTestClient from sanic.blueprints import Blueprint @@ -16,10 +15,7 @@ def test_routes_with_host(app): assert app.url_for("hostindex") == "/" assert app.url_for("hostpath") == "/path" assert app.url_for("hostindex", _external=True) == "http://example.com/" - assert ( - app.url_for("hostpath", _external=True) - == "http://path.example.com/path" - ) + assert app.url_for("hostpath", _external=True) == "http://path.example.com/path" def test_routes_with_multiple_hosts(app): @@ -28,9 +24,7 @@ def test_routes_with_multiple_hosts(app): pass assert app.url_for("hostindex") == "/" - assert ( - app.url_for("hostindex", _host="example.com") == "http://example.com/" - ) + assert app.url_for("hostindex", _host="example.com") == "http://example.com/" with pytest.raises(ValueError) as e: assert app.url_for("hostindex", _external=True) diff --git a/tests/test_url_for_static.py b/tests/test_url_for_static.py index 6c12c023..4adcfb77 100644 --- a/tests/test_url_for_static.py +++ b/tests/test_url_for_static.py @@ -36,9 +36,7 @@ def get_file_content(static_file_directory, file_name): ) def test_static_file(static_file_directory, file_name): app = Sanic("qq") - app.static( - "/testing.file", get_file_path(static_file_directory, file_name) - ) + app.static("/testing.file", get_file_path(static_file_directory, file_name)) app.static( "/testing2.file", get_file_path(static_file_directory, file_name), @@ -155,16 +153,10 @@ def test_static_directory(file_name, base_uri, static_file_directory): app.router.reset() app.blueprint(bp) - uri = app.url_for( - "static", name="test_bp_static.static", filename=file_name - ) - uri2 = app.url_for( - "static", name="test_bp_static.static", filename="/" + file_name - ) + uri = app.url_for("static", name="test_bp_static.static", filename=file_name) + uri2 = app.url_for("static", name="test_bp_static.static", filename="/" + file_name) - uri4 = app.url_for( - "static", name="test_bp_static.uploads", filename=file_name - ) + uri4 = app.url_for("static", name="test_bp_static.uploads", filename=file_name) uri5 = app.url_for( "static", name="test_bp_static.uploads", filename="/" + file_name ) @@ -213,9 +205,7 @@ def test_static_head_request(file_name, static_file_directory): # blueprint uri = app.url_for("static", name="test_bp_static.static") assert uri == "/bp/testing.file" - assert uri == app.url_for( - "static", name="test_bp_static.static", filename="any" - ) + assert uri == app.url_for("static", name="test_bp_static.static", filename="any") request, response = app.test_client.head(uri) assert response.status == 200 @@ -253,27 +243,21 @@ def test_static_content_range_correct(file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - 12:20 - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[12:20] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content # blueprint uri = app.url_for("static", name="test_bp_static.static") assert uri == "/bp/testing.file" - assert uri == app.url_for( - "static", name="test_bp_static.static", filename="any" - ) + assert uri == app.url_for("static", name="test_bp_static.static", filename="any") assert uri == app.url_for("test_bp_static.static") request, response = app.test_client.get(uri, headers=headers) assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - 12:20 - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[12:20] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content @@ -305,18 +289,14 @@ def test_static_content_range_front(file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - 12: - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[12:] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content # blueprint uri = app.url_for("static", name="test_bp_static.static") assert uri == "/bp/testing.file" - assert uri == app.url_for( - "static", name="test_bp_static.static", filename="any" - ) + assert uri == app.url_for("static", name="test_bp_static.static", filename="any") assert uri == app.url_for("test_bp_static.static") assert uri == app.url_for("test_bp_static.static", filename="any") @@ -324,9 +304,7 @@ def test_static_content_range_front(file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - 12: - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[12:] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content @@ -358,18 +336,14 @@ def test_static_content_range_back(file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - -12: - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[-12:] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content # blueprint uri = app.url_for("static", name="test_bp_static.static") assert uri == "/bp/testing.file" - assert uri == app.url_for( - "static", name="test_bp_static.static", filename="any" - ) + assert uri == app.url_for("static", name="test_bp_static.static", filename="any") assert uri == app.url_for("test_bp_static.static") assert uri == app.url_for("test_bp_static.static", filename="any") @@ -377,9 +351,7 @@ def test_static_content_range_back(file_name, static_file_directory): assert response.status == 206 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - static_content = bytes(get_file_content(static_file_directory, file_name))[ - -12: - ] + static_content = bytes(get_file_content(static_file_directory, file_name))[-12:] assert int(response.headers["Content-Length"]) == len(static_content) assert response.body == static_content @@ -413,16 +385,12 @@ def test_static_content_range_empty(file_name, static_file_directory): assert int(response.headers["Content-Length"]) == len( get_file_content(static_file_directory, file_name) ) - assert response.body == bytes( - get_file_content(static_file_directory, file_name) - ) + assert response.body == bytes(get_file_content(static_file_directory, file_name)) # blueprint uri = app.url_for("static", name="test_bp_static.static") assert uri == "/bp/testing.file" - assert uri == app.url_for( - "static", name="test_bp_static.static", filename="any" - ) + assert uri == app.url_for("static", name="test_bp_static.static", filename="any") assert uri == app.url_for("test_bp_static.static") assert uri == app.url_for("test_bp_static.static", filename="any") @@ -433,9 +401,7 @@ def test_static_content_range_empty(file_name, static_file_directory): assert int(response.headers["Content-Length"]) == len( get_file_content(static_file_directory, file_name) ) - assert response.body == bytes( - get_file_content(static_file_directory, file_name) - ) + assert response.body == bytes(get_file_content(static_file_directory, file_name)) @pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"]) @@ -465,16 +431,14 @@ def test_static_content_range_error(app, file_name, static_file_directory): assert response.status == 416 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - assert response.headers["Content-Range"] == "bytes */%s" % ( + assert response.headers["Content-Range"] == "bytes */{}".format( len(get_file_content(static_file_directory, file_name)), ) # blueprint uri = app.url_for("static", name="test_bp_static.static") assert uri == "/bp/testing.file" - assert uri == app.url_for( - "static", name="test_bp_static.static", filename="any" - ) + assert uri == app.url_for("static", name="test_bp_static.static", filename="any") assert uri == app.url_for("test_bp_static.static") assert uri == app.url_for("test_bp_static.static", filename="any") @@ -482,6 +446,6 @@ def test_static_content_range_error(app, file_name, static_file_directory): assert response.status == 416 assert "Content-Length" in response.headers assert "Content-Range" in response.headers - assert response.headers["Content-Range"] == "bytes */%s" % ( + assert response.headers["Content-Range"] == "bytes */{}".format( len(get_file_content(static_file_directory, file_name)), ) diff --git a/tests/test_utf8.py b/tests/test_utf8.py index a2bf893e..a025baaa 100644 --- a/tests/test_utf8.py +++ b/tests/test_utf8.py @@ -2,7 +2,6 @@ from json import dumps as json_dumps from sanic.response import text - # ------------------------------------------------------------ # # UTF-8 # ------------------------------------------------------------ # diff --git a/tests/test_vhosts.py b/tests/test_vhosts.py index c62c8b80..e1cef5a2 100644 --- a/tests/test_vhosts.py +++ b/tests/test_vhosts.py @@ -1,5 +1,4 @@ import pytest - from sanic_routing.exceptions import RouteExists from sanic import Sanic diff --git a/tests/test_views.py b/tests/test_views.py index ab35679e..3c172786 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -195,9 +195,7 @@ def test_with_custom_class_methods(app): def get(self, request): self._iternal_method() - return text( - f"I am get method and global var " f"is {self.global_var}" - ) + return text(f"I am get method and global var is {self.global_var}") app.add_route(DummyView.as_view(), "/") request, response = app.test_client.get("/") diff --git a/tests/test_websockets.py b/tests/test_websockets.py index dd8413b9..0d23d47e 100644 --- a/tests/test_websockets.py +++ b/tests/test_websockets.py @@ -1,16 +1,13 @@ import re - from asyncio import Event, Queue, TimeoutError from unittest.mock import Mock, call import pytest - from websockets.frames import CTRL_OPCODES, DATA_OPCODES, Frame from sanic.exceptions import ServerError from sanic.server.websockets.frame import WebsocketFrameAssembler - try: from unittest.mock import AsyncMock except ImportError: diff --git a/tests/test_ws_handlers.py b/tests/test_ws_handlers.py index 5205090d..87ac06e9 100644 --- a/tests/test_ws_handlers.py +++ b/tests/test_ws_handlers.py @@ -1,15 +1,11 @@ from typing import Any, Callable, Coroutine import pytest - from websockets.client import WebSocketClientProtocol from sanic import Request, Sanic, Websocket - -MimicClientType = Callable[ - [WebSocketClientProtocol], Coroutine[None, None, Any] -] +MimicClientType = Callable[[WebSocketClientProtocol], Coroutine[None, None, Any]] @pytest.fixture @@ -33,9 +29,7 @@ def test_ws_handler( msg = await ws.recv() await ws.send(msg) - _, ws_proxy = app.test_client.websocket( - "/ws", mimic=simple_ws_mimic_client - ) + _, ws_proxy = app.test_client.websocket("/ws", mimic=simple_ws_mimic_client) assert ws_proxy.client_sent == ["test 1", "test 2", ""] assert ws_proxy.client_received == ["test 1", "test 2"] @@ -49,8 +43,6 @@ def test_ws_handler_async_for( async for msg in ws: await ws.send(msg) - _, ws_proxy = app.test_client.websocket( - "/ws", mimic=simple_ws_mimic_client - ) + _, ws_proxy = app.test_client.websocket("/ws", mimic=simple_ws_mimic_client) assert ws_proxy.client_sent == ["test 1", "test 2", ""] assert ws_proxy.client_received == ["test 1", "test 2"] diff --git a/tests/typing/samples/app_default.py b/tests/typing/samples/app_default.py index 34524c50..aac412e6 100644 --- a/tests/typing/samples/app_default.py +++ b/tests/typing/samples/app_default.py @@ -1,5 +1,4 @@ from sanic import Sanic - app = Sanic("test") reveal_type(app) diff --git a/tests/typing/samples/request_fully_custom.py b/tests/typing/samples/request_fully_custom.py index e2ec4b31..e094d68c 100644 --- a/tests/typing/samples/request_fully_custom.py +++ b/tests/typing/samples/request_fully_custom.py @@ -22,9 +22,7 @@ class CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]): return ctx -app = Sanic( - "test", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest -) +app = Sanic("test", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest) @app.get("/") diff --git a/tests/typing/test_typing.py b/tests/typing/test_typing.py index 5ebba266..424b5163 100644 --- a/tests/typing/test_typing.py +++ b/tests/typing/test_typing.py @@ -1,14 +1,11 @@ # flake8: noqa: E501 import subprocess -import sys - from pathlib import Path from typing import List, Tuple import pytest - CURRENT_DIR = Path(__file__).parent @@ -21,9 +18,8 @@ def run_check(path_location: str) -> str: process = subprocess.run( command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, + capture_output=True, + text=True, ) output = process.stdout + process.stderr return output @@ -96,9 +92,7 @@ def run_check(path_location: str) -> str: ), ), ) -def test_check_app_default( - path_location: str, expected: List[Tuple[str, int]] -) -> None: +def test_check_app_default(path_location: str, expected: List[Tuple[str, int]]) -> None: output = run_check(f"samples/{path_location}") for text, number in expected: diff --git a/tests/worker/test_inspector.py b/tests/worker/test_inspector.py index b1773203..cf06c2cb 100644 --- a/tests/worker/test_inspector.py +++ b/tests/worker/test_inspector.py @@ -8,7 +8,6 @@ from unittest.mock import Mock, patch from urllib.error import URLError import pytest - from sanic_testing import TestManager from sanic.cli.inspector_client import InspectorClient @@ -16,7 +15,6 @@ from sanic.helpers import Default from sanic.log import Colors from sanic.worker.inspector import Inspector - DATA = { "info": { "packages": ["foo"], @@ -129,9 +127,9 @@ def test_state_to_json(): def test_run_inspector_authentication(): - inspector = Inspector( - Mock(), {}, {}, "", 0, "super-secret", Default(), Default() - )(False) + inspector = Inspector(Mock(), {}, {}, "", 0, "super-secret", Default(), Default())( + False + ) manager = TestManager(inspector.app) _, response = manager.test_client.get("/") assert response.status == 401 diff --git a/tests/worker/test_loader.py b/tests/worker/test_loader.py index c70a2346..838e9cb5 100644 --- a/tests/worker/test_loader.py +++ b/tests/worker/test_loader.py @@ -1,5 +1,4 @@ import sys - from os import getcwd from pathlib import Path from types import SimpleNamespace @@ -10,7 +9,6 @@ import pytest from sanic.app import Sanic from sanic.worker.loader import AppLoader, CertLoader - STATIC = Path.cwd() / "tests" / "static" diff --git a/tests/worker/test_manager.py b/tests/worker/test_manager.py index ae5f4592..4753b07d 100644 --- a/tests/worker/test_manager.py +++ b/tests/worker/test_manager.py @@ -9,7 +9,6 @@ from sanic.exceptions import ServerKilled from sanic.worker.constants import RestartOrder from sanic.worker.manager import WorkerManager - if not OS_IS_WINDOWS: from signal import SIGKILL else: @@ -63,9 +62,7 @@ def test_kill(os_mock: Mock): @patch("sanic.worker.process.os") @patch("sanic.worker.manager.os") -def test_shutdown_signal_send_kill( - manager_os_mock: Mock, process_os_mock: Mock -): +def test_shutdown_signal_send_kill(manager_os_mock: Mock, process_os_mock: Mock): process = Mock() process.pid = 1234 context = Mock() @@ -125,9 +122,7 @@ def test_monitor_all(zero_downtime): p2 = Mock() sub = Mock() incoming = ( - "__ALL_PROCESSES__::STARTUP_FIRST" - if zero_downtime - else "__ALL_PROCESSES__:" + "__ALL_PROCESSES__::STARTUP_FIRST" if zero_downtime else "__ALL_PROCESSES__:" ) sub.recv.side_effect = [incoming, ""] context = Mock() @@ -138,9 +133,7 @@ def test_monitor_all(zero_downtime): manager.monitor() restart_order = ( - RestartOrder.STARTUP_FIRST - if zero_downtime - else RestartOrder.SHUTDOWN_FIRST + RestartOrder.STARTUP_FIRST if zero_downtime else RestartOrder.SHUTDOWN_FIRST ) manager.restart.assert_called_once_with( process_names=None, @@ -168,9 +161,7 @@ def test_monitor_all_with_files(zero_downtime): manager.monitor() restart_order = ( - RestartOrder.STARTUP_FIRST - if zero_downtime - else RestartOrder.SHUTDOWN_FIRST + RestartOrder.STARTUP_FIRST if zero_downtime else RestartOrder.SHUTDOWN_FIRST ) manager.restart.assert_called_once_with( process_names=None, @@ -186,9 +177,7 @@ def test_monitor_one_process(zero_downtime): p2 = Mock() sub = Mock() incoming = ( - f"{p1.name}:foo,bar:STARTUP_FIRST" - if zero_downtime - else f"{p1.name}:foo,bar" + f"{p1.name}:foo,bar:STARTUP_FIRST" if zero_downtime else f"{p1.name}:foo,bar" ) sub.recv.side_effect = [incoming, ""] context = Mock() @@ -199,9 +188,7 @@ def test_monitor_one_process(zero_downtime): manager.monitor() restart_order = ( - RestartOrder.STARTUP_FIRST - if zero_downtime - else RestartOrder.SHUTDOWN_FIRST + RestartOrder.STARTUP_FIRST if zero_downtime else RestartOrder.SHUTDOWN_FIRST ) manager.restart.assert_called_once_with( process_names=[p1.name], diff --git a/tests/worker/test_multiplexer.py b/tests/worker/test_multiplexer.py index 8195b094..83571469 100644 --- a/tests/worker/test_multiplexer.py +++ b/tests/worker/test_multiplexer.py @@ -1,5 +1,4 @@ import sys - from multiprocessing import Event from os import environ, getpid from typing import Any, Dict, Type, Union diff --git a/tests/worker/test_reloader.py b/tests/worker/test_reloader.py index 6530c770..b7a27878 100644 --- a/tests/worker/test_reloader.py +++ b/tests/worker/test_reloader.py @@ -1,7 +1,6 @@ import re import signal import threading - from asyncio import Event from logging import DEBUG from pathlib import Path @@ -155,9 +154,7 @@ def test_reload_delayed(monkeypatch): monkeypatch.setattr(threading.Thread, "start", orig) -def test_reloader_triggers_start_stop_listeners( - app: Sanic, app_loader: AppLoader -): +def test_reloader_triggers_start_stop_listeners(app: Sanic, app_loader: AppLoader): results = [] @app.reload_process_start diff --git a/tests/worker/test_shared_ctx.py b/tests/worker/test_shared_ctx.py index 9a41d496..9b26e9c0 100644 --- a/tests/worker/test_shared_ctx.py +++ b/tests/worker/test_shared_ctx.py @@ -7,7 +7,6 @@ # 38-> import logging - from ctypes import c_int32 from multiprocessing import Pipe, Queue, Value from os import environ diff --git a/tests/worker/test_state.py b/tests/worker/test_state.py index 929b4d26..d934e8c3 100644 --- a/tests/worker/test_state.py +++ b/tests/worker/test_state.py @@ -23,12 +23,12 @@ def test_del_state(): def test_iter_state(): - result = [item for item in gen_state(one=1, two=2)] + result = list(gen_state(one=1, two=2)) assert result == ["one", "two"] def test_state_len(): - result = [item for item in gen_state(one=1, two=2)] + result = list(gen_state(one=1, two=2)) assert len(result) == 2 @@ -43,21 +43,15 @@ def test_state_eq(): def test_state_keys(): - assert list(gen_state(one=1, two=2).keys()) == list( - {"one": 1, "two": 2}.keys() - ) + assert list(gen_state(one=1, two=2).keys()) == list({"one": 1, "two": 2}.keys()) def test_state_values(): - assert list(gen_state(one=1, two=2).values()) == list( - {"one": 1, "two": 2}.values() - ) + assert list(gen_state(one=1, two=2).values()) == list({"one": 1, "two": 2}.values()) def test_state_items(): - assert list(gen_state(one=1, two=2).items()) == list( - {"one": 1, "two": 2}.items() - ) + assert list(gen_state(one=1, two=2).items()) == list({"one": 1, "two": 2}.items()) def test_state_update(): diff --git a/tests/worker/test_worker_serve.py b/tests/worker/test_worker_serve.py index 54ff9d65..3d8dd8fb 100644 --- a/tests/worker/test_worker_serve.py +++ b/tests/worker/test_worker_serve.py @@ -1,5 +1,4 @@ import logging - from os import environ from unittest.mock import Mock, patch @@ -67,9 +66,7 @@ def test_has_multiplexer(app: Sanic): Sanic.register_app(app) with patch("sanic.worker.serve._serve_http_1"): - worker_serve( - **args(app, monitor_publisher=Mock(), worker_state=Mock()) - ) + worker_serve(**args(app, monitor_publisher=Mock(), worker_state=Mock())) assert isinstance(app.multiplexer, WorkerMultiplexer) del environ["SANIC_WORKER_NAME"] @@ -103,9 +100,7 @@ def test_serve_app_factory(wm: Mock, mock_app): @patch("sanic.mixins.startup.WorkerManager") @pytest.mark.parametrize("config", (True, False)) -def test_serve_with_inspector( - WorkerManager: Mock, mock_app: Mock, config: bool -): +def test_serve_with_inspector(WorkerManager: Mock, mock_app: Mock, config: bool): Inspector = Mock() mock_app.config.INSPECTOR = config mock_app.inspector_class = Inspector diff --git a/tox.ini b/tox.ini index fabf3bb9..4f5a850d 100644 --- a/tox.ini +++ b/tox.ini @@ -20,9 +20,8 @@ commands = [testenv:lint] commands = - flake8 sanic - black --check --verbose sanic/ - isort --check-only sanic + ruff sanic + ruff format sanic --check slotscheck --verbose -m sanic [testenv:type-checking]