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