Format with default line-length=88 (was 79 with Sanic).
This commit is contained in:
parent
e4daf1ab21
commit
1afea39cb2
@ -35,9 +35,7 @@ def proxy(request, path):
|
|||||||
|
|
||||||
@https.main_process_start
|
@https.main_process_start
|
||||||
async def start(app, _):
|
async def start(app, _):
|
||||||
http_server = await http.create_server(
|
http_server = await http.create_server(port=HTTP_PORT, return_asyncio_server=True)
|
||||||
port=HTTP_PORT, return_asyncio_server=True
|
|
||||||
)
|
|
||||||
app.add_task(runner(http, http_server))
|
app.add_task(runner(http, http_server))
|
||||||
app.ctx.http_server = http_server
|
app.ctx.http_server = http_server
|
||||||
app.ctx.http = http
|
app.ctx.http = http
|
||||||
|
@ -33,9 +33,7 @@ logdna_options = {
|
|||||||
"mac": get_mac_address(),
|
"mac": get_mac_address(),
|
||||||
}
|
}
|
||||||
|
|
||||||
logdna_handler = LogDNAHandler(
|
logdna_handler = LogDNAHandler(getenv("LOGDNA_API_KEY"), options=logdna_options)
|
||||||
getenv("LOGDNA_API_KEY"), options=logdna_options
|
|
||||||
)
|
|
||||||
|
|
||||||
logdna = logging.getLogger(__name__)
|
logdna = logging.getLogger(__name__)
|
||||||
logdna.setLevel(logging.INFO)
|
logdna.setLevel(logging.INFO)
|
||||||
|
@ -42,9 +42,7 @@ async def handler_file(request):
|
|||||||
|
|
||||||
@app.route("/file_stream")
|
@app.route("/file_stream")
|
||||||
async def handler_file_stream(request):
|
async def handler_file_stream(request):
|
||||||
return await response.file_stream(
|
return await response.file_stream(Path("../") / "setup.py", chunk_size=1024)
|
||||||
Path("../") / "setup.py", chunk_size=1024
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/stream", stream=True)
|
@app.post("/stream", stream=True)
|
||||||
|
@ -36,9 +36,7 @@ async def test(request):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||||
serv_coro = app.create_server(
|
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||||
host="0.0.0.0", port=8000, return_asyncio_server=True
|
|
||||||
)
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||||
signal(SIGINT, lambda s, f: loop.stop())
|
signal(SIGINT, lambda s, f: loop.stop())
|
||||||
|
@ -42,9 +42,7 @@ async def test_file(request):
|
|||||||
|
|
||||||
@app.route("/file_stream")
|
@app.route("/file_stream")
|
||||||
async def test_file_stream(request):
|
async def test_file_stream(request):
|
||||||
return await response.file_stream(
|
return await response.file_stream(os.path.abspath("setup.py"), chunk_size=1024)
|
||||||
os.path.abspath("setup.py"), chunk_size=1024
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------- #
|
# ----------------------------------------------- #
|
||||||
|
@ -11,9 +11,7 @@ app = Sanic("Example")
|
|||||||
bp = Blueprint("bp", host="bp.example.com")
|
bp = Blueprint("bp", host="bp.example.com")
|
||||||
|
|
||||||
|
|
||||||
@app.route(
|
@app.route("/", host=["example.com", "somethingelse.com", "therestofyourdomains.com"])
|
||||||
"/", host=["example.com", "somethingelse.com", "therestofyourdomains.com"]
|
|
||||||
)
|
|
||||||
async def hello_0(request):
|
async def hello_0(request):
|
||||||
return response.text("Some defaults")
|
return response.text("Some defaults")
|
||||||
|
|
||||||
|
@ -7,14 +7,10 @@ from emoji import EMOJI
|
|||||||
COLUMN_PATTERN = re.compile(r"---:1\s*(.*?)\s*:--:1\s*(.*?)\s*:---", re.DOTALL)
|
COLUMN_PATTERN = re.compile(r"---:1\s*(.*?)\s*:--:1\s*(.*?)\s*:---", re.DOTALL)
|
||||||
PYTHON_HIGHLIGHT_PATTERN = re.compile(r"```python\{+.*?\}", re.DOTALL)
|
PYTHON_HIGHLIGHT_PATTERN = re.compile(r"```python\{+.*?\}", re.DOTALL)
|
||||||
BASH_HIGHLIGHT_PATTERN = re.compile(r"```bash\{+.*?\}", re.DOTALL)
|
BASH_HIGHLIGHT_PATTERN = re.compile(r"```bash\{+.*?\}", re.DOTALL)
|
||||||
NOTIFICATION_PATTERN = re.compile(
|
NOTIFICATION_PATTERN = re.compile(r":::\s*(\w+)\s*(.*?)\n([\s\S]*?):::", re.MULTILINE)
|
||||||
r":::\s*(\w+)\s*(.*?)\n([\s\S]*?):::", re.MULTILINE
|
|
||||||
)
|
|
||||||
EMOJI_PATTERN = re.compile(r":(\w+):")
|
EMOJI_PATTERN = re.compile(r":(\w+):")
|
||||||
CURRENT_DIR = Path(__file__).parent
|
CURRENT_DIR = Path(__file__).parent
|
||||||
SOURCE_DIR = (
|
SOURCE_DIR = CURRENT_DIR.parent.parent.parent.parent / "sanic-guide" / "src" / "en"
|
||||||
CURRENT_DIR.parent.parent.parent.parent / "sanic-guide" / "src" / "en"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_columns(content: str):
|
def convert_columns(content: str):
|
||||||
|
@ -13,9 +13,7 @@ def do_footer(builder: Builder, request: Request) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _pagination(request: Request) -> Builder:
|
def _pagination(request: Request) -> Builder:
|
||||||
return E.div(
|
return E.div(_pagination_left(request), _pagination_right(request), class_="level")
|
||||||
_pagination_left(request), _pagination_right(request), class_="level"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _pagination_left(request: Request) -> Builder:
|
def _pagination_left(request: Request) -> Builder:
|
||||||
@ -64,9 +62,7 @@ def _content() -> Builder:
|
|||||||
href="https://github.com/sanic-org/sanic/blob/master/LICENSE",
|
href="https://github.com/sanic-org/sanic/blob/master/LICENSE",
|
||||||
target="_blank",
|
target="_blank",
|
||||||
rel="nofollow noopener noreferrer",
|
rel="nofollow noopener noreferrer",
|
||||||
).br()(
|
).br()(E.small(f"Copyright © 2018-{year} Sanic Community Organization")),
|
||||||
E.small(f"Copyright © 2018-{year} Sanic Community Organization")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return E.div(
|
return E.div(
|
||||||
inner,
|
inner,
|
||||||
|
@ -6,12 +6,9 @@ from webapp.display.layouts.models import MenuItem
|
|||||||
|
|
||||||
def do_navbar(builder: Builder, request: Request) -> None:
|
def do_navbar(builder: Builder, request: Request) -> None:
|
||||||
navbar_items = [
|
navbar_items = [
|
||||||
_render_navbar_item(item, request)
|
_render_navbar_item(item, request) for item in request.app.config.NAVBAR
|
||||||
for item in request.app.config.NAVBAR
|
|
||||||
]
|
]
|
||||||
container = E.div(
|
container = E.div(_search_form(request), *navbar_items, class_="navbar-end")
|
||||||
_search_form(request), *navbar_items, class_="navbar-end"
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.nav(
|
builder.nav(
|
||||||
E.div(container, class_="navbar-menu"),
|
E.div(container, class_="navbar-menu"),
|
||||||
@ -46,10 +43,7 @@ def _render_navbar_item(item: MenuItem, request: Request) -> Builder:
|
|||||||
return E.div(
|
return E.div(
|
||||||
E.a(item.label, class_="navbar-link"),
|
E.a(item.label, class_="navbar-link"),
|
||||||
E.div(
|
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-dropdown",
|
||||||
),
|
),
|
||||||
class_="navbar-item has-dropdown is-hoverable",
|
class_="navbar-item has-dropdown is-hoverable",
|
||||||
|
@ -15,9 +15,7 @@ def _menu_items(request: Request) -> list[Builder]:
|
|||||||
_sanic_logo(request),
|
_sanic_logo(request),
|
||||||
*_sidebar_items(request),
|
*_sidebar_items(request),
|
||||||
E.hr(),
|
E.hr(),
|
||||||
E.p("Current with version ").strong(
|
E.p("Current with version ").strong(request.app.config.GENERAL.current_version),
|
||||||
request.app.config.GENERAL.current_version
|
|
||||||
),
|
|
||||||
E.hr(),
|
E.hr(),
|
||||||
E.p("Want more? ").a(
|
E.p("Want more? ").a(
|
||||||
"sanicbook.com", href="https://sanicbook.com", target="_blank"
|
"sanicbook.com", href="https://sanicbook.com", target="_blank"
|
||||||
@ -73,9 +71,7 @@ def _single_sidebar_item(item: MenuItem, request: Request) -> Builder:
|
|||||||
kwargs = {}
|
kwargs = {}
|
||||||
classes: list[str] = []
|
classes: list[str] = []
|
||||||
li_classes = "menu-item"
|
li_classes = "menu-item"
|
||||||
_, page, _ = request.app.ctx.get_page(
|
_, page, _ = request.app.ctx.get_page(request.ctx.language, item.path or "")
|
||||||
request.ctx.language, item.path or ""
|
|
||||||
)
|
|
||||||
if request.path == path:
|
if request.path == path:
|
||||||
classes.append("is-active")
|
classes.append("is-active")
|
||||||
if item.href:
|
if item.href:
|
||||||
|
@ -36,9 +36,9 @@ class DocsRenderer(HTMLRenderer):
|
|||||||
class_="code-block__copy",
|
class_="code-block__copy",
|
||||||
onclick="copyCode(this)",
|
onclick="copyCode(this)",
|
||||||
):
|
):
|
||||||
builder.div(
|
builder.div(class_="code-block__rectangle code-block__filled").div(
|
||||||
class_="code-block__rectangle code-block__filled"
|
class_="code-block__rectangle code-block__outlined"
|
||||||
).div(class_="code-block__rectangle code-block__outlined")
|
)
|
||||||
else:
|
else:
|
||||||
builder.pre(E.code(escape(code)))
|
builder.pre(E.code(escape(code)))
|
||||||
return str(builder)
|
return str(builder)
|
||||||
@ -46,9 +46,7 @@ class DocsRenderer(HTMLRenderer):
|
|||||||
def heading(self, text: str, level: int, **attrs) -> str:
|
def heading(self, text: str, level: int, **attrs) -> str:
|
||||||
ident = slugify(text)
|
ident = slugify(text)
|
||||||
if level > 1:
|
if level > 1:
|
||||||
text += self._make_tag(
|
text += self._make_tag("a", {"href": f"#{ident}", "class": "anchor"}, "#")
|
||||||
"a", {"href": f"#{ident}", "class": "anchor"}, "#"
|
|
||||||
)
|
|
||||||
return self._make_tag(
|
return self._make_tag(
|
||||||
f"h{level}", {"id": ident, "class": f"is-size-{level}"}, text
|
f"h{level}", {"id": ident, "class": f"is-size-{level}"}, text
|
||||||
)
|
)
|
||||||
@ -92,9 +90,7 @@ class DocsRenderer(HTMLRenderer):
|
|||||||
def _make_tag(
|
def _make_tag(
|
||||||
self, tag: str, attributes: dict[str, str], text: str | None = None
|
self, tag: str, attributes: dict[str, str], text: str | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
attrs = " ".join(
|
attrs = " ".join(f'{key}="{value}"' for key, value in attributes.items())
|
||||||
f'{key}="{value}"' for key, value in attributes.items()
|
|
||||||
)
|
|
||||||
if text is None:
|
if text is None:
|
||||||
return f"<{tag} {attrs} />"
|
return f"<{tag} {attrs} />"
|
||||||
return f"<{tag} {attrs}>{text}</{tag}>"
|
return f"<{tag} {attrs}>{text}</{tag}>"
|
||||||
|
@ -119,9 +119,7 @@ def _extract_docobjects(package_name: str) -> dict[str, DocObject]:
|
|||||||
docstrings = {}
|
docstrings = {}
|
||||||
package = importlib.import_module(package_name)
|
package = importlib.import_module(package_name)
|
||||||
|
|
||||||
for _, name, _ in pkgutil.walk_packages(
|
for _, name, _ in pkgutil.walk_packages(package.__path__, package_name + "."):
|
||||||
package.__path__, package_name + "."
|
|
||||||
):
|
|
||||||
module = importlib.import_module(name)
|
module = importlib.import_module(name)
|
||||||
for obj_name, obj in inspect.getmembers(module):
|
for obj_name, obj in inspect.getmembers(module):
|
||||||
if (
|
if (
|
||||||
@ -155,9 +153,7 @@ def _docobject_to_html(
|
|||||||
) -> None:
|
) -> None:
|
||||||
anchor_id = slugify(docobject.full_name.replace(".", "-"))
|
anchor_id = slugify(docobject.full_name.replace(".", "-"))
|
||||||
anchor = E.a("#", class_="anchor", href=f"#{anchor_id}")
|
anchor = E.a("#", class_="anchor", href=f"#{anchor_id}")
|
||||||
class_name, heading = _define_heading_and_class(
|
class_name, heading = _define_heading_and_class(docobject, anchor, as_method)
|
||||||
docobject, anchor, as_method
|
|
||||||
)
|
|
||||||
|
|
||||||
with builder.div(class_=class_name):
|
with builder.div(class_=class_name):
|
||||||
builder(heading)
|
builder(heading)
|
||||||
@ -211,9 +207,7 @@ def _docobject_to_html(
|
|||||||
|
|
||||||
if docobject.docstring.params:
|
if docobject.docstring.params:
|
||||||
with builder.div(class_="box mt-5"):
|
with builder.div(class_="box mt-5"):
|
||||||
builder.h5(
|
builder.h5("Parameters", class_="is-size-5 has-text-weight-bold")
|
||||||
"Parameters", class_="is-size-5 has-text-weight-bold"
|
|
||||||
)
|
|
||||||
_render_params(builder, docobject.docstring.params)
|
_render_params(builder, docobject.docstring.params)
|
||||||
|
|
||||||
if docobject.docstring.returns:
|
if docobject.docstring.returns:
|
||||||
@ -238,9 +232,7 @@ def _signature_to_html(
|
|||||||
parts = []
|
parts = []
|
||||||
parts.append("<span class='function-signature'>")
|
parts.append("<span class='function-signature'>")
|
||||||
for decorator in decorators:
|
for decorator in decorators:
|
||||||
parts.append(
|
parts.append(f"<span class='function-decorator'>@{decorator}</span><br>")
|
||||||
f"<span class='function-decorator'>@{decorator}</span><br>"
|
|
||||||
)
|
|
||||||
parts.append(
|
parts.append(
|
||||||
f"<span class='is-italic'>{object_type}</span> "
|
f"<span class='is-italic'>{object_type}</span> "
|
||||||
f"<span class='has-text-weight-bold'>{name}</span>("
|
f"<span class='has-text-weight-bold'>{name}</span>("
|
||||||
@ -254,9 +246,7 @@ def _signature_to_html(
|
|||||||
annotation = ""
|
annotation = ""
|
||||||
if param.annotation != inspect.Parameter.empty:
|
if param.annotation != inspect.Parameter.empty:
|
||||||
annotation = escape(str(param.annotation))
|
annotation = escape(str(param.annotation))
|
||||||
parts.append(
|
parts.append(f": <span class='param-annotation'>{annotation}</span>")
|
||||||
f": <span class='param-annotation'>{annotation}</span>"
|
|
||||||
)
|
|
||||||
if param.default != inspect.Parameter.empty:
|
if param.default != inspect.Parameter.empty:
|
||||||
default = escape(str(param.default))
|
default = escape(str(param.default))
|
||||||
if annotation == "str":
|
if annotation == "str":
|
||||||
@ -267,9 +257,7 @@ def _signature_to_html(
|
|||||||
parts.append(")")
|
parts.append(")")
|
||||||
if signature.return_annotation != inspect.Signature.empty:
|
if signature.return_annotation != inspect.Signature.empty:
|
||||||
return_annotation = escape(str(signature.return_annotation))
|
return_annotation = escape(str(signature.return_annotation))
|
||||||
parts.append(
|
parts.append(f": -> <span class='return-annotation'>{return_annotation}</span>")
|
||||||
f": -> <span class='return-annotation'>{return_annotation}</span>"
|
|
||||||
)
|
|
||||||
parts.append("</span>")
|
parts.append("</span>")
|
||||||
return "".join(parts)
|
return "".join(parts)
|
||||||
|
|
||||||
@ -317,10 +305,7 @@ def _render_params(builder: Builder, params: list[DocstringParam]) -> None:
|
|||||||
builder.dd(
|
builder.dd(
|
||||||
HTML(
|
HTML(
|
||||||
render_markdown(
|
render_markdown(
|
||||||
param.description
|
param.description or param.arg_name or param.type_name or ""
|
||||||
or param.arg_name
|
|
||||||
or param.type_name
|
|
||||||
or ""
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -333,11 +318,7 @@ def _render_raises(builder: Builder, raises: list[DocstringRaises]) -> None:
|
|||||||
with builder.dl(class_="mt-2"):
|
with builder.dl(class_="mt-2"):
|
||||||
builder.dt(raise_.type_name, class_="is-family-monospace")
|
builder.dt(raise_.type_name, class_="is-family-monospace")
|
||||||
builder.dd(
|
builder.dd(
|
||||||
HTML(
|
HTML(render_markdown(raise_.description or raise_.type_name or ""))
|
||||||
render_markdown(
|
|
||||||
raise_.description or raise_.type_name or ""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -353,11 +334,7 @@ def _render_returns(builder: Builder, docobject: DocObject) -> None:
|
|||||||
if not return_type or return_type == inspect.Signature.empty:
|
if not return_type or return_type == inspect.Signature.empty:
|
||||||
return_type = "N/A"
|
return_type = "N/A"
|
||||||
|
|
||||||
term = (
|
term = "Return" if not docobject.docstring.returns.is_generator else "Yields"
|
||||||
"Return"
|
|
||||||
if not docobject.docstring.returns.is_generator
|
|
||||||
else "Yields"
|
|
||||||
)
|
|
||||||
builder.h5(term, class_="is-size-5 has-text-weight-bold")
|
builder.h5(term, class_="is-size-5 has-text-weight-bold")
|
||||||
with builder.dl(class_="mt-2"):
|
with builder.dl(class_="mt-2"):
|
||||||
builder.dt(return_type, class_="is-family-monospace")
|
builder.dt(return_type, class_="is-family-monospace")
|
||||||
@ -372,17 +349,11 @@ def _render_returns(builder: Builder, docobject: DocObject) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _render_examples(
|
def _render_examples(builder: Builder, examples: list[DocstringExample]) -> None:
|
||||||
builder: Builder, examples: list[DocstringExample]
|
|
||||||
) -> None:
|
|
||||||
with builder.div(class_="box mt-5"):
|
with builder.div(class_="box mt-5"):
|
||||||
builder.h5("Examples", class_="is-size-5 has-text-weight-bold")
|
builder.h5("Examples", class_="is-size-5 has-text-weight-bold")
|
||||||
for example in examples:
|
for example in examples:
|
||||||
with builder.div(class_="mt-2"):
|
with builder.div(class_="mt-2"):
|
||||||
builder(
|
builder(
|
||||||
HTML(
|
HTML(render_markdown(example.description or example.snippet or ""))
|
||||||
render_markdown(
|
|
||||||
example.description or example.snippet or ""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
@ -11,9 +11,7 @@ from ..layouts.main import MainLayout
|
|||||||
from ..markdown import render_markdown
|
from ..markdown import render_markdown
|
||||||
from .docobject import organize_docobjects
|
from .docobject import organize_docobjects
|
||||||
|
|
||||||
_PAGE_CACHE: dict[
|
_PAGE_CACHE: dict[str, dict[str, tuple[Page | None, Page | None, Page | None]]] = {}
|
||||||
str, dict[str, tuple[Page | None, Page | None, Page | None]]
|
|
||||||
] = {}
|
|
||||||
_LAYOUTS_CACHE: dict[str, type[BaseLayout]] = {
|
_LAYOUTS_CACHE: dict[str, type[BaseLayout]] = {
|
||||||
"home": HomeLayout,
|
"home": HomeLayout,
|
||||||
"main": MainLayout,
|
"main": MainLayout,
|
||||||
|
@ -20,13 +20,9 @@ class PageRenderer(BaseRenderer):
|
|||||||
self._body(request, builder, language, path)
|
self._body(request, builder, language, path)
|
||||||
return builder
|
return builder
|
||||||
|
|
||||||
def _body(
|
def _body(self, request: Request, builder: Builder, language: str, path: str):
|
||||||
self, request: Request, builder: Builder, language: str, path: str
|
|
||||||
):
|
|
||||||
prev_page, current_page, next_page = Page.get(language, path)
|
prev_page, current_page, next_page = Page.get(language, path)
|
||||||
request.ctx.language = (
|
request.ctx.language = Page.DEFAULT_LANGUAGE if language == "api" else language
|
||||||
Page.DEFAULT_LANGUAGE if language == "api" else language
|
|
||||||
)
|
|
||||||
request.ctx.current_page = current_page
|
request.ctx.current_page = current_page
|
||||||
request.ctx.previous_page = prev_page
|
request.ctx.previous_page = prev_page
|
||||||
request.ctx.next_page = next_page
|
request.ctx.next_page = next_page
|
||||||
@ -38,9 +34,7 @@ class PageRenderer(BaseRenderer):
|
|||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _base(self, request: Request, builder: Builder, page: Page | None):
|
def _base(self, request: Request, builder: Builder, page: Page | None):
|
||||||
layout_type: type[BaseLayout] = (
|
layout_type: type[BaseLayout] = page.get_layout() if page else BaseLayout
|
||||||
page.get_layout() if page else BaseLayout
|
|
||||||
)
|
|
||||||
layout = layout_type(builder)
|
layout = layout_type(builder)
|
||||||
with layout(request, builder.full):
|
with layout(request, builder.full):
|
||||||
yield
|
yield
|
||||||
|
@ -15,9 +15,7 @@ class Attributes(DirectivePlugin):
|
|||||||
if md.renderer.NAME == "html":
|
if md.renderer.NAME == "html":
|
||||||
md.renderer.register("attrs", self._render)
|
md.renderer.register("attrs", self._render)
|
||||||
|
|
||||||
def parse(
|
def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]:
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
info = m.groupdict()
|
info = m.groupdict()
|
||||||
options = dict(self.parse_options(m))
|
options = dict(self.parse_options(m))
|
||||||
new_state = block.state_cls()
|
new_state = block.state_cls()
|
||||||
|
@ -10,9 +10,7 @@ from mistune.markdown import Markdown
|
|||||||
|
|
||||||
|
|
||||||
class Column(DirectivePlugin):
|
class Column(DirectivePlugin):
|
||||||
def parse(
|
def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]:
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
info = m.groupdict()
|
info = m.groupdict()
|
||||||
|
|
||||||
new_state = block.state_cls()
|
new_state = block.state_cls()
|
||||||
@ -36,9 +34,7 @@ class Column(DirectivePlugin):
|
|||||||
|
|
||||||
def _render_column(self, renderer: HTMLRenderer, text: str, **attrs):
|
def _render_column(self, renderer: HTMLRenderer, text: str, **attrs):
|
||||||
start = (
|
start = (
|
||||||
'<div class="columns mt-3 is-multiline">\n'
|
'<div class="columns mt-3 is-multiline">\n' if attrs.get("first") else ""
|
||||||
if attrs.get("first")
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
end = "</div>\n" if attrs.get("last") else ""
|
end = "</div>\n" if attrs.get("last") else ""
|
||||||
col = f'<div class="column is-half">{text}</div>\n'
|
col = f'<div class="column is-half">{text}</div>\n'
|
||||||
|
@ -16,16 +16,12 @@ class Hook(DirectivePlugin):
|
|||||||
for type_ in ("column", "tab"):
|
for type_ in ("column", "tab"):
|
||||||
if token["type"] == type_:
|
if token["type"] == type_:
|
||||||
maybe_next = (
|
maybe_next = (
|
||||||
state.tokens[idx + 1]
|
state.tokens[idx + 1] if idx + 1 < len(state.tokens) else None
|
||||||
if idx + 1 < len(state.tokens)
|
|
||||||
else None
|
|
||||||
)
|
)
|
||||||
token.setdefault("attrs", {})
|
token.setdefault("attrs", {})
|
||||||
if prev and prev["type"] != type_:
|
if prev and prev["type"] != type_:
|
||||||
token["attrs"]["first"] = True
|
token["attrs"]["first"] = True
|
||||||
if (
|
if (maybe_next and maybe_next["type"] != type_) or not maybe_next:
|
||||||
maybe_next and maybe_next["type"] != type_
|
|
||||||
) or not maybe_next:
|
|
||||||
token["attrs"]["last"] = True
|
token["attrs"]["last"] = True
|
||||||
|
|
||||||
prev = token
|
prev = token
|
||||||
|
@ -12,9 +12,7 @@ from mistune.markdown import Markdown
|
|||||||
|
|
||||||
|
|
||||||
class Mermaid(DirectivePlugin):
|
class Mermaid(DirectivePlugin):
|
||||||
def parse(
|
def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]:
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
info = m.groupdict()
|
info = m.groupdict()
|
||||||
|
|
||||||
new_state = block.state_cls()
|
new_state = block.state_cls()
|
||||||
|
@ -19,12 +19,8 @@ class Notification(Admonition):
|
|||||||
|
|
||||||
if md.renderer.NAME == "html":
|
if md.renderer.NAME == "html":
|
||||||
md.renderer.register("admonition", self._render_admonition)
|
md.renderer.register("admonition", self._render_admonition)
|
||||||
md.renderer.register(
|
md.renderer.register("admonition_title", self._render_admonition_title)
|
||||||
"admonition_title", self._render_admonition_title
|
md.renderer.register("admonition_content", self._render_admonition_content)
|
||||||
)
|
|
||||||
md.renderer.register(
|
|
||||||
"admonition_content", self._render_admonition_content
|
|
||||||
)
|
|
||||||
|
|
||||||
def _render_admonition(self, _, text, name, **attrs) -> str:
|
def _render_admonition(self, _, text, name, **attrs) -> str:
|
||||||
return str(
|
return str(
|
||||||
|
@ -10,9 +10,7 @@ from mistune.markdown import Markdown
|
|||||||
|
|
||||||
|
|
||||||
class Tabs(DirectivePlugin):
|
class Tabs(DirectivePlugin):
|
||||||
def parse(
|
def parse(self, block: BlockParser, m: Match, state: BlockState) -> dict[str, Any]:
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
info = m.groupdict()
|
info = m.groupdict()
|
||||||
|
|
||||||
new_state = block.state_cls()
|
new_state = block.state_cls()
|
||||||
@ -41,9 +39,7 @@ class Tabs(DirectivePlugin):
|
|||||||
def _render_tab(self, renderer: HTMLRenderer, text: str, **attrs):
|
def _render_tab(self, renderer: HTMLRenderer, text: str, **attrs):
|
||||||
start = '<div class="tabs mt-6"><ul>\n' if attrs.get("first") else ""
|
start = '<div class="tabs mt-6"><ul>\n' if attrs.get("first") else ""
|
||||||
end = (
|
end = (
|
||||||
'</ul></div><div class="tab-display"></div>\n'
|
'</ul></div><div class="tab-display"></div>\n' if attrs.get("last") else ""
|
||||||
if attrs.get("last")
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
content = f'<div class="tab-content">{text}</div>\n'
|
content = f'<div class="tab-content">{text}</div>\n'
|
||||||
tab = f'<li><a>{attrs["title"]}</a>{content}</li>\n'
|
tab = f'<li><a>{attrs["title"]}</a>{content}</li>\n'
|
||||||
|
@ -92,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()}
|
return {word: num_docs / count for word, count in word_count.items()}
|
||||||
|
|
||||||
|
|
||||||
def _tf_idf_vector(
|
def _tf_idf_vector(document: Document, idf: dict[str, float]) -> dict[str, float]:
|
||||||
document: Document, idf: dict[str, float]
|
|
||||||
) -> dict[str, float]:
|
|
||||||
"""Calculate the TF-IDF vector for a document."""
|
"""Calculate the TF-IDF vector for a document."""
|
||||||
return {
|
return {
|
||||||
word: tf * idf[word]
|
word: tf * idf[word]
|
||||||
@ -103,9 +101,7 @@ def _tf_idf_vector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _cosine_similarity(
|
def _cosine_similarity(vec1: dict[str, float], vec2: dict[str, float]) -> float:
|
||||||
vec1: dict[str, float], vec2: dict[str, float]
|
|
||||||
) -> float:
|
|
||||||
"""Calculate the cosine similarity between two vectors."""
|
"""Calculate the cosine similarity between two vectors."""
|
||||||
if not vec1 or not vec2:
|
if not vec1 or not vec2:
|
||||||
return 0.0
|
return 0.0
|
||||||
@ -127,9 +123,7 @@ def _search(
|
|||||||
tf_idf_query = _tf_idf_vector(
|
tf_idf_query = _tf_idf_vector(
|
||||||
Document(page=dummy_page, language=language).process(stemmer), idf
|
Document(page=dummy_page, language=language).process(stemmer), idf
|
||||||
)
|
)
|
||||||
similarities = [
|
similarities = [_cosine_similarity(tf_idf_query, vector) for vector in vectors]
|
||||||
_cosine_similarity(tf_idf_query, vector) for vector in vectors
|
|
||||||
]
|
|
||||||
return [
|
return [
|
||||||
(similarity, document)
|
(similarity, document)
|
||||||
for similarity, document in sorted(
|
for similarity, document in sorted(
|
||||||
@ -156,16 +150,13 @@ class Searcher:
|
|||||||
}
|
}
|
||||||
self._vectors = {
|
self._vectors = {
|
||||||
language: [
|
language: [
|
||||||
_tf_idf_vector(document, self._idf[language])
|
_tf_idf_vector(document, self._idf[language]) for document in documents
|
||||||
for document in documents
|
|
||||||
]
|
]
|
||||||
for language, documents in self._documents.items()
|
for language, documents in self._documents.items()
|
||||||
}
|
}
|
||||||
self._stemmer = stemmer
|
self._stemmer = stemmer
|
||||||
|
|
||||||
def search(
|
def search(self, query: str, language: str) -> list[tuple[float, Document]]:
|
||||||
self, query: str, language: str
|
|
||||||
) -> list[tuple[float, Document]]:
|
|
||||||
return _search(
|
return _search(
|
||||||
query,
|
query,
|
||||||
language,
|
language,
|
||||||
|
@ -28,13 +28,9 @@ def create_app(root: Path) -> Sanic:
|
|||||||
app.config.STYLE_DIR = root / "style"
|
app.config.STYLE_DIR = root / "style"
|
||||||
app.config.NODE_MODULES_DIR = root / "node_modules"
|
app.config.NODE_MODULES_DIR = root / "node_modules"
|
||||||
app.config.LANGUAGES = ["en"]
|
app.config.LANGUAGES = ["en"]
|
||||||
app.config.SIDEBAR = load_menu(
|
app.config.SIDEBAR = load_menu(app.config.CONFIG_DIR / "en" / "sidebar.yaml")
|
||||||
app.config.CONFIG_DIR / "en" / "sidebar.yaml"
|
|
||||||
)
|
|
||||||
app.config.NAVBAR = load_menu(app.config.CONFIG_DIR / "en" / "navbar.yaml")
|
app.config.NAVBAR = load_menu(app.config.CONFIG_DIR / "en" / "navbar.yaml")
|
||||||
app.config.GENERAL = load_config(
|
app.config.GENERAL = load_config(app.config.CONFIG_DIR / "en" / "general.yaml")
|
||||||
app.config.CONFIG_DIR / "en" / "general.yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
setup_livereload(app)
|
setup_livereload(app)
|
||||||
setup_style(app)
|
setup_style(app)
|
||||||
@ -66,8 +62,6 @@ def create_app(root: Path) -> Sanic:
|
|||||||
|
|
||||||
@app.on_request
|
@app.on_request
|
||||||
async def set_language(request: Request):
|
async def set_language(request: Request):
|
||||||
request.ctx.language = request.match_info.get(
|
request.ctx.language = request.match_info.get("language", Page.DEFAULT_LANGUAGE)
|
||||||
"language", Page.DEFAULT_LANGUAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -53,16 +53,12 @@ class Livereload:
|
|||||||
"serverName": SERVER_NAME,
|
"serverName": SERVER_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, reload_queue: Queue, debug: bool, state: dict[str, Any]):
|
||||||
self, reload_queue: Queue, debug: bool, state: dict[str, Any]
|
|
||||||
):
|
|
||||||
self.reload_queue = reload_queue
|
self.reload_queue = reload_queue
|
||||||
self.app = Sanic(self.SERVER_NAME)
|
self.app = Sanic(self.SERVER_NAME)
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.state = state
|
self.state = state
|
||||||
self.app.static(
|
self.app.static("/livereload.js", Path(__file__).parent / "livereload.js")
|
||||||
"/livereload.js", Path(__file__).parent / "livereload.js"
|
|
||||||
)
|
|
||||||
self.app.add_websocket_route(
|
self.app.add_websocket_route(
|
||||||
self.livereload_handler, "/livereload", name="livereload"
|
self.livereload_handler, "/livereload", name="livereload"
|
||||||
)
|
)
|
||||||
@ -108,7 +104,5 @@ class Livereload:
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def _run_reload_server(
|
def _run_reload_server(reload_queue: Queue, debug: bool, state: dict[str, Any]):
|
||||||
reload_queue: Queue, debug: bool, state: dict[str, Any]
|
|
||||||
):
|
|
||||||
Livereload(reload_queue, debug, state).run()
|
Livereload(reload_queue, debug, state).run()
|
||||||
|
126
sanic/app.py
126
sanic/app.py
@ -309,9 +309,7 @@ class Sanic(
|
|||||||
self.asgi = False
|
self.asgi = False
|
||||||
self.auto_reload = False
|
self.auto_reload = False
|
||||||
self.blueprints: dict[str, Blueprint] = {}
|
self.blueprints: dict[str, Blueprint] = {}
|
||||||
self.certloader_class: type[CertLoader] = (
|
self.certloader_class: type[CertLoader] = certloader_class or CertLoader
|
||||||
certloader_class or CertLoader
|
|
||||||
)
|
|
||||||
self.configure_logging: bool = configure_logging
|
self.configure_logging: bool = configure_logging
|
||||||
self.ctx: ctx_type = cast(ctx_type, ctx or SimpleNamespace())
|
self.ctx: ctx_type = cast(ctx_type, ctx or SimpleNamespace())
|
||||||
self.error_handler: ErrorHandler = error_handler or ErrorHandler()
|
self.error_handler: ErrorHandler = error_handler or ErrorHandler()
|
||||||
@ -387,15 +385,11 @@ class Sanic(
|
|||||||
try:
|
try:
|
||||||
_event = ListenerEvent[event.upper()]
|
_event = ListenerEvent[event.upper()]
|
||||||
except (ValueError, AttributeError):
|
except (ValueError, AttributeError):
|
||||||
valid = ", ".join(
|
valid = ", ".join(x.lower() for x in ListenerEvent.__members__.keys())
|
||||||
x.lower() for x in ListenerEvent.__members__.keys()
|
|
||||||
)
|
|
||||||
raise BadRequest(f"Invalid event: {event}. Use one of: {valid}")
|
raise BadRequest(f"Invalid event: {event}. Use one of: {valid}")
|
||||||
|
|
||||||
if "." in _event:
|
if "." in _event:
|
||||||
self.signal(_event.value)(
|
self.signal(_event.value)(partial(self._listener, listener=listener))
|
||||||
partial(self._listener, listener=listener)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.listeners[_event.value].append(listener)
|
self.listeners[_event.value].append(listener)
|
||||||
|
|
||||||
@ -522,9 +516,7 @@ class Sanic(
|
|||||||
def _apply_listener(self, listener: FutureListener):
|
def _apply_listener(self, listener: FutureListener):
|
||||||
return self.register_listener(listener.listener, listener.event)
|
return self.register_listener(listener.listener, listener.event)
|
||||||
|
|
||||||
def _apply_route(
|
def _apply_route(self, route: FutureRoute, overwrite: bool = False) -> list[Route]:
|
||||||
self, route: FutureRoute, overwrite: bool = False
|
|
||||||
) -> list[Route]:
|
|
||||||
params = route._asdict()
|
params = route._asdict()
|
||||||
params["overwrite"] = overwrite
|
params["overwrite"] = overwrite
|
||||||
websocket = params.pop("websocket", False)
|
websocket = params.pop("websocket", False)
|
||||||
@ -653,9 +645,7 @@ class Sanic(
|
|||||||
fail_not_found=fail_not_found,
|
fail_not_found=fail_not_found,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def event(
|
async def event(self, event: str, timeout: int | float | None = None) -> None:
|
||||||
self, event: str, timeout: int | float | None = None
|
|
||||||
) -> None:
|
|
||||||
"""Wait for a specific event to be triggered.
|
"""Wait for a specific event to be triggered.
|
||||||
|
|
||||||
This method waits for a named event to be triggered and can be used
|
This method waits for a named event to be triggered and can be used
|
||||||
@ -740,9 +730,7 @@ class Sanic(
|
|||||||
async def report(exception: Exception) -> None:
|
async def report(exception: Exception) -> None:
|
||||||
await handler(self, exception)
|
await handler(self, exception)
|
||||||
|
|
||||||
self.add_signal(
|
self.add_signal(handler=report, event=Event.SERVER_EXCEPTION_REPORT.value)
|
||||||
handler=report, event=Event.SERVER_EXCEPTION_REPORT.value
|
|
||||||
)
|
|
||||||
|
|
||||||
return report
|
return report
|
||||||
|
|
||||||
@ -831,14 +819,12 @@ class Sanic(
|
|||||||
|
|
||||||
for _attr in ["version", "strict_slashes"]:
|
for _attr in ["version", "strict_slashes"]:
|
||||||
if getattr(item, _attr) is None:
|
if getattr(item, _attr) is None:
|
||||||
params[_attr] = getattr(
|
params[_attr] = getattr(blueprint, _attr) or options.get(
|
||||||
blueprint, _attr
|
_attr
|
||||||
) or options.get(_attr)
|
)
|
||||||
if item.version_prefix == "/v":
|
if item.version_prefix == "/v":
|
||||||
if blueprint.version_prefix == "/v":
|
if blueprint.version_prefix == "/v":
|
||||||
params["version_prefix"] = options.get(
|
params["version_prefix"] = options.get("version_prefix")
|
||||||
"version_prefix"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
params["version_prefix"] = blueprint.version_prefix
|
params["version_prefix"] = blueprint.version_prefix
|
||||||
name_prefix = getattr(blueprint, "name_prefix", None)
|
name_prefix = getattr(blueprint, "name_prefix", None)
|
||||||
@ -855,10 +841,7 @@ class Sanic(
|
|||||||
self.blueprints[blueprint.name] = blueprint
|
self.blueprints[blueprint.name] = blueprint
|
||||||
self._blueprint_order.append(blueprint)
|
self._blueprint_order.append(blueprint)
|
||||||
|
|
||||||
if (
|
if self.strict_slashes is not None and blueprint.strict_slashes is None:
|
||||||
self.strict_slashes is not None
|
|
||||||
and blueprint.strict_slashes is None
|
|
||||||
):
|
|
||||||
blueprint.strict_slashes = self.strict_slashes
|
blueprint.strict_slashes = self.strict_slashes
|
||||||
blueprint.register(self, options)
|
blueprint.register(self, options)
|
||||||
|
|
||||||
@ -928,9 +911,7 @@ class Sanic(
|
|||||||
|
|
||||||
route = self.router.find_route_by_view_name(view_name, **kw)
|
route = self.router.find_route_by_view_name(view_name, **kw)
|
||||||
if not route:
|
if not route:
|
||||||
raise URLBuildError(
|
raise URLBuildError(f"Endpoint with name `{view_name}` was not found")
|
||||||
f"Endpoint with name `{view_name}` was not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
uri = route.path
|
uri = route.path
|
||||||
|
|
||||||
@ -969,9 +950,7 @@ class Sanic(
|
|||||||
scheme = kwargs.pop("_scheme", "")
|
scheme = kwargs.pop("_scheme", "")
|
||||||
if route.extra.hosts and external:
|
if route.extra.hosts and external:
|
||||||
if not host and len(route.extra.hosts) > 1:
|
if not host and len(route.extra.hosts) > 1:
|
||||||
raise ValueError(
|
raise ValueError(f"Host is ambiguous: {', '.join(route.extra.hosts)}")
|
||||||
f"Host is ambiguous: {', '.join(route.extra.hosts)}"
|
|
||||||
)
|
|
||||||
elif host and host not in route.extra.hosts:
|
elif host and host not in route.extra.hosts:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Requested host ({host}) is not available for this "
|
f"Requested host ({host}) is not available for this "
|
||||||
@ -1087,10 +1066,7 @@ class Sanic(
|
|||||||
context={"request": request, "exception": exception},
|
context={"request": request, "exception": exception},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if request.stream is not None and request.stream.stage is not Stage.HANDLER:
|
||||||
request.stream is not None
|
|
||||||
and request.stream.stage is not Stage.HANDLER
|
|
||||||
):
|
|
||||||
error_logger.exception(exception, exc_info=True)
|
error_logger.exception(exception, exc_info=True)
|
||||||
logger.error(
|
logger.error(
|
||||||
"The error response will not be sent to the client for "
|
"The error response will not be sent to the client for "
|
||||||
@ -1137,10 +1113,7 @@ class Sanic(
|
|||||||
response = self.error_handler.default(request, e)
|
response = self.error_handler.default(request, e)
|
||||||
elif self.debug:
|
elif self.debug:
|
||||||
response = HTTPResponse(
|
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,
|
status=500,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -1185,9 +1158,7 @@ class Sanic(
|
|||||||
)
|
)
|
||||||
await response.eof()
|
await response.eof()
|
||||||
else:
|
else:
|
||||||
raise ServerError(
|
raise ServerError(f"Invalid response type {response!r} (need HTTPResponse)")
|
||||||
f"Invalid response type {response!r} (need HTTPResponse)"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def handle_request(self, request: Request) -> None: # no cov
|
async def handle_request(self, request: Request) -> None: # no cov
|
||||||
"""Handles a request by dispatching it to the appropriate handler.
|
"""Handles a request by dispatching it to the appropriate handler.
|
||||||
@ -1334,17 +1305,14 @@ class Sanic(
|
|||||||
else:
|
else:
|
||||||
if not hasattr(handler, "is_websocket"):
|
if not hasattr(handler, "is_websocket"):
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
f"Invalid response type {response!r} "
|
f"Invalid response type {response!r} " "(need HTTPResponse)"
|
||||||
"(need HTTPResponse)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except CancelledError: # type: ignore
|
except CancelledError: # type: ignore
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Response Generation Failed
|
# Response Generation Failed
|
||||||
await self.handle_exception(
|
await self.handle_exception(request, e, run_middleware=run_middleware)
|
||||||
request, e, run_middleware=run_middleware
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _websocket_handler(
|
async def _websocket_handler(
|
||||||
self, handler, request, *args, subprotocols=None, **kwargs
|
self, handler, request, *args, subprotocols=None, **kwargs
|
||||||
@ -1423,9 +1391,7 @@ class Sanic(
|
|||||||
# Execution
|
# Execution
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
async def _run_request_middleware(
|
async def _run_request_middleware(self, request, middleware_collection): # no cov
|
||||||
self, request, middleware_collection
|
|
||||||
): # no cov
|
|
||||||
request._request_middleware_started = True
|
request._request_middleware_started = True
|
||||||
|
|
||||||
for middleware in middleware_collection:
|
for middleware in middleware_collection:
|
||||||
@ -1502,9 +1468,7 @@ class Sanic(
|
|||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _listener(
|
async def _listener(app: Sanic, loop: AbstractEventLoop, listener: ListenerType):
|
||||||
app: Sanic, loop: AbstractEventLoop, listener: ListenerType
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
maybe_coro = listener(app) # type: ignore
|
maybe_coro = listener(app) # type: ignore
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@ -1533,9 +1497,7 @@ class Sanic(
|
|||||||
if isawaitable(task):
|
if isawaitable(task):
|
||||||
await task
|
await task
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
error_logger.warning(
|
error_logger.warning(f"Task {task} was cancelled before it completed.")
|
||||||
f"Task {task} was cancelled before it completed."
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await app.dispatch(
|
await app.dispatch(
|
||||||
@ -1644,18 +1606,14 @@ class Sanic(
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
try:
|
try:
|
||||||
loop = self.loop # Will raise SanicError if loop is not started
|
loop = self.loop # Will raise SanicError if loop is not started
|
||||||
return self._loop_add_task(
|
return self._loop_add_task(task, self, loop, name=name, register=register)
|
||||||
task, self, loop, name=name, register=register
|
|
||||||
)
|
|
||||||
except SanicException:
|
except SanicException:
|
||||||
task_name = f"sanic.delayed_task.{hash(task)}"
|
task_name = f"sanic.delayed_task.{hash(task)}"
|
||||||
if not self._delayed_tasks:
|
if not self._delayed_tasks:
|
||||||
self.after_server_start(partial(self.dispatch_delayed_tasks))
|
self.after_server_start(partial(self.dispatch_delayed_tasks))
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
raise RuntimeError(
|
raise RuntimeError("Cannot name task outside of a running application")
|
||||||
"Cannot name task outside of a running application"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.signal(task_name)(partial(self.run_delayed_task, task=task))
|
self.signal(task_name)(partial(self.run_delayed_task, task=task))
|
||||||
self._delayed_tasks.append(task_name)
|
self._delayed_tasks.append(task_name)
|
||||||
@ -1666,18 +1624,14 @@ class Sanic(
|
|||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_task(
|
def get_task(self, name: str, *, raise_exception: Literal[False]) -> Task | None:
|
||||||
self, name: str, *, raise_exception: Literal[False]
|
|
||||||
) -> Task | None:
|
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_task(self, name: str, *, raise_exception: bool) -> Task | None:
|
def get_task(self, name: str, *, raise_exception: bool) -> Task | None:
|
||||||
...
|
...
|
||||||
|
|
||||||
def get_task(
|
def get_task(self, name: str, *, raise_exception: bool = True) -> Task | None:
|
||||||
self, name: str, *, raise_exception: bool = True
|
|
||||||
) -> Task | None:
|
|
||||||
"""Get a named task.
|
"""Get a named task.
|
||||||
|
|
||||||
This method is used to get a task by its name. Optionally, you can
|
This method is used to get a task by its name. Optionally, you can
|
||||||
@ -1695,9 +1649,7 @@ class Sanic(
|
|||||||
return self._task_registry[name]
|
return self._task_registry[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
raise SanicException(
|
raise SanicException(f'Registered task named "{name}" not found.')
|
||||||
f'Registered task named "{name}" not found.'
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def cancel_task(
|
async def cancel_task(
|
||||||
@ -1809,11 +1761,7 @@ class Sanic(
|
|||||||
Iterable[Task[Any]]: The tasks that are currently registered with
|
Iterable[Task[Any]]: The tasks that are currently registered with
|
||||||
the application.
|
the application.
|
||||||
"""
|
"""
|
||||||
return (
|
return (task for task in iter(self._task_registry.values()) if task is not None)
|
||||||
task
|
|
||||||
for task in iter(self._task_registry.values())
|
|
||||||
if task is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# ASGI
|
# ASGI
|
||||||
@ -2055,9 +2003,7 @@ class Sanic(
|
|||||||
del cls._app_registry[name]
|
del cls._app_registry[name]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_app(
|
def get_app(cls, name: str | None = None, *, force_create: bool = False) -> Sanic:
|
||||||
cls, name: str | None = None, *, force_create: bool = False
|
|
||||||
) -> Sanic:
|
|
||||||
"""Retrieve an instantiated Sanic instance by name.
|
"""Retrieve an instantiated Sanic instance by name.
|
||||||
|
|
||||||
This method is best used when needing to get access to an already
|
This method is best used when needing to get access to an already
|
||||||
@ -2264,9 +2210,7 @@ class Sanic(
|
|||||||
self.finalize()
|
self.finalize()
|
||||||
|
|
||||||
route_names = [route.extra.ident for route in self.router.routes]
|
route_names = [route.extra.ident for route in self.router.routes]
|
||||||
duplicates = {
|
duplicates = {name for name in route_names if route_names.count(name) > 1}
|
||||||
name for name in route_names if route_names.count(name) > 1
|
|
||||||
}
|
|
||||||
if duplicates:
|
if duplicates:
|
||||||
names = ", ".join(duplicates)
|
names = ", ".join(duplicates)
|
||||||
message = (
|
message = (
|
||||||
@ -2311,9 +2255,7 @@ class Sanic(
|
|||||||
"shutdown",
|
"shutdown",
|
||||||
):
|
):
|
||||||
raise SanicException(f"Invalid server event: {event}")
|
raise SanicException(f"Invalid server event: {event}")
|
||||||
logger.debug(
|
logger.debug(f"Triggering server events: {event}", extra={"verbosity": 1})
|
||||||
f"Triggering server events: {event}", extra={"verbosity": 1}
|
|
||||||
)
|
|
||||||
reverse = concern == "shutdown"
|
reverse = concern == "shutdown"
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = self.loop
|
loop = self.loop
|
||||||
@ -2379,9 +2321,7 @@ class Sanic(
|
|||||||
Inspector: An instance of Inspector.
|
Inspector: An instance of Inspector.
|
||||||
"""
|
"""
|
||||||
if environ.get("SANIC_WORKER_PROCESS") or not self._inspector:
|
if environ.get("SANIC_WORKER_PROCESS") or not self._inspector:
|
||||||
raise SanicException(
|
raise SanicException("Can only access the inspector from the main process")
|
||||||
"Can only access the inspector from the main process"
|
|
||||||
)
|
|
||||||
return self._inspector
|
return self._inspector
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -2414,7 +2354,5 @@ class Sanic(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if environ.get("SANIC_WORKER_PROCESS") or not self._manager:
|
if environ.get("SANIC_WORKER_PROCESS") or not self._manager:
|
||||||
raise SanicException(
|
raise SanicException("Can only access the manager from the main process")
|
||||||
"Can only access the manager from the main process"
|
|
||||||
)
|
|
||||||
return self._manager
|
return self._manager
|
||||||
|
@ -61,10 +61,7 @@ def get_logo(full: bool = False, coffee: bool = False) -> str:
|
|||||||
else BASE_LOGO
|
else BASE_LOGO
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if sys.platform == "darwin" and environ.get("TERM_PROGRAM") == "Apple_Terminal":
|
||||||
sys.platform == "darwin"
|
|
||||||
and environ.get("TERM_PROGRAM") == "Apple_Terminal"
|
|
||||||
):
|
|
||||||
logo = ansi_pattern.sub("", logo)
|
logo = ansi_pattern.sub("", logo)
|
||||||
|
|
||||||
return logo
|
return logo
|
||||||
|
@ -79,9 +79,7 @@ class MOTDTTY(MOTD):
|
|||||||
def set_variables(self): # no cov
|
def set_variables(self): # no cov
|
||||||
"""Set the variables used for display."""
|
"""Set the variables used for display."""
|
||||||
fallback = (108, 24)
|
fallback = (108, 24)
|
||||||
terminal_width = max(
|
terminal_width = max(get_terminal_size(fallback=fallback).columns, fallback[0])
|
||||||
get_terminal_size(fallback=fallback).columns, fallback[0]
|
|
||||||
)
|
|
||||||
self.max_value_width = terminal_width - fallback[0] + 36
|
self.max_value_width = terminal_width - fallback[0] + 36
|
||||||
|
|
||||||
self.key_width = 4
|
self.key_width = 4
|
||||||
|
@ -52,13 +52,9 @@ class Spinner: # noqa
|
|||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
ci = _CursorInfo()
|
ci = _CursorInfo()
|
||||||
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
||||||
ctypes.windll.kernel32.GetConsoleCursorInfo(
|
ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||||
handle, ctypes.byref(ci)
|
|
||||||
)
|
|
||||||
ci.visible = False
|
ci.visible = False
|
||||||
ctypes.windll.kernel32.SetConsoleCursorInfo(
|
ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||||
handle, ctypes.byref(ci)
|
|
||||||
)
|
|
||||||
elif os.name == "posix":
|
elif os.name == "posix":
|
||||||
sys.stdout.write("\033[?25l")
|
sys.stdout.write("\033[?25l")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@ -68,13 +64,9 @@ class Spinner: # noqa
|
|||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
ci = _CursorInfo()
|
ci = _CursorInfo()
|
||||||
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
||||||
ctypes.windll.kernel32.GetConsoleCursorInfo(
|
ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||||
handle, ctypes.byref(ci)
|
|
||||||
)
|
|
||||||
ci.visible = True
|
ci.visible = True
|
||||||
ctypes.windll.kernel32.SetConsoleCursorInfo(
|
ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
|
||||||
handle, ctypes.byref(ci)
|
|
||||||
)
|
|
||||||
elif os.name == "posix":
|
elif os.name == "posix":
|
||||||
sys.stdout.write("\033[?25h")
|
sys.stdout.write("\033[?25h")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
@ -62,9 +62,7 @@ class ApplicationState:
|
|||||||
|
|
||||||
def __setattr__(self, name: str, value: Any) -> None:
|
def __setattr__(self, name: str, value: Any) -> None:
|
||||||
if self._init and name == "_init":
|
if self._init and name == "_init":
|
||||||
raise RuntimeError(
|
raise RuntimeError("Cannot change the value of _init after instantiation")
|
||||||
"Cannot change the value of _init after instantiation"
|
|
||||||
)
|
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
if self._init and hasattr(self, f"set_{name}"):
|
if self._init and hasattr(self, f"set_{name}"):
|
||||||
getattr(self, f"set_{name}")(value)
|
getattr(self, f"set_{name}")(value)
|
||||||
@ -105,9 +103,7 @@ class ApplicationState:
|
|||||||
|
|
||||||
if all(info.stage is ServerStage.SERVING for info in self.server_info):
|
if all(info.stage is ServerStage.SERVING for info in self.server_info):
|
||||||
return ServerStage.SERVING
|
return ServerStage.SERVING
|
||||||
elif any(
|
elif any(info.stage is ServerStage.SERVING for info in self.server_info):
|
||||||
info.stage is ServerStage.SERVING for info in self.server_info
|
|
||||||
):
|
|
||||||
return ServerStage.PARTIAL
|
return ServerStage.PARTIAL
|
||||||
|
|
||||||
return ServerStage.STOPPED
|
return ServerStage.STOPPED
|
||||||
|
@ -140,9 +140,7 @@ class ASGIApp:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
raise BadRequest(
|
raise BadRequest("Header names can only contain US-ASCII characters")
|
||||||
"Header names can only contain US-ASCII characters"
|
|
||||||
)
|
|
||||||
|
|
||||||
if scope["type"] == "http":
|
if scope["type"] == "http":
|
||||||
version = scope["http_version"]
|
version = scope["http_version"]
|
||||||
@ -151,9 +149,7 @@ class ASGIApp:
|
|||||||
version = "1.1"
|
version = "1.1"
|
||||||
method = "GET"
|
method = "GET"
|
||||||
|
|
||||||
instance.ws = instance.transport.create_websocket_connection(
|
instance.ws = instance.transport.create_websocket_connection(send, receive)
|
||||||
send, receive
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ServerError("Received unknown ASGI scope")
|
raise ServerError("Received unknown ASGI scope")
|
||||||
|
|
||||||
|
@ -24,9 +24,7 @@ class BaseSanic(
|
|||||||
):
|
):
|
||||||
__slots__ = ("name",)
|
__slots__ = ("name",)
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, name: Optional[str] = None, *args: Any, **kwargs: Any) -> None:
|
||||||
self, name: Optional[str] = None, *args: Any, **kwargs: Any
|
|
||||||
) -> None:
|
|
||||||
class_name = self.__class__.__name__
|
class_name = self.__class__.__name__
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
|
@ -128,9 +128,7 @@ class Blueprint(BaseSanic):
|
|||||||
self.host = host
|
self.host = host
|
||||||
self.strict_slashes = strict_slashes
|
self.strict_slashes = strict_slashes
|
||||||
self.url_prefix = (
|
self.url_prefix = (
|
||||||
url_prefix[:-1]
|
url_prefix[:-1] if url_prefix and url_prefix.endswith("/") else url_prefix
|
||||||
if url_prefix and url_prefix.endswith("/")
|
|
||||||
else url_prefix
|
|
||||||
)
|
)
|
||||||
self.version = version
|
self.version = version
|
||||||
self.version_prefix = version_prefix
|
self.version_prefix = version_prefix
|
||||||
@ -164,9 +162,7 @@ class Blueprint(BaseSanic):
|
|||||||
an app.
|
an app.
|
||||||
"""
|
"""
|
||||||
if not self._apps:
|
if not self._apps:
|
||||||
raise SanicException(
|
raise SanicException(f"{self} has not yet been registered to an app")
|
||||||
f"{self} has not yet been registered to an app"
|
|
||||||
)
|
|
||||||
return self._apps
|
return self._apps
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -345,9 +341,7 @@ class Blueprint(BaseSanic):
|
|||||||
opt_strict_slashes = options.get("strict_slashes", None)
|
opt_strict_slashes = options.get("strict_slashes", None)
|
||||||
opt_version_prefix = options.get("version_prefix", self.version_prefix)
|
opt_version_prefix = options.get("version_prefix", self.version_prefix)
|
||||||
opt_name_prefix = options.get("name_prefix", None)
|
opt_name_prefix = options.get("name_prefix", None)
|
||||||
error_format = options.get(
|
error_format = options.get("error_format", app.config.FALLBACK_ERROR_FORMAT)
|
||||||
"error_format", app.config.FALLBACK_ERROR_FORMAT
|
|
||||||
)
|
|
||||||
|
|
||||||
routes = []
|
routes = []
|
||||||
middleware = []
|
middleware = []
|
||||||
@ -373,9 +367,7 @@ class Blueprint(BaseSanic):
|
|||||||
version_prefix = prefix
|
version_prefix = prefix
|
||||||
break
|
break
|
||||||
|
|
||||||
version = self._extract_value(
|
version = self._extract_value(future.version, opt_version, self.version)
|
||||||
future.version, opt_version, self.version
|
|
||||||
)
|
|
||||||
strict_slashes = self._extract_value(
|
strict_slashes = self._extract_value(
|
||||||
future.strict_slashes, opt_strict_slashes, self.strict_slashes
|
future.strict_slashes, opt_strict_slashes, self.strict_slashes
|
||||||
)
|
)
|
||||||
@ -411,22 +403,16 @@ class Blueprint(BaseSanic):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
registered.add(apply_route)
|
registered.add(apply_route)
|
||||||
route = app._apply_route(
|
route = app._apply_route(apply_route, overwrite=self._allow_route_overwrite)
|
||||||
apply_route, overwrite=self._allow_route_overwrite
|
|
||||||
)
|
|
||||||
|
|
||||||
# If it is a copied BP, then make sure all of the names of routes
|
# If it is a copied BP, then make sure all of the names of routes
|
||||||
# matchup with the new BP name
|
# matchup with the new BP name
|
||||||
if self.copied_from:
|
if self.copied_from:
|
||||||
for r in route:
|
for r in route:
|
||||||
r.name = r.name.replace(self.copied_from, self.name)
|
r.name = r.name.replace(self.copied_from, self.name)
|
||||||
r.extra.ident = r.extra.ident.replace(
|
r.extra.ident = r.extra.ident.replace(self.copied_from, self.name)
|
||||||
self.copied_from, self.name
|
|
||||||
)
|
|
||||||
|
|
||||||
operation = (
|
operation = routes.extend if isinstance(route, list) else routes.append
|
||||||
routes.extend if isinstance(route, list) else routes.append
|
|
||||||
)
|
|
||||||
operation(route)
|
operation(route)
|
||||||
|
|
||||||
# Static Files
|
# Static Files
|
||||||
@ -504,9 +490,7 @@ class Blueprint(BaseSanic):
|
|||||||
condition = kwargs.pop("condition", {})
|
condition = kwargs.pop("condition", {})
|
||||||
condition.update({"__blueprint__": self.name})
|
condition.update({"__blueprint__": self.name})
|
||||||
kwargs["condition"] = condition
|
kwargs["condition"] = condition
|
||||||
await asyncio.gather(
|
await asyncio.gather(*[app.dispatch(*args, **kwargs) for app in self.apps])
|
||||||
*[app.dispatch(*args, **kwargs) for app in self.apps]
|
|
||||||
)
|
|
||||||
|
|
||||||
def event(self, event: str, timeout: int | float | None = None):
|
def event(self, event: str, timeout: int | float | None = None):
|
||||||
"""Wait for a signal event to be dispatched.
|
"""Wait for a signal event to be dispatched.
|
||||||
@ -747,9 +731,7 @@ class BlueprintGroup(bpg_base):
|
|||||||
def __getitem__(self, item: slice) -> MutableSequence[Blueprint]:
|
def __getitem__(self, item: slice) -> MutableSequence[Blueprint]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def __getitem__(
|
def __getitem__(self, item: int | slice) -> Blueprint | MutableSequence[Blueprint]:
|
||||||
self, item: int | slice
|
|
||||||
) -> Blueprint | MutableSequence[Blueprint]:
|
|
||||||
"""Get the Blueprint object at the specified index.
|
"""Get the Blueprint object at the specified index.
|
||||||
|
|
||||||
This method returns a blueprint inside the group specified by
|
This method returns a blueprint inside the group specified by
|
||||||
|
@ -56,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._positionals.title = "Required\n========\n Positional"
|
||||||
self.parser._optionals.title = "Optional\n========\n General"
|
self.parser._optionals.title = "Optional\n========\n General"
|
||||||
self.main_process = (
|
self.main_process = os.environ.get("SANIC_RELOADER_PROCESS", "") != "true"
|
||||||
os.environ.get("SANIC_RELOADER_PROCESS", "") != "true"
|
|
||||||
)
|
|
||||||
self.args: Namespace = Namespace()
|
self.args: Namespace = Namespace()
|
||||||
self.groups: List[Group] = []
|
self.groups: List[Group] = []
|
||||||
self.inspecting = False
|
self.inspecting = False
|
||||||
@ -126,11 +124,7 @@ Or, a path to a directory to run as a simple HTTP server:
|
|||||||
key = key.lstrip("-")
|
key = key.lstrip("-")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
value = False if arg.startswith("--no-") else True
|
value = False if arg.startswith("--no-") else True
|
||||||
key = (
|
key = arg.replace("--no-", "").lstrip("-").replace("-", "_")
|
||||||
arg.replace("--no-", "")
|
|
||||||
.lstrip("-")
|
|
||||||
.replace("-", "_")
|
|
||||||
)
|
|
||||||
setattr(self.args, key, value)
|
setattr(self.args, key, value)
|
||||||
|
|
||||||
kwargs = {**self.args.__dict__}
|
kwargs = {**self.args.__dict__}
|
||||||
@ -180,8 +174,7 @@ Or, a path to a directory to run as a simple HTTP server:
|
|||||||
" Example Module: project.sanic_server.app"
|
" Example Module: project.sanic_server.app"
|
||||||
)
|
)
|
||||||
error_logger.error(
|
error_logger.error(
|
||||||
"\nThe error below might have caused the above one:\n"
|
"\nThe error below might have caused the above one:\n" f"{e.msg}"
|
||||||
f"{e.msg}"
|
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
|
@ -244,10 +244,7 @@ class DevelopmentGroup(Group):
|
|||||||
"--auto-reload",
|
"--auto-reload",
|
||||||
dest="auto_reload",
|
dest="auto_reload",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help=(
|
help=("Watch source directory for file changes and reload on " "changes"),
|
||||||
"Watch source directory for file changes and reload on "
|
|
||||||
"changes"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.container.add_argument(
|
self.container.add_argument(
|
||||||
"-R",
|
"-R",
|
||||||
|
@ -45,11 +45,7 @@ class InspectorClient:
|
|||||||
return
|
return
|
||||||
result = self.request(action, **kwargs).get("result")
|
result = self.request(action, **kwargs).get("result")
|
||||||
if result:
|
if result:
|
||||||
out = (
|
out = dumps(result) if isinstance(result, (list, dict)) else str(result)
|
||||||
dumps(result)
|
|
||||||
if isinstance(result, (list, dict))
|
|
||||||
else str(result)
|
|
||||||
)
|
|
||||||
sys.stdout.write(out + "\n")
|
sys.stdout.write(out + "\n")
|
||||||
|
|
||||||
def info(self) -> None:
|
def info(self) -> None:
|
||||||
|
@ -12,9 +12,7 @@ from multidict import CIMultiDict # type: ignore
|
|||||||
from sanic.helpers import Default
|
from sanic.helpers import Default
|
||||||
from sanic.log import error_logger
|
from sanic.log import error_logger
|
||||||
|
|
||||||
StartMethod = Union[
|
StartMethod = Union[Default, Literal["fork"], Literal["forkserver"], Literal["spawn"]]
|
||||||
Default, Literal["fork"], Literal["forkserver"], Literal["spawn"]
|
|
||||||
]
|
|
||||||
|
|
||||||
OS_IS_WINDOWS = os.name == "nt"
|
OS_IS_WINDOWS = os.name == "nt"
|
||||||
PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy"
|
PYPY_IMPLEMENTATION = platform.python_implementation() == "PyPy"
|
||||||
|
@ -227,9 +227,7 @@ class Config(dict, metaclass=DescriptorMeta):
|
|||||||
if attr == "LOCAL_CERT_CREATOR" and not isinstance(
|
if attr == "LOCAL_CERT_CREATOR" and not isinstance(
|
||||||
self.LOCAL_CERT_CREATOR, LocalCertCreator
|
self.LOCAL_CERT_CREATOR, LocalCertCreator
|
||||||
):
|
):
|
||||||
self.LOCAL_CERT_CREATOR = LocalCertCreator[
|
self.LOCAL_CERT_CREATOR = LocalCertCreator[self.LOCAL_CERT_CREATOR.upper()]
|
||||||
self.LOCAL_CERT_CREATOR.upper()
|
|
||||||
]
|
|
||||||
elif attr == "DEPRECATION_FILTER":
|
elif attr == "DEPRECATION_FILTER":
|
||||||
self._configure_warnings()
|
self._configure_warnings()
|
||||||
|
|
||||||
|
@ -147,9 +147,7 @@ class CookieRequestParameters(RequestParameters):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return super().get(name, default)
|
return super().get(name, default)
|
||||||
|
|
||||||
def getlist(
|
def getlist(self, name: str, default: Optional[Any] = None) -> Optional[Any]:
|
||||||
self, name: str, default: Optional[Any] = None
|
|
||||||
) -> Optional[Any]:
|
|
||||||
try:
|
try:
|
||||||
return self._get_prefixed_cookie(name)
|
return self._get_prefixed_cookie(name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -496,9 +496,7 @@ class Cookie(dict):
|
|||||||
"Cannot set host_prefix on a cookie without secure=True"
|
"Cannot set host_prefix on a cookie without secure=True"
|
||||||
)
|
)
|
||||||
if path != "/":
|
if path != "/":
|
||||||
raise ServerError(
|
raise ServerError("Cannot set host_prefix on a cookie unless path='/'")
|
||||||
"Cannot set host_prefix on a cookie unless path='/'"
|
|
||||||
)
|
|
||||||
if domain:
|
if domain:
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
"Cannot set host_prefix on a cookie with a defined domain"
|
"Cannot set host_prefix on a cookie with a defined domain"
|
||||||
@ -600,9 +598,7 @@ class Cookie(dict):
|
|||||||
"""Format as a Set-Cookie header value."""
|
"""Format as a Set-Cookie header value."""
|
||||||
output = [f"{self.key}={_quote(self.value)}"]
|
output = [f"{self.key}={_quote(self.value)}"]
|
||||||
key_index = list(self._keys)
|
key_index = list(self._keys)
|
||||||
for key, value in sorted(
|
for key, value in sorted(self.items(), key=lambda x: key_index.index(x[0])):
|
||||||
self.items(), key=lambda x: key_index.index(x[0])
|
|
||||||
):
|
|
||||||
if value is not None and value is not False:
|
if value is not None and value is not False:
|
||||||
if key == "max-age":
|
if key == "max-age":
|
||||||
try:
|
try:
|
||||||
|
@ -190,8 +190,7 @@ class TextRenderer(BaseRenderer):
|
|||||||
lines += [
|
lines += [
|
||||||
f"{self.exception.__class__.__name__}: {self.exception} while "
|
f"{self.exception.__class__.__name__}: {self.exception} while "
|
||||||
f"handling path {self.request.path}",
|
f"handling path {self.request.path}",
|
||||||
f"Traceback of {self.request.app.name} "
|
f"Traceback of {self.request.app.name} " "(most recent call last):\n",
|
||||||
"(most recent call last):\n",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
while exc_value:
|
while exc_value:
|
||||||
@ -388,9 +387,7 @@ def guess_mime(req: Request, fallback: str) -> str:
|
|||||||
if m:
|
if m:
|
||||||
format = CONFIG_BY_MIME[m.mime]
|
format = CONFIG_BY_MIME[m.mime]
|
||||||
source = formats[format]
|
source = formats[format]
|
||||||
logger.debug(
|
logger.debug(f"The client accepts {m.header}, using '{format}' from {source}")
|
||||||
f"The client accepts {m.header}, using '{format}' from {source}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.debug(f"No format found, the client accepts {req.accept!r}")
|
logger.debug(f"No format found, the client accepts {req.accept!r}")
|
||||||
return m.mime
|
return m.mime
|
||||||
|
@ -69,9 +69,7 @@ class SanicException(Exception):
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.context = context
|
self.context = context
|
||||||
self.extra = extra
|
self.extra = extra
|
||||||
status_code = status_code or getattr(
|
status_code = status_code or getattr(self.__class__, "status_code", None)
|
||||||
self.__class__, "status_code", None
|
|
||||||
)
|
|
||||||
quiet = quiet or getattr(self.__class__, "quiet", None)
|
quiet = quiet or getattr(self.__class__, "quiet", None)
|
||||||
headers = headers or getattr(self.__class__, "headers", {})
|
headers = headers or getattr(self.__class__, "headers", {})
|
||||||
if message is None:
|
if message is None:
|
||||||
|
@ -36,20 +36,14 @@ class ContentRangeHandler(Range):
|
|||||||
try:
|
try:
|
||||||
self.start = int(start_b) if start_b else None
|
self.start = int(start_b) if start_b else None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RangeNotSatisfiable(
|
raise RangeNotSatisfiable(f"'{start_b}' is invalid for Content Range", self)
|
||||||
f"'{start_b}' is invalid for Content Range", self
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
self.end = int(end_b) if end_b else None
|
self.end = int(end_b) if end_b else None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RangeNotSatisfiable(
|
raise RangeNotSatisfiable(f"'{end_b}' is invalid for Content Range", self)
|
||||||
f"'{end_b}' is invalid for Content Range", self
|
|
||||||
)
|
|
||||||
if self.end is None:
|
if self.end is None:
|
||||||
if self.start is None:
|
if self.start is None:
|
||||||
raise RangeNotSatisfiable(
|
raise RangeNotSatisfiable("Invalid for Content Range parameters", self)
|
||||||
"Invalid for Content Range parameters", self
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# this case represents `Content-Range: bytes 5-`
|
# this case represents `Content-Range: bytes 5-`
|
||||||
self.end = self.total - 1
|
self.end = self.total - 1
|
||||||
@ -59,13 +53,9 @@ class ContentRangeHandler(Range):
|
|||||||
self.start = self.total - self.end
|
self.start = self.total - self.end
|
||||||
self.end = self.total - 1
|
self.end = self.total - 1
|
||||||
if self.start >= self.end:
|
if self.start >= self.end:
|
||||||
raise RangeNotSatisfiable(
|
raise RangeNotSatisfiable("Invalid for Content Range parameters", self)
|
||||||
"Invalid for Content Range parameters", self
|
|
||||||
)
|
|
||||||
self.size = self.end - self.start + 1
|
self.size = self.end - self.start + 1
|
||||||
self.headers = {
|
self.headers = {"Content-Range": f"bytes {self.start}-{self.end}/{self.total}"}
|
||||||
"Content-Range": f"bytes {self.start}-{self.end}/{self.total}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return hasattr(self, "size") and self.size > 0
|
return hasattr(self, "size") and self.size > 0
|
||||||
|
@ -60,9 +60,7 @@ class DirectoryHandler:
|
|||||||
return await file(index_file)
|
return await file(index_file)
|
||||||
|
|
||||||
if self.directory_view:
|
if self.directory_view:
|
||||||
return self._index(
|
return self._index(self.directory / current, path, request.app.debug)
|
||||||
self.directory / current, path, request.app.debug
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.index:
|
if self.index:
|
||||||
raise NotFound("File not found")
|
raise NotFound("File not found")
|
||||||
@ -72,9 +70,7 @@ class DirectoryHandler:
|
|||||||
def _index(self, location: Path, path: str, debug: bool):
|
def _index(self, location: Path, path: str, debug: bool):
|
||||||
# Remove empty path elements, append slash
|
# Remove empty path elements, append slash
|
||||||
if "//" in path or not path.endswith("/"):
|
if "//" in path or not path.endswith("/"):
|
||||||
return redirect(
|
return redirect("/" + "".join([f"{p}/" for p in path.split("/") if p]))
|
||||||
"/" + "".join([f"{p}/" for p in path.split("/") if p])
|
|
||||||
)
|
|
||||||
|
|
||||||
# Render file browser
|
# Render file browser
|
||||||
page = DirectoryPage(self._iter_files(location), path, debug)
|
page = DirectoryPage(self._iter_files(location), path, debug)
|
||||||
@ -83,9 +79,7 @@ class DirectoryHandler:
|
|||||||
def _prepare_file(self, path: Path) -> dict[str, int | str]:
|
def _prepare_file(self, path: Path) -> dict[str, int | str]:
|
||||||
stat = path.stat()
|
stat = path.stat()
|
||||||
modified = (
|
modified = (
|
||||||
datetime.fromtimestamp(stat.st_mtime)
|
datetime.fromtimestamp(stat.st_mtime).isoformat()[:19].replace("T", " ")
|
||||||
.isoformat()[:19]
|
|
||||||
.replace("T", " ")
|
|
||||||
)
|
)
|
||||||
is_dir = S_ISDIR(stat.st_mode)
|
is_dir = S_ISDIR(stat.st_mode)
|
||||||
icon = "📁" if is_dir else "📄"
|
icon = "📁" if is_dir else "📄"
|
||||||
|
@ -96,9 +96,7 @@ class ErrorHandler:
|
|||||||
exception_key = (ancestor, name)
|
exception_key = (ancestor, name)
|
||||||
if exception_key in self.cached_handlers:
|
if exception_key in self.cached_handlers:
|
||||||
handler = self.cached_handlers[exception_key]
|
handler = self.cached_handlers[exception_key]
|
||||||
self.cached_handlers[
|
self.cached_handlers[(exception_class, route_name)] = handler
|
||||||
(exception_class, route_name)
|
|
||||||
] = handler
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
if ancestor is BaseException:
|
if ancestor is BaseException:
|
||||||
@ -196,6 +194,4 @@ class ErrorHandler:
|
|||||||
except AttributeError: # no cov
|
except AttributeError: # no cov
|
||||||
url = "unknown"
|
url = "unknown"
|
||||||
|
|
||||||
error_logger.exception(
|
error_logger.exception("Exception occurred while handling uri: %s", url)
|
||||||
"Exception occurred while handling uri: %s", url
|
|
||||||
)
|
|
||||||
|
@ -19,9 +19,7 @@ _token, _quoted = r"([\w!#$%&'*+\-.^_`|~]+)", r'"([^"]*)"'
|
|||||||
_param = re.compile(rf";\s*{_token}=(?:{_token}|{_quoted})", re.ASCII)
|
_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 = "(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}"
|
||||||
_ipv6_re = re.compile(_ipv6)
|
_ipv6_re = re.compile(_ipv6)
|
||||||
_host_re = re.compile(
|
_host_re = re.compile(r"((?:\[" + _ipv6 + r"\])|[a-zA-Z0-9.\-]{1,253})(?::(\d{1,5}))?")
|
||||||
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
|
# 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,
|
# curl all have different escaping, that we try to handle as well as possible,
|
||||||
@ -122,9 +120,7 @@ class MediaType:
|
|||||||
or mt.subtype == "*"
|
or mt.subtype == "*"
|
||||||
)
|
)
|
||||||
# Type match
|
# Type match
|
||||||
and (
|
and (self.type == mt.type or self.type == "*" or mt.type == "*")
|
||||||
self.type == mt.type or self.type == "*" or mt.type == "*"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
@ -312,9 +308,7 @@ def parse_accept(accept: str | None) -> AcceptList:
|
|||||||
accept = "*/*" # No header means that all types are accepted
|
accept = "*/*" # No header means that all types are accepted
|
||||||
try:
|
try:
|
||||||
a = [
|
a = [
|
||||||
mt
|
mt for mt in [MediaType._parse(mtype) for mtype in accept.split(",")] if mt
|
||||||
for mt in [MediaType._parse(mtype) for mtype in accept.split(",")]
|
|
||||||
if mt
|
|
||||||
]
|
]
|
||||||
if not a:
|
if not a:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
@ -411,11 +405,7 @@ def parse_xforwarded(headers, config) -> Options | None:
|
|||||||
# Combine, split and filter multiple headers' entries
|
# Combine, split and filter multiple headers' entries
|
||||||
forwarded_for = headers.getall(config.FORWARDED_FOR_HEADER)
|
forwarded_for = headers.getall(config.FORWARDED_FOR_HEADER)
|
||||||
proxies = [
|
proxies = [
|
||||||
p
|
p for p in (p.strip() for h in forwarded_for for p in h.split(",")) if p
|
||||||
for p in (
|
|
||||||
p.strip() for h in forwarded_for for p in h.split(",")
|
|
||||||
)
|
|
||||||
if p
|
|
||||||
]
|
]
|
||||||
addr = proxies[-proxies_count]
|
addr = proxies[-proxies_count]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
|
@ -361,26 +361,20 @@ class Http(Stream, metaclass=TouchUpMeta):
|
|||||||
self.response_func = None
|
self.response_func = None
|
||||||
self.stage = Stage.IDLE
|
self.stage = Stage.IDLE
|
||||||
|
|
||||||
async def http1_response_chunked(
|
async def http1_response_chunked(self, data: bytes, end_stream: bool) -> None:
|
||||||
self, data: bytes, end_stream: bool
|
|
||||||
) -> None:
|
|
||||||
"""Format a part of response body in chunked encoding."""
|
"""Format a part of response body in chunked encoding."""
|
||||||
# Chunked encoding
|
# Chunked encoding
|
||||||
size = len(data)
|
size = len(data)
|
||||||
if end_stream:
|
if end_stream:
|
||||||
await self._send(
|
await self._send(
|
||||||
b"%x\r\n%b\r\n0\r\n\r\n" % (size, data)
|
b"%x\r\n%b\r\n0\r\n\r\n" % (size, data) if size else b"0\r\n\r\n"
|
||||||
if size
|
|
||||||
else b"0\r\n\r\n"
|
|
||||||
)
|
)
|
||||||
self.response_func = None
|
self.response_func = None
|
||||||
self.stage = Stage.IDLE
|
self.stage = Stage.IDLE
|
||||||
elif size:
|
elif size:
|
||||||
await self._send(b"%x\r\n%b\r\n" % (size, data))
|
await self._send(b"%x\r\n%b\r\n" % (size, data))
|
||||||
|
|
||||||
async def http1_response_normal(
|
async def http1_response_normal(self, data: bytes, end_stream: bool) -> None:
|
||||||
self, data: bytes, end_stream: bool
|
|
||||||
) -> None:
|
|
||||||
"""Format / keep track of non-chunked response."""
|
"""Format / keep track of non-chunked response."""
|
||||||
bytes_left = self.response_bytes_left - len(data)
|
bytes_left = self.response_bytes_left - len(data)
|
||||||
if bytes_left <= 0:
|
if bytes_left <= 0:
|
||||||
@ -418,9 +412,7 @@ class Http(Stream, metaclass=TouchUpMeta):
|
|||||||
exception, (ServiceUnavailable, RequestCancelled)
|
exception, (ServiceUnavailable, RequestCancelled)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await app.handle_exception(
|
await app.handle_exception(self.request, exception, request_middleware)
|
||||||
self.request, exception, request_middleware
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await app.handle_exception(self.request, e, False)
|
await app.handle_exception(self.request, e, False)
|
||||||
|
|
||||||
|
@ -65,10 +65,7 @@ class HTTP3Transport(TransportProtocol):
|
|||||||
return self._protocol
|
return self._protocol
|
||||||
|
|
||||||
def get_extra_info(self, info: str, default: Any = None) -> Any:
|
def get_extra_info(self, info: str, default: Any = None) -> Any:
|
||||||
if (
|
if info in ("socket", "sockname", "peername") and self._protocol._transport:
|
||||||
info in ("socket", "sockname", "peername")
|
|
||||||
and self._protocol._transport
|
|
||||||
):
|
|
||||||
return self._protocol._transport.get_extra_info(info, default)
|
return self._protocol._transport.get_extra_info(info, default)
|
||||||
elif info == "network_paths":
|
elif info == "network_paths":
|
||||||
return self._protocol._quic._network_paths
|
return self._protocol._quic._network_paths
|
||||||
@ -114,8 +111,7 @@ class HTTPReceiver(Receiver, Stream):
|
|||||||
|
|
||||||
if exception:
|
if exception:
|
||||||
logger.info( # no cov
|
logger.info( # no cov
|
||||||
f"{Colors.BLUE}[exception]: "
|
f"{Colors.BLUE}[exception]: " f"{Colors.RED}{exception}{Colors.END}",
|
||||||
f"{Colors.RED}{exception}{Colors.END}",
|
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
extra={"verbosity": 1},
|
extra={"verbosity": 1},
|
||||||
)
|
)
|
||||||
@ -140,17 +136,13 @@ class HTTPReceiver(Receiver, Stream):
|
|||||||
|
|
||||||
await app.handle_exception(self.request, exception)
|
await app.handle_exception(self.request, exception)
|
||||||
|
|
||||||
def _prepare_headers(
|
def _prepare_headers(self, response: BaseHTTPResponse) -> list[tuple[bytes, bytes]]:
|
||||||
self, response: BaseHTTPResponse
|
|
||||||
) -> list[tuple[bytes, bytes]]:
|
|
||||||
size = len(response.body) if response.body else 0
|
size = len(response.body) if response.body else 0
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
status = response.status
|
status = response.status
|
||||||
|
|
||||||
if not has_message_body(status) and (
|
if not has_message_body(status) and (
|
||||||
size
|
size or "content-length" in headers or "transfer-encoding" in headers
|
||||||
or "content-length" in headers
|
|
||||||
or "transfer-encoding" in headers
|
|
||||||
):
|
):
|
||||||
headers.pop("content-length", None)
|
headers.pop("content-length", None)
|
||||||
headers.pop("transfer-encoding", None)
|
headers.pop("transfer-encoding", None)
|
||||||
@ -243,11 +235,7 @@ class HTTPReceiver(Receiver, Stream):
|
|||||||
):
|
):
|
||||||
size = len(data)
|
size = len(data)
|
||||||
if end_stream:
|
if end_stream:
|
||||||
data = (
|
data = 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"
|
|
||||||
)
|
|
||||||
elif size:
|
elif size:
|
||||||
data = b"%x\r\n%b\r\n" % (size, data)
|
data = b"%x\r\n%b\r\n" % (size, data)
|
||||||
|
|
||||||
@ -325,10 +313,7 @@ class Http3:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_or_make_receiver(self, event: H3Event) -> tuple[Receiver, bool]:
|
def get_or_make_receiver(self, event: H3Event) -> tuple[Receiver, bool]:
|
||||||
if (
|
if isinstance(event, HeadersReceived) and event.stream_id not in self.receivers:
|
||||||
isinstance(event, HeadersReceived)
|
|
||||||
and event.stream_id not in self.receivers
|
|
||||||
):
|
|
||||||
request = self._make_request(event)
|
request = self._make_request(event)
|
||||||
receiver = HTTPReceiver(self.transmit, self.protocol, request)
|
receiver = HTTPReceiver(self.transmit, self.protocol, request)
|
||||||
request.stream = receiver
|
request.stream = receiver
|
||||||
@ -351,9 +336,7 @@ class Http3:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
raise BadRequest(
|
raise BadRequest("Header names may only contain US-ASCII characters.")
|
||||||
"Header names may only contain US-ASCII characters."
|
|
||||||
)
|
|
||||||
method = headers[":method"]
|
method = headers[":method"]
|
||||||
path = headers[":path"]
|
path = headers[":path"]
|
||||||
scheme = headers.pop(":scheme", "")
|
scheme = headers.pop(":scheme", "")
|
||||||
@ -422,8 +405,6 @@ def get_config(app: Sanic, ssl: SanicSSLContext | CertSelector | SSLContext):
|
|||||||
)
|
)
|
||||||
password = app.config.TLS_CERT_PASSWORD or None
|
password = app.config.TLS_CERT_PASSWORD or None
|
||||||
|
|
||||||
config.load_cert_chain(
|
config.load_cert_chain(ssl.sanic["cert"], ssl.sanic["key"], password=password)
|
||||||
ssl.sanic["cert"], ssl.sanic["key"], password=password
|
|
||||||
)
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
@ -20,7 +20,5 @@ class Stream:
|
|||||||
__touchup__: tuple[str, ...] = ()
|
__touchup__: tuple[str, ...] = ()
|
||||||
__slots__ = ("request_max_size",)
|
__slots__ = ("request_max_size",)
|
||||||
|
|
||||||
def respond(
|
def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse: # no cov
|
||||||
self, response: BaseHTTPResponse
|
|
||||||
) -> BaseHTTPResponse: # no cov
|
|
||||||
raise NotImplementedError("Not implemented")
|
raise NotImplementedError("Not implemented")
|
||||||
|
@ -69,13 +69,9 @@ def load_cert_dir(p: str) -> ssl.SSLContext:
|
|||||||
keyfile = os.path.join(p, "privkey.pem")
|
keyfile = os.path.join(p, "privkey.pem")
|
||||||
certfile = os.path.join(p, "fullchain.pem")
|
certfile = os.path.join(p, "fullchain.pem")
|
||||||
if not os.access(keyfile, os.R_OK):
|
if not os.access(keyfile, os.R_OK):
|
||||||
raise ValueError(
|
raise ValueError(f"Certificate not found or permission denied {keyfile}")
|
||||||
f"Certificate not found or permission denied {keyfile}"
|
|
||||||
)
|
|
||||||
if not os.access(certfile, os.R_OK):
|
if not os.access(certfile, os.R_OK):
|
||||||
raise ValueError(
|
raise ValueError(f"Certificate not found or permission denied {certfile}")
|
||||||
f"Certificate not found or permission denied {certfile}"
|
|
||||||
)
|
|
||||||
return CertSimple(certfile, keyfile)
|
return CertSimple(certfile, keyfile)
|
||||||
|
|
||||||
|
|
||||||
@ -87,9 +83,7 @@ def find_cert(self: CertSelector, server_name: str):
|
|||||||
if not server_name:
|
if not server_name:
|
||||||
if self.sanic_fallback:
|
if self.sanic_fallback:
|
||||||
return self.sanic_fallback
|
return self.sanic_fallback
|
||||||
raise ValueError(
|
raise ValueError("The client provided no SNI to match for certificate.")
|
||||||
"The client provided no SNI to match for certificate."
|
|
||||||
)
|
|
||||||
for ctx in self.sanic_select:
|
for ctx in self.sanic_select:
|
||||||
if match_hostname(ctx, server_name):
|
if match_hostname(ctx, server_name):
|
||||||
return ctx
|
return ctx
|
||||||
@ -162,9 +156,7 @@ class CertSimple(SanicSSLContext):
|
|||||||
if "names" not in kw:
|
if "names" not in kw:
|
||||||
cert = ssl._ssl._test_decode_cert(certfile) # type: ignore
|
cert = ssl._ssl._test_decode_cert(certfile) # type: ignore
|
||||||
kw["names"] = [
|
kw["names"] = [
|
||||||
name
|
name for t, name in cert["subjectAltName"] if t in ["DNS", "IP Address"]
|
||||||
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}
|
subject = {k: v for item in cert["subject"] for k, v in item}
|
||||||
self = create_context(certfile, keyfile, password)
|
self = create_context(certfile, keyfile, password)
|
||||||
@ -201,7 +193,5 @@ class CertSelector(ssl.SSLContext):
|
|||||||
if i == 0:
|
if i == 0:
|
||||||
self.sanic_fallback = ctx
|
self.sanic_fallback = ctx
|
||||||
if not all_names:
|
if not all_names:
|
||||||
raise ValueError(
|
raise ValueError("No certificates with SubjectAlternativeNames found.")
|
||||||
"No certificates with SubjectAlternativeNames found."
|
|
||||||
)
|
|
||||||
logger.info(f"Certificate vhosts: {', '.join(all_names)}")
|
logger.info(f"Certificate vhosts: {', '.join(all_names)}")
|
||||||
|
@ -92,16 +92,8 @@ class CertCreator(ABC):
|
|||||||
if isinstance(self.key, Default) or isinstance(self.cert, Default):
|
if isinstance(self.key, Default) or isinstance(self.cert, Default):
|
||||||
self.tmpdir = Path(mkdtemp())
|
self.tmpdir = Path(mkdtemp())
|
||||||
|
|
||||||
key = (
|
key = DEFAULT_LOCAL_TLS_KEY if isinstance(self.key, Default) else self.key
|
||||||
DEFAULT_LOCAL_TLS_KEY
|
cert = DEFAULT_LOCAL_TLS_CERT if isinstance(self.cert, Default) else self.cert
|
||||||
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.key_path = _make_path(key, self.tmpdir)
|
||||||
self.cert_path = _make_path(cert, self.tmpdir)
|
self.cert_path = _make_path(cert, self.tmpdir)
|
||||||
@ -124,9 +116,7 @@ class CertCreator(ABC):
|
|||||||
) -> CertCreator:
|
) -> CertCreator:
|
||||||
creator: CertCreator | None = None
|
creator: CertCreator | None = None
|
||||||
|
|
||||||
cert_creator_options: tuple[
|
cert_creator_options: tuple[tuple[type[CertCreator], LocalCertCreator], ...] = (
|
||||||
tuple[type[CertCreator], LocalCertCreator], ...
|
|
||||||
] = (
|
|
||||||
(MkcertCreator, LocalCertCreator.MKCERT),
|
(MkcertCreator, LocalCertCreator.MKCERT),
|
||||||
(TrustmeCreator, LocalCertCreator.TRUSTME),
|
(TrustmeCreator, LocalCertCreator.TRUSTME),
|
||||||
)
|
)
|
||||||
|
@ -79,9 +79,7 @@ class ExceptionMixin(metaclass=SanicMeta):
|
|||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def all_exceptions(
|
def all_exceptions(self, handler: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
self, handler: Callable[..., Any]
|
|
||||||
) -> Callable[..., Any]:
|
|
||||||
"""Enables the process of creating a global exception handler as a convenience.
|
"""Enables the process of creating a global exception handler as a convenience.
|
||||||
|
|
||||||
This following two examples are equivalent:
|
This following two examples are equivalent:
|
||||||
|
@ -120,16 +120,12 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
|
|
||||||
if callable(listener_or_event):
|
if callable(listener_or_event):
|
||||||
if event_or_none is None:
|
if event_or_none is None:
|
||||||
raise BadRequest(
|
raise BadRequest("Invalid event registration: Missing event name.")
|
||||||
"Invalid event registration: Missing event name."
|
|
||||||
)
|
|
||||||
return register_listener(listener_or_event, event_or_none)
|
return register_listener(listener_or_event, event_or_none)
|
||||||
else:
|
else:
|
||||||
return partial(register_listener, event=listener_or_event)
|
return partial(register_listener, event=listener_or_event)
|
||||||
|
|
||||||
def main_process_start(
|
def main_process_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the main_process_start event.
|
"""Decorator for registering a listener for the main_process_start event.
|
||||||
|
|
||||||
This event is fired only on the main process and **NOT** on any
|
This event is fired only on the main process and **NOT** on any
|
||||||
@ -151,9 +147,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "main_process_start")
|
return self.listener(listener, "main_process_start")
|
||||||
|
|
||||||
def main_process_ready(
|
def main_process_ready(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the main_process_ready event.
|
"""Decorator for registering a listener for the main_process_ready event.
|
||||||
|
|
||||||
This event is fired only on the main process and **NOT** on any
|
This event is fired only on the main process and **NOT** on any
|
||||||
@ -176,9 +170,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "main_process_ready")
|
return self.listener(listener, "main_process_ready")
|
||||||
|
|
||||||
def main_process_stop(
|
def main_process_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the main_process_stop event.
|
"""Decorator for registering a listener for the main_process_stop event.
|
||||||
|
|
||||||
This event is fired only on the main process and **NOT** on any
|
This event is fired only on the main process and **NOT** on any
|
||||||
@ -222,9 +214,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "reload_process_start")
|
return self.listener(listener, "reload_process_start")
|
||||||
|
|
||||||
def reload_process_stop(
|
def reload_process_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the reload_process_stop event.
|
"""Decorator for registering a listener for the reload_process_stop event.
|
||||||
|
|
||||||
This event is fired only on the reload process and **NOT** on any
|
This event is fired only on the reload process and **NOT** on any
|
||||||
@ -293,9 +283,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "after_reload_trigger")
|
return self.listener(listener, "after_reload_trigger")
|
||||||
|
|
||||||
def before_server_start(
|
def before_server_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the before_server_start event.
|
"""Decorator for registering a listener for the before_server_start event.
|
||||||
|
|
||||||
This event is fired on all worker processes. You should typically
|
This event is fired on all worker processes. You should typically
|
||||||
@ -319,9 +307,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "before_server_start")
|
return self.listener(listener, "before_server_start")
|
||||||
|
|
||||||
def after_server_start(
|
def after_server_start(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the after_server_start event.
|
"""Decorator for registering a listener for the after_server_start event.
|
||||||
|
|
||||||
This event is fired on all worker processes. You should typically
|
This event is fired on all worker processes. You should typically
|
||||||
@ -349,9 +335,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "after_server_start")
|
return self.listener(listener, "after_server_start")
|
||||||
|
|
||||||
def before_server_stop(
|
def before_server_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the before_server_stop event.
|
"""Decorator for registering a listener for the before_server_stop event.
|
||||||
|
|
||||||
This event is fired on all worker processes. This event is fired
|
This event is fired on all worker processes. This event is fired
|
||||||
@ -376,9 +360,7 @@ class ListenerMixin(metaclass=SanicMeta):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self.listener(listener, "before_server_stop")
|
return self.listener(listener, "before_server_stop")
|
||||||
|
|
||||||
def after_server_stop(
|
def after_server_stop(self, listener: ListenerType[Sanic]) -> ListenerType[Sanic]:
|
||||||
self, listener: ListenerType[Sanic]
|
|
||||||
) -> ListenerType[Sanic]:
|
|
||||||
"""Decorator for registering a listener for the after_server_stop event.
|
"""Decorator for registering a listener for the after_server_stop event.
|
||||||
|
|
||||||
This event is fired on all worker processes. This event is fired
|
This event is fired on all worker processes. This event is fired
|
||||||
|
@ -99,13 +99,9 @@ class MiddlewareMixin(metaclass=SanicMeta):
|
|||||||
|
|
||||||
# Detect which way this was called, @middleware or @middleware('AT')
|
# Detect which way this was called, @middleware or @middleware('AT')
|
||||||
if callable(middleware_or_request):
|
if callable(middleware_or_request):
|
||||||
return register_middleware(
|
return register_middleware(middleware_or_request, attach_to=attach_to)
|
||||||
middleware_or_request, attach_to=attach_to
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return partial(
|
return partial(register_middleware, attach_to=middleware_or_request)
|
||||||
register_middleware, attach_to=middleware_or_request
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_request(self, middleware=None, *, priority=0) -> MiddlewareType:
|
def on_request(self, middleware=None, *, priority=0) -> MiddlewareType:
|
||||||
"""Register a middleware to be called before a request is handled.
|
"""Register a middleware to be called before a request is handled.
|
||||||
@ -157,9 +153,7 @@ class MiddlewareMixin(metaclass=SanicMeta):
|
|||||||
if callable(middleware):
|
if callable(middleware):
|
||||||
return self.middleware(middleware, "response", priority=priority)
|
return self.middleware(middleware, "response", priority=priority)
|
||||||
else:
|
else:
|
||||||
return partial(
|
return partial(self.middleware, attach_to="response", priority=priority)
|
||||||
self.middleware, attach_to="response", priority=priority
|
|
||||||
)
|
|
||||||
|
|
||||||
def finalize_middleware(self) -> None:
|
def finalize_middleware(self) -> None:
|
||||||
"""Finalize the middleware configuration for the Sanic application.
|
"""Finalize the middleware configuration for the Sanic application.
|
||||||
|
@ -25,9 +25,7 @@ from sanic.models.futures import FutureRoute, FutureStatic
|
|||||||
from sanic.models.handler_types import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
from sanic.types import HashableDict
|
from sanic.types import HashableDict
|
||||||
|
|
||||||
RouteWrapper = Callable[
|
RouteWrapper = Callable[[RouteHandler], Union[RouteHandler, Tuple[Route, RouteHandler]]]
|
||||||
[RouteHandler], Union[RouteHandler, Tuple[Route, RouteHandler]]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class RouteMixin(BaseMixin, metaclass=SanicMeta):
|
class RouteMixin(BaseMixin, metaclass=SanicMeta):
|
||||||
@ -814,7 +812,5 @@ class RouteMixin(BaseMixin, metaclass=SanicMeta):
|
|||||||
}
|
}
|
||||||
if raw:
|
if raw:
|
||||||
unexpected_arguments = ", ".join(raw.keys())
|
unexpected_arguments = ", ".join(raw.keys())
|
||||||
raise TypeError(
|
raise TypeError(f"Unexpected keyword arguments: {unexpected_arguments}")
|
||||||
f"Unexpected keyword arguments: {unexpected_arguments}"
|
|
||||||
)
|
|
||||||
return HashableDict(ctx_kwargs)
|
return HashableDict(ctx_kwargs)
|
||||||
|
@ -92,9 +92,7 @@ class SignalMixin(metaclass=SanicMeta):
|
|||||||
...
|
...
|
||||||
|
|
||||||
handler = noop
|
handler = noop
|
||||||
self.signal(event=event, condition=condition, exclusive=exclusive)(
|
self.signal(event=event, condition=condition, exclusive=exclusive)(handler)
|
||||||
handler
|
|
||||||
)
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
def event(self, event: str):
|
def event(self, event: str):
|
||||||
|
@ -100,8 +100,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
"""
|
"""
|
||||||
if not self.asgi:
|
if not self.asgi:
|
||||||
if self.config.USE_UVLOOP is True or (
|
if self.config.USE_UVLOOP is True or (
|
||||||
isinstance(self.config.USE_UVLOOP, Default)
|
isinstance(self.config.USE_UVLOOP, Default) and not OS_IS_WINDOWS
|
||||||
and not OS_IS_WINDOWS
|
|
||||||
):
|
):
|
||||||
try_use_uvloop()
|
try_use_uvloop()
|
||||||
elif OS_IS_WINDOWS:
|
elif OS_IS_WINDOWS:
|
||||||
@ -373,8 +372,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
|
|
||||||
if single_process and (fast or (workers > 1) or auto_reload):
|
if single_process and (fast or (workers > 1) or auto_reload):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Single process cannot be run with multiple workers "
|
"Single process cannot be run with multiple workers " "or auto-reload"
|
||||||
"or auto-reload"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if register_sys_signals is False and not single_process:
|
if register_sys_signals is False and not single_process:
|
||||||
@ -393,9 +391,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
for directory in reload_dir:
|
for directory in reload_dir:
|
||||||
direc = Path(directory)
|
direc = Path(directory)
|
||||||
if not direc.is_dir():
|
if not direc.is_dir():
|
||||||
logger.warning(
|
logger.warning(f"Directory {directory} could not be located")
|
||||||
f"Directory {directory} could not be located"
|
|
||||||
)
|
|
||||||
self.state.reload_dirs.add(Path(directory))
|
self.state.reload_dirs.add(Path(directory))
|
||||||
|
|
||||||
if loop is not None:
|
if loop is not None:
|
||||||
@ -410,9 +406,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
host, port = self.get_address(host, port, version, auto_tls)
|
host, port = self.get_address(host, port, version, auto_tls)
|
||||||
|
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = (
|
protocol = WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
||||||
WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set explicitly passed configuration values
|
# Set explicitly passed configuration values
|
||||||
for attribute, value in {
|
for attribute, value in {
|
||||||
@ -448,9 +442,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
register_sys_signals=register_sys_signals,
|
register_sys_signals=register_sys_signals,
|
||||||
auto_tls=auto_tls,
|
auto_tls=auto_tls,
|
||||||
)
|
)
|
||||||
self.state.server_info.append(
|
self.state.server_info.append(ApplicationServerInfo(settings=server_settings))
|
||||||
ApplicationServerInfo(settings=server_settings)
|
|
||||||
)
|
|
||||||
|
|
||||||
# if self.config.USE_UVLOOP is True or (
|
# if self.config.USE_UVLOOP is True or (
|
||||||
# self.config.USE_UVLOOP is _default and not OS_IS_WINDOWS
|
# self.config.USE_UVLOOP is _default and not OS_IS_WINDOWS
|
||||||
@ -546,9 +538,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
host, port = host, port = self.get_address(host, port)
|
host, port = host, port = self.get_address(host, port)
|
||||||
|
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = (
|
protocol = WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
||||||
WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set explicitly passed configuration values
|
# Set explicitly passed configuration values
|
||||||
for attribute, value in {
|
for attribute, value in {
|
||||||
@ -790,10 +780,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
reload_display += ", ".join(
|
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
|
display["auto-reload"] = reload_display
|
||||||
@ -832,9 +819,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
return f"http://<{location}>"
|
return f"http://<{location}>"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_server_location(
|
def get_server_location(server_settings: dict[str, Any] | None = None) -> str:
|
||||||
server_settings: dict[str, Any] | None = None
|
|
||||||
) -> str:
|
|
||||||
"""Using the server settings, retrieve the server location.
|
"""Using the server settings, retrieve the server location.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -901,9 +886,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _get_startup_method(cls) -> str:
|
def _get_startup_method(cls) -> str:
|
||||||
return (
|
return (
|
||||||
cls.start_method
|
cls.start_method if not isinstance(cls.start_method, Default) else "spawn"
|
||||||
if not isinstance(cls.start_method, Default)
|
|
||||||
else "spawn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -984,9 +967,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
try:
|
try:
|
||||||
primary = apps[0]
|
primary = apps[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise RuntimeError(
|
raise RuntimeError("Did not find any applications.") from None
|
||||||
"Did not find any applications."
|
|
||||||
) from None
|
|
||||||
|
|
||||||
# This exists primarily for unit testing
|
# This exists primarily for unit testing
|
||||||
if not primary.state.server_info: # no cov
|
if not primary.state.server_info: # no cov
|
||||||
@ -1089,9 +1070,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
inspector = None
|
inspector = None
|
||||||
if primary.config.INSPECTOR:
|
if primary.config.INSPECTOR:
|
||||||
display, extra = primary.get_motd_data()
|
display, extra = primary.get_motd_data()
|
||||||
packages = [
|
packages = [pkg.strip() for pkg in display["packages"].split(",")]
|
||||||
pkg.strip() for pkg in display["packages"].split(",")
|
|
||||||
]
|
|
||||||
module = import_module("sanic")
|
module = import_module("sanic")
|
||||||
sanic_version = f"sanic=={module.__version__}" # type: ignore
|
sanic_version = f"sanic=={module.__version__}" # type: ignore
|
||||||
app_info = {
|
app_info = {
|
||||||
@ -1122,9 +1101,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
exit_code = 1
|
exit_code = 1
|
||||||
except BaseException:
|
except BaseException:
|
||||||
kwargs = primary_server_info.settings
|
kwargs = primary_server_info.settings
|
||||||
error_logger.exception(
|
error_logger.exception("Experienced exception while trying to serve")
|
||||||
"Experienced exception while trying to serve"
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
logger.info("Server Stopped")
|
logger.info("Server Stopped")
|
||||||
@ -1230,9 +1207,7 @@ class StartupMixin(metaclass=SanicMeta):
|
|||||||
try:
|
try:
|
||||||
worker_serve(monitor_publisher=None, **kwargs)
|
worker_serve(monitor_publisher=None, **kwargs)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
error_logger.exception(
|
error_logger.exception("Experienced exception while trying to serve")
|
||||||
"Experienced exception while trying to serve"
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
logger.info("Server Stopped")
|
logger.info("Server Stopped")
|
||||||
|
@ -205,17 +205,12 @@ class StaticHandleMixin(metaclass=SanicMeta):
|
|||||||
)
|
)
|
||||||
uri = uri.rstrip("/")
|
uri = uri.rstrip("/")
|
||||||
uri += "/<__file_uri__:path>"
|
uri += "/<__file_uri__:path>"
|
||||||
elif static.resource_type == "file" and not path.isfile(
|
elif static.resource_type == "file" and not path.isfile(file_or_directory):
|
||||||
file_or_directory
|
|
||||||
):
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Resource type improperly identified as file. "
|
"Resource type improperly identified as file. " f"'{file_or_directory}'"
|
||||||
f"'{file_or_directory}'"
|
|
||||||
)
|
)
|
||||||
elif static.resource_type != "file":
|
elif static.resource_type != "file":
|
||||||
raise ValueError(
|
raise ValueError("The resource_type should be set to 'file' or 'dir'")
|
||||||
"The resource_type should be set to 'file' or 'dir'"
|
|
||||||
)
|
|
||||||
|
|
||||||
# special prefix for static files
|
# special prefix for static files
|
||||||
# if not static.name.startswith("_static_"):
|
# if not static.name.startswith("_static_"):
|
||||||
@ -278,9 +273,7 @@ class StaticHandleMixin(metaclass=SanicMeta):
|
|||||||
response = await validate_file(request.headers, modified_since)
|
response = await validate_file(request.headers, modified_since)
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
headers["Last-Modified"] = formatdate(
|
headers["Last-Modified"] = formatdate(modified_since, usegmt=True)
|
||||||
modified_since, usegmt=True
|
|
||||||
)
|
|
||||||
_range = None
|
_range = None
|
||||||
if use_content_range:
|
if use_content_range:
|
||||||
_range = None
|
_range = None
|
||||||
|
@ -47,9 +47,7 @@ class MockProtocol: # no cov
|
|||||||
class MockTransport(TransportProtocol): # no cov
|
class MockTransport(TransportProtocol): # no cov
|
||||||
_protocol: Optional[MockProtocol]
|
_protocol: Optional[MockProtocol]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend) -> None:
|
||||||
self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend
|
|
||||||
) -> None:
|
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
self._receive = receive
|
self._receive = receive
|
||||||
self._send = send
|
self._send = send
|
||||||
@ -61,9 +59,7 @@ class MockTransport(TransportProtocol): # no cov
|
|||||||
self._protocol = MockProtocol(self, self.loop)
|
self._protocol = MockProtocol(self, self.loop)
|
||||||
return self._protocol
|
return self._protocol
|
||||||
|
|
||||||
def get_extra_info(
|
def get_extra_info(self, info: str, default=None) -> Optional[Union[str, bool]]:
|
||||||
self, info: str, default=None
|
|
||||||
) -> Optional[Union[str, bool]]:
|
|
||||||
if info == "peername":
|
if info == "peername":
|
||||||
return self.scope.get("client")
|
return self.scope.get("client")
|
||||||
elif info == "sslcontext":
|
elif info == "sslcontext":
|
||||||
|
@ -12,9 +12,7 @@ MiddlewareResponse = Union[
|
|||||||
Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]]
|
Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]]
|
||||||
]
|
]
|
||||||
RequestMiddlewareType = Callable[[Request], MiddlewareResponse]
|
RequestMiddlewareType = Callable[[Request], MiddlewareResponse]
|
||||||
ResponseMiddlewareType = Callable[
|
ResponseMiddlewareType = Callable[[Request, BaseHTTPResponse], MiddlewareResponse]
|
||||||
[Request, BaseHTTPResponse], MiddlewareResponse
|
|
||||||
]
|
|
||||||
ErrorMiddlewareType = Callable[
|
ErrorMiddlewareType = Callable[
|
||||||
[Request, BaseException], Optional[Coroutine[Any, Any, None]]
|
[Request, BaseException], Optional[Coroutine[Any, Any, None]]
|
||||||
]
|
]
|
||||||
|
@ -27,8 +27,6 @@ class CSS(ABCMeta):
|
|||||||
Page.STYLE += attrs.get("STYLE_APPEND", "")
|
Page.STYLE += attrs.get("STYLE_APPEND", "")
|
||||||
# Combine with all ancestor styles
|
# Combine with all ancestor styles
|
||||||
Page.CSS = "".join(
|
Page.CSS = "".join(
|
||||||
Class.STYLE
|
Class.STYLE for Class in reversed(Page.__mro__) if type(Class) is CSS
|
||||||
for Class in reversed(Page.__mro__)
|
|
||||||
if type(Class) is CSS
|
|
||||||
)
|
)
|
||||||
return Page
|
return Page
|
||||||
|
@ -19,9 +19,7 @@ class DirectoryPage(BasePage): # no cov
|
|||||||
|
|
||||||
TITLE = "Directory Viewer"
|
TITLE = "Directory Viewer"
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, files: Iterable[FileInfo], url: str, debug: bool) -> None:
|
||||||
self, files: Iterable[FileInfo], url: str, debug: bool
|
|
||||||
) -> None:
|
|
||||||
super().__init__(debug)
|
super().__init__(debug)
|
||||||
self.files = files
|
self.files = files
|
||||||
self.url = url
|
self.url = url
|
||||||
|
@ -37,9 +37,7 @@ class ErrorPage(BasePage):
|
|||||||
if name.islower():
|
if name.islower():
|
||||||
name = name.title()
|
name = name.title()
|
||||||
self.TITLE = f"Application {name} cannot handle your request"
|
self.TITLE = f"Application {name} cannot handle your request"
|
||||||
self.HEADING = E("Application ").strong(name)(
|
self.HEADING = E("Application ").strong(name)(" cannot handle your request")
|
||||||
" cannot handle your request"
|
|
||||||
)
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
self.request = request
|
self.request = request
|
||||||
@ -58,9 +56,7 @@ class ErrorPage(BasePage):
|
|||||||
# Show context details if available on the exception
|
# Show context details if available on the exception
|
||||||
context = getattr(self.exc, "context", None)
|
context = getattr(self.exc, "context", None)
|
||||||
if context:
|
if context:
|
||||||
self._key_value_table(
|
self._key_value_table("Issue context", "exception-context", context)
|
||||||
"Issue context", "exception-context", context
|
|
||||||
)
|
|
||||||
|
|
||||||
if not debug:
|
if not debug:
|
||||||
with self.doc.div(id="enduser"):
|
with self.doc.div(id="enduser"):
|
||||||
@ -72,19 +68,13 @@ class ErrorPage(BasePage):
|
|||||||
# Show extra details if available on the exception
|
# Show extra details if available on the exception
|
||||||
extra = getattr(self.exc, "extra", None)
|
extra = getattr(self.exc, "extra", None)
|
||||||
if extra:
|
if extra:
|
||||||
self._key_value_table(
|
self._key_value_table("Issue extra data", "exception-extra", extra)
|
||||||
"Issue extra data", "exception-extra", extra
|
|
||||||
)
|
|
||||||
|
|
||||||
self.doc.summary(
|
self.doc.summary("Details for developers (Sanic debug mode only)")
|
||||||
"Details for developers (Sanic debug mode only)"
|
|
||||||
)
|
|
||||||
if self.exc:
|
if self.exc:
|
||||||
with self.doc.div(class_="exception-wrapper"):
|
with self.doc.div(class_="exception-wrapper"):
|
||||||
self.doc.h2(f"Exception in {route_name}:")
|
self.doc.h2(f"Exception in {route_name}:")
|
||||||
self.doc(
|
self.doc(html_traceback(self.exc, include_js_css=False))
|
||||||
html_traceback(self.exc, include_js_css=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._key_value_table(
|
self._key_value_table(
|
||||||
f"{self.request.method} {self.request.path}",
|
f"{self.request.method} {self.request.path}",
|
||||||
@ -104,6 +94,4 @@ class ErrorPage(BasePage):
|
|||||||
value = str(value)
|
value = str(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
value = E.em("Unable to display value")
|
value = E.em("Unable to display value")
|
||||||
self.doc.dt.span(key, class_="nobr key").span(": ").dd(
|
self.doc.dt.span(key, class_="nobr key").span(": ").dd(value)
|
||||||
value
|
|
||||||
)
|
|
||||||
|
@ -60,9 +60,7 @@ def parse_multipart_form(body, boundary):
|
|||||||
colon_index = form_line.index(":")
|
colon_index = form_line.index(":")
|
||||||
idx = colon_index + 2
|
idx = colon_index + 2
|
||||||
form_header_field = form_line[0:colon_index].lower()
|
form_header_field = form_line[0:colon_index].lower()
|
||||||
form_header_value, form_parameters = parse_content_header(
|
form_header_value, form_parameters = parse_content_header(form_line[idx:])
|
||||||
form_line[idx:]
|
|
||||||
)
|
|
||||||
|
|
||||||
if form_header_field == "content-disposition":
|
if form_header_field == "content-disposition":
|
||||||
field_name = form_parameters.get("name")
|
field_name = form_parameters.get("name")
|
||||||
@ -97,9 +95,7 @@ def parse_multipart_form(body, boundary):
|
|||||||
else:
|
else:
|
||||||
fields[field_name] = [value]
|
fields[field_name] = [value]
|
||||||
else:
|
else:
|
||||||
form_file = File(
|
form_file = File(type=content_type, name=file_name, body=post_data)
|
||||||
type=content_type, name=file_name, body=post_data
|
|
||||||
)
|
|
||||||
if field_name in files:
|
if field_name in files:
|
||||||
files[field_name].append(form_file)
|
files[field_name].append(form_file)
|
||||||
else:
|
else:
|
||||||
|
@ -279,9 +279,7 @@ class Request(Generic[sanic_type, ctx_type]):
|
|||||||
int: The HTTP/3 stream ID.
|
int: The HTTP/3 stream ID.
|
||||||
"""
|
"""
|
||||||
if self.protocol.version is not HTTP.VERSION_3:
|
if self.protocol.version is not HTTP.VERSION_3:
|
||||||
raise ServerError(
|
raise ServerError("Stream ID is only a property of a HTTP/3 request")
|
||||||
"Stream ID is only a property of a HTTP/3 request"
|
|
||||||
)
|
|
||||||
return self._stream_id
|
return self._stream_id
|
||||||
|
|
||||||
def reset_response(self) -> None:
|
def reset_response(self) -> None:
|
||||||
@ -296,10 +294,7 @@ class Request(Generic[sanic_type, ctx_type]):
|
|||||||
sent.
|
sent.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if (
|
if self.stream is not None and self.stream.stage is not Stage.HANDLER:
|
||||||
self.stream is not None
|
|
||||||
and self.stream.stage is not Stage.HANDLER
|
|
||||||
):
|
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
"Cannot reset response because previous response was sent."
|
"Cannot reset response because previous response was sent."
|
||||||
)
|
)
|
||||||
@ -622,9 +617,7 @@ class Request(Generic[sanic_type, ctx_type]):
|
|||||||
pass
|
pass
|
||||||
return self.parsed_credentials
|
return self.parsed_credentials
|
||||||
|
|
||||||
def get_form(
|
def get_form(self, keep_blank_values: bool = False) -> RequestParameters | None:
|
||||||
self, keep_blank_values: bool = False
|
|
||||||
) -> RequestParameters | None:
|
|
||||||
"""Method to extract and parse the form data from a request.
|
"""Method to extract and parse the form data from a request.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -635,9 +628,7 @@ class Request(Generic[sanic_type, ctx_type]):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
self.parsed_form = RequestParameters()
|
self.parsed_form = RequestParameters()
|
||||||
self.parsed_files = RequestParameters()
|
self.parsed_files = RequestParameters()
|
||||||
content_type = self.headers.getone(
|
content_type = self.headers.getone("content-type", DEFAULT_HTTP_CONTENT_TYPE)
|
||||||
"content-type", DEFAULT_HTTP_CONTENT_TYPE
|
|
||||||
)
|
|
||||||
content_type, parameters = parse_content_header(content_type)
|
content_type, parameters = parse_content_header(content_type)
|
||||||
try:
|
try:
|
||||||
if content_type == "application/x-www-form-urlencoded":
|
if content_type == "application/x-www-form-urlencoded":
|
||||||
@ -735,9 +726,7 @@ class Request(Generic[sanic_type, ctx_type]):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.parsed_args[
|
return self.parsed_args[(keep_blank_values, strict_parsing, encoding, errors)]
|
||||||
(keep_blank_values, strict_parsing, encoding, errors)
|
|
||||||
]
|
|
||||||
|
|
||||||
args = property(get_args)
|
args = property(get_args)
|
||||||
"""Convenience property to access `Request.get_args` with default values.
|
"""Convenience property to access `Request.get_args` with default values.
|
||||||
@ -990,9 +979,7 @@ class Request(Generic[sanic_type, ctx_type]):
|
|||||||
server_name = self.app.config.get("SERVER_NAME")
|
server_name = self.app.config.get("SERVER_NAME")
|
||||||
if server_name:
|
if server_name:
|
||||||
return server_name.split("//", 1)[-1].split("/", 1)[0]
|
return server_name.split("//", 1)[-1].split("/", 1)[0]
|
||||||
return str(
|
return str(self.forwarded.get("host") or self.headers.getone("host", ""))
|
||||||
self.forwarded.get("host") or self.headers.getone("host", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def server_name(self) -> str:
|
def server_name(self) -> str:
|
||||||
|
@ -18,9 +18,7 @@ from sanic.models.protocol_types import HTMLProtocol, Range
|
|||||||
from .types import HTTPResponse, JSONResponse, ResponseStream
|
from .types import HTTPResponse, JSONResponse, ResponseStream
|
||||||
|
|
||||||
|
|
||||||
def empty(
|
def empty(status: int = 204, headers: dict[str, str] | None = None) -> HTTPResponse:
|
||||||
status: int = 204, headers: dict[str, str] | None = None
|
|
||||||
) -> HTTPResponse:
|
|
||||||
"""Returns an empty response to the client.
|
"""Returns an empty response to the client.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -85,13 +83,9 @@ def text(
|
|||||||
TypeError: If the body is not a string.
|
TypeError: If the body is not a string.
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
if not isinstance(body, str):
|
if not isinstance(body, str):
|
||||||
raise TypeError(
|
raise TypeError(f"Bad body type. Expected str, got {type(body).__name__})")
|
||||||
f"Bad body type. Expected str, got {type(body).__name__})"
|
|
||||||
)
|
|
||||||
|
|
||||||
return HTTPResponse(
|
return HTTPResponse(body, status=status, headers=headers, content_type=content_type)
|
||||||
body, status=status, headers=headers, content_type=content_type
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def raw(
|
def raw(
|
||||||
@ -178,18 +172,14 @@ async def validate_file(
|
|||||||
float(last_modified), tz=timezone.utc
|
float(last_modified), tz=timezone.utc
|
||||||
).replace(microsecond=0)
|
).replace(microsecond=0)
|
||||||
|
|
||||||
if (
|
if last_modified.utcoffset() is None and if_modified_since.utcoffset() is not None:
|
||||||
last_modified.utcoffset() is None
|
|
||||||
and if_modified_since.utcoffset() is not None
|
|
||||||
):
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Cannot compare tz-aware and tz-naive datetimes. To avoid "
|
"Cannot compare tz-aware and tz-naive datetimes. To avoid "
|
||||||
"this conflict Sanic is converting last_modified to UTC."
|
"this conflict Sanic is converting last_modified to UTC."
|
||||||
)
|
)
|
||||||
last_modified.replace(tzinfo=timezone.utc)
|
last_modified.replace(tzinfo=timezone.utc)
|
||||||
elif (
|
elif (
|
||||||
last_modified.utcoffset() is not None
|
last_modified.utcoffset() is not None and if_modified_since.utcoffset() is None
|
||||||
and if_modified_since.utcoffset() is None
|
|
||||||
):
|
):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Cannot compare tz-aware and tz-naive datetimes. To avoid "
|
"Cannot compare tz-aware and tz-naive datetimes. To avoid "
|
||||||
@ -240,25 +230,17 @@ async def file(
|
|||||||
stat = await stat_async(location)
|
stat = await stat_async(location)
|
||||||
last_modified = stat.st_mtime
|
last_modified = stat.st_mtime
|
||||||
|
|
||||||
if (
|
if validate_when_requested and request_headers is not None and last_modified:
|
||||||
validate_when_requested
|
|
||||||
and request_headers is not None
|
|
||||||
and last_modified
|
|
||||||
):
|
|
||||||
response = await validate_file(request_headers, last_modified)
|
response = await validate_file(request_headers, last_modified)
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
if last_modified:
|
if last_modified:
|
||||||
headers.setdefault(
|
headers.setdefault("Last-Modified", formatdate(last_modified, usegmt=True))
|
||||||
"Last-Modified", formatdate(last_modified, usegmt=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
headers.setdefault(
|
headers.setdefault("Content-Disposition", f'attachment; filename="{filename}"')
|
||||||
"Content-Disposition", f'attachment; filename="{filename}"'
|
|
||||||
)
|
|
||||||
|
|
||||||
if no_store:
|
if no_store:
|
||||||
cache_control = "no-store"
|
cache_control = "no-store"
|
||||||
@ -323,9 +305,7 @@ def redirect(
|
|||||||
# According to RFC 7231, a relative URI is now permitted.
|
# According to RFC 7231, a relative URI is now permitted.
|
||||||
headers["Location"] = safe_to
|
headers["Location"] = safe_to
|
||||||
|
|
||||||
return HTTPResponse(
|
return HTTPResponse(status=status, headers=headers, content_type=content_type)
|
||||||
status=status, headers=headers, content_type=content_type
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def file_stream(
|
async def file_stream(
|
||||||
@ -357,9 +337,7 @@ async def file_stream(
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
if filename:
|
if filename:
|
||||||
headers.setdefault(
|
headers.setdefault("Content-Disposition", f'attachment; filename="{filename}"')
|
||||||
"Content-Disposition", f'attachment; filename="{filename}"'
|
|
||||||
)
|
|
||||||
filename = filename or path.split(location)[-1]
|
filename = filename or path.split(location)[-1]
|
||||||
mime_type = mime_type or guess_type(filename)[0] or "text/plain"
|
mime_type = mime_type or guess_type(filename)[0] or "text/plain"
|
||||||
if _range:
|
if _range:
|
||||||
|
@ -350,9 +350,7 @@ class JSONResponse(HTTPResponse):
|
|||||||
@raw_body.setter
|
@raw_body.setter
|
||||||
def raw_body(self, value: Any):
|
def raw_body(self, value: Any):
|
||||||
self._body_manually_set = False
|
self._body_manually_set = False
|
||||||
self._body = self._encode_body(
|
self._body = self._encode_body(self._use_dumps(value, **self._use_dumps_kwargs))
|
||||||
self._use_dumps(value, **self._use_dumps_kwargs)
|
|
||||||
)
|
|
||||||
self._raw_body = value
|
self._raw_body = value
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@ -484,9 +482,7 @@ class JSONResponse(HTTPResponse):
|
|||||||
self._check_body_not_manually_set()
|
self._check_body_not_manually_set()
|
||||||
|
|
||||||
if not isinstance(self._raw_body, (list, dict)):
|
if not isinstance(self._raw_body, (list, dict)):
|
||||||
raise SanicException(
|
raise SanicException("Cannot pop from a non-list and non-dict object.")
|
||||||
"Cannot pop from a non-list and non-dict object."
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(default, Default):
|
if isinstance(default, Default):
|
||||||
value = self._raw_body.pop(key)
|
value = self._raw_body.pop(key)
|
||||||
|
@ -40,9 +40,7 @@ class Router(BaseRouter):
|
|||||||
raise MethodNotAllowed(
|
raise MethodNotAllowed(
|
||||||
f"Method {method} not allowed for URL {path}",
|
f"Method {method} not allowed for URL {path}",
|
||||||
method=method,
|
method=method,
|
||||||
allowed_methods=tuple(e.allowed_methods)
|
allowed_methods=tuple(e.allowed_methods) if e.allowed_methods else None,
|
||||||
if e.allowed_methods
|
|
||||||
else None,
|
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||||
@ -137,11 +135,7 @@ class Router(BaseRouter):
|
|||||||
|
|
||||||
ident = name
|
ident = name
|
||||||
if len(hosts) > 1:
|
if len(hosts) > 1:
|
||||||
ident = (
|
ident = f"{name}_{host.replace('.', '_')}" if name else "__unnamed__"
|
||||||
f"{name}_{host.replace('.', '_')}"
|
|
||||||
if name
|
|
||||||
else "__unnamed__"
|
|
||||||
)
|
|
||||||
|
|
||||||
route = super().add(**params) # type: ignore
|
route = super().add(**params) # type: ignore
|
||||||
route.extra.ident = ident
|
route.extra.ident = ident
|
||||||
|
@ -303,9 +303,7 @@ class Http3Protocol(HttpProtocolMixin, ConnectionProtocol): # type: ignore
|
|||||||
if isinstance(event, ProtocolNegotiated):
|
if isinstance(event, ProtocolNegotiated):
|
||||||
self._setup_connection(transmit=self.transmit)
|
self._setup_connection(transmit=self.transmit)
|
||||||
if event.alpn_protocol in H3_ALPN:
|
if event.alpn_protocol in H3_ALPN:
|
||||||
self._connection = H3Connection(
|
self._connection = H3Connection(self._quic, enable_webtransport=True)
|
||||||
self._quic, enable_webtransport=True
|
|
||||||
)
|
|
||||||
elif isinstance(event, DatagramFrameReceived):
|
elif isinstance(event, DatagramFrameReceived):
|
||||||
if event.data == b"quack":
|
if event.data == b"quack":
|
||||||
self._quic.send_datagram_frame(b"quack-ack")
|
self._quic.send_datagram_frame(b"quack-ack")
|
||||||
|
@ -140,8 +140,7 @@ class WebSocketProtocol(HttpProtocol):
|
|||||||
)
|
)
|
||||||
loop = (
|
loop = (
|
||||||
request.transport.loop
|
request.transport.loop
|
||||||
if hasattr(request, "transport")
|
if hasattr(request, "transport") and hasattr(request.transport, "loop")
|
||||||
and hasattr(request.transport, "loop")
|
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
await self.websocket.connection_made(self, loop=loop)
|
await self.websocket.connection_made(self, loop=loop)
|
||||||
|
@ -167,9 +167,7 @@ def _setup_system_signals(
|
|||||||
ctrlc_workaround_for_windows(app)
|
ctrlc_workaround_for_windows(app)
|
||||||
else:
|
else:
|
||||||
for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]:
|
for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]:
|
||||||
loop.add_signal_handler(
|
loop.add_signal_handler(_signal, partial(app.stop, terminate=False))
|
||||||
_signal, partial(app.stop, terminate=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _run_server_forever(loop, before_stop, after_stop, cleanup, unix):
|
def _run_server_forever(loop, before_stop, after_stop, cleanup, unix):
|
||||||
@ -223,9 +221,7 @@ def _serve_http_1(
|
|||||||
unix=unix,
|
unix=unix,
|
||||||
**protocol_kwargs,
|
**protocol_kwargs,
|
||||||
)
|
)
|
||||||
asyncio_server_kwargs = (
|
asyncio_server_kwargs = asyncio_server_kwargs if asyncio_server_kwargs else {}
|
||||||
asyncio_server_kwargs if asyncio_server_kwargs else {}
|
|
||||||
)
|
|
||||||
if OS_IS_WINDOWS and sock:
|
if OS_IS_WINDOWS and sock:
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
sock = sock.share(pid)
|
sock = sock.share(pid)
|
||||||
@ -314,9 +310,7 @@ def _serve_http_3(
|
|||||||
run_multiple: bool = False,
|
run_multiple: bool = False,
|
||||||
):
|
):
|
||||||
if not HTTP3_AVAILABLE:
|
if not HTTP3_AVAILABLE:
|
||||||
raise ServerError(
|
raise ServerError("Cannot run HTTP/3 server without aioquic installed. ")
|
||||||
"Cannot run HTTP/3 server without aioquic installed. "
|
|
||||||
)
|
|
||||||
protocol = partial(Http3Protocol, app=app)
|
protocol = partial(Http3Protocol, app=app)
|
||||||
ticket_store = SessionTicketStore()
|
ticket_store = SessionTicketStore()
|
||||||
ssl_context = get_ssl_context(app, ssl)
|
ssl_context = get_ssl_context(app, ssl)
|
||||||
@ -339,9 +333,7 @@ def _serve_http_3(
|
|||||||
|
|
||||||
# TODO: Create connection cleanup and graceful shutdown
|
# TODO: Create connection cleanup and graceful shutdown
|
||||||
cleanup = None
|
cleanup = None
|
||||||
_run_server_forever(
|
_run_server_forever(loop, server.before_stop, server.after_stop, cleanup, None)
|
||||||
loop, server.before_stop, server.after_stop, cleanup, None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_protocol_kwargs(
|
def _build_protocol_kwargs(
|
||||||
|
@ -23,9 +23,7 @@ def bind_socket(host: str, port: int, *, backlog=100) -> socket.socket:
|
|||||||
try: # IP address: family must be specified for IPv6 at least
|
try: # IP address: family must be specified for IPv6 at least
|
||||||
ip = ip_address(host)
|
ip = ip_address(host)
|
||||||
host = str(ip)
|
host = str(ip)
|
||||||
sock = socket.socket(
|
sock = socket.socket(socket.AF_INET6 if ip.version == 6 else socket.AF_INET)
|
||||||
socket.AF_INET6 if ip.version == 6 else socket.AF_INET
|
|
||||||
)
|
|
||||||
except ValueError: # Hostname, may become AF_INET or AF_INET6
|
except ValueError: # Hostname, may become AF_INET or AF_INET6
|
||||||
sock = socket.socket()
|
sock = socket.socket()
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
@ -92,9 +90,7 @@ def remove_unix_socket(path: str | None) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def configure_socket(
|
def configure_socket(server_settings: dict[str, Any]) -> socket.SocketType | None:
|
||||||
server_settings: dict[str, Any]
|
|
||||||
) -> socket.SocketType | None:
|
|
||||||
# Create a listening socket or use the one in settings
|
# Create a listening socket or use the one in settings
|
||||||
if server_settings.get("version") is HTTP.VERSION_3:
|
if server_settings.get("version") is HTTP.VERSION_3:
|
||||||
return None
|
return None
|
||||||
|
@ -152,8 +152,7 @@ class WebsocketFrameAssembler:
|
|||||||
# and get_in_progress check, this exception is here
|
# and get_in_progress check, this exception is here
|
||||||
# as a failsafe
|
# as a failsafe
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
"Websocket get() found a message when "
|
"Websocket get() found a message when " "state was already fetched."
|
||||||
"state was already fetched."
|
|
||||||
)
|
)
|
||||||
self.message_fetched.set()
|
self.message_fetched.set()
|
||||||
self.chunks = []
|
self.chunks = []
|
||||||
|
@ -152,12 +152,8 @@ class WebsocketImplProtocol:
|
|||||||
self.data_finished_fut = asyncio.shield(self.loop.create_future())
|
self.data_finished_fut = asyncio.shield(self.loop.create_future())
|
||||||
|
|
||||||
if self.ping_interval:
|
if self.ping_interval:
|
||||||
self.keepalive_ping_task = asyncio.create_task(
|
self.keepalive_ping_task = asyncio.create_task(self.keepalive_ping())
|
||||||
self.keepalive_ping()
|
self.auto_closer_task = asyncio.create_task(self.auto_close_connection())
|
||||||
)
|
|
||||||
self.auto_closer_task = asyncio.create_task(
|
|
||||||
self.auto_close_connection()
|
|
||||||
)
|
|
||||||
|
|
||||||
async def wait_for_connection_lost(self, timeout=None) -> bool:
|
async def wait_for_connection_lost(self, timeout=None) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -246,9 +242,7 @@ class WebsocketImplProtocol:
|
|||||||
try:
|
try:
|
||||||
await asyncio.wait_for(ping_waiter, self.ping_timeout)
|
await asyncio.wait_for(ping_waiter, self.ping_timeout)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
error_logger.warning(
|
error_logger.warning("Websocket timed out waiting for pong")
|
||||||
"Websocket timed out waiting for pong"
|
|
||||||
)
|
|
||||||
self.fail_connection(1011)
|
self.fail_connection(1011)
|
||||||
break
|
break
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@ -278,9 +272,7 @@ class WebsocketImplProtocol:
|
|||||||
self.keepalive_ping_task = None
|
self.keepalive_ping_task = None
|
||||||
if self.loop and self.io_proto and self.io_proto.transport:
|
if self.loop and self.io_proto and self.io_proto.transport:
|
||||||
self.io_proto.transport.close()
|
self.io_proto.transport.close()
|
||||||
self.loop.call_later(
|
self.loop.call_later(self.close_timeout, self.io_proto.transport.abort)
|
||||||
self.close_timeout, self.io_proto.transport.abort
|
|
||||||
)
|
|
||||||
# We were never open, or already closed
|
# We were never open, or already closed
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -318,9 +310,7 @@ class WebsocketImplProtocol:
|
|||||||
try:
|
try:
|
||||||
data_to_send = self.ws_proto.data_to_send()
|
data_to_send = self.ws_proto.data_to_send()
|
||||||
while (
|
while (
|
||||||
len(data_to_send)
|
len(data_to_send) and self.io_proto and self.io_proto.transport
|
||||||
and self.io_proto
|
|
||||||
and self.io_proto.transport
|
|
||||||
):
|
):
|
||||||
frame_data = data_to_send.pop(0)
|
frame_data = data_to_send.pop(0)
|
||||||
self.io_proto.transport.write(frame_data)
|
self.io_proto.transport.write(frame_data)
|
||||||
@ -356,11 +346,7 @@ class WebsocketImplProtocol:
|
|||||||
self.ws_proto.send_close(code, reason)
|
self.ws_proto.send_close(code, reason)
|
||||||
data_to_send.extend(self.ws_proto.data_to_send())
|
data_to_send.extend(self.ws_proto.data_to_send())
|
||||||
try:
|
try:
|
||||||
while (
|
while len(data_to_send) and self.io_proto and self.io_proto.transport:
|
||||||
len(data_to_send)
|
|
||||||
and self.io_proto
|
|
||||||
and self.io_proto.transport
|
|
||||||
):
|
|
||||||
frame_data = data_to_send.pop(0)
|
frame_data = data_to_send.pop(0)
|
||||||
self.io_proto.transport.write(frame_data)
|
self.io_proto.transport.write(frame_data)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -392,15 +378,11 @@ class WebsocketImplProtocol:
|
|||||||
if self.data_finished_fut:
|
if self.data_finished_fut:
|
||||||
try:
|
try:
|
||||||
await self.data_finished_fut
|
await self.data_finished_fut
|
||||||
logger.debug(
|
logger.debug("Websocket task finished. Closing the connection.")
|
||||||
"Websocket task finished. Closing the connection."
|
|
||||||
)
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
# Cancelled error is called when data phase is cancelled
|
# Cancelled error is called when data phase is cancelled
|
||||||
# if an error occurred or the client closed the connection
|
# if an error occurred or the client closed the connection
|
||||||
logger.debug(
|
logger.debug("Websocket handler cancelled. Closing the connection.")
|
||||||
"Websocket handler cancelled. Closing the connection."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cancel the keepalive ping task.
|
# Cancel the keepalive ping task.
|
||||||
if self.keepalive_ping_task:
|
if self.keepalive_ping_task:
|
||||||
@ -426,10 +408,7 @@ class WebsocketImplProtocol:
|
|||||||
if (not self.io_proto) or (not self.io_proto.transport):
|
if (not self.io_proto) or (not self.io_proto.transport):
|
||||||
# we were never open, or done. Can't do any finalization.
|
# we were never open, or done. Can't do any finalization.
|
||||||
return
|
return
|
||||||
elif (
|
elif self.connection_lost_waiter and self.connection_lost_waiter.done():
|
||||||
self.connection_lost_waiter
|
|
||||||
and self.connection_lost_waiter.done()
|
|
||||||
):
|
|
||||||
# connection confirmed closed already, proceed to abort waiter
|
# connection confirmed closed already, proceed to abort waiter
|
||||||
...
|
...
|
||||||
elif self.io_proto.transport.is_closing():
|
elif self.io_proto.transport.is_closing():
|
||||||
@ -447,9 +426,7 @@ class WebsocketImplProtocol:
|
|||||||
if self.io_proto and self.io_proto.transport:
|
if self.io_proto and self.io_proto.transport:
|
||||||
self.io_proto.transport.abort()
|
self.io_proto.transport.abort()
|
||||||
else:
|
else:
|
||||||
if await self.wait_for_connection_lost(
|
if await self.wait_for_connection_lost(timeout=self.close_timeout):
|
||||||
timeout=self.close_timeout
|
|
||||||
):
|
|
||||||
# Connection aborted before the timeout expired.
|
# Connection aborted before the timeout expired.
|
||||||
return
|
return
|
||||||
error_logger.warning(
|
error_logger.warning(
|
||||||
@ -731,8 +708,7 @@ class WebsocketImplProtocol:
|
|||||||
async with self.conn_mutex:
|
async with self.conn_mutex:
|
||||||
if self.ws_proto.state in (CLOSED, CLOSING):
|
if self.ws_proto.state in (CLOSED, CLOSING):
|
||||||
raise WebsocketClosed(
|
raise WebsocketClosed(
|
||||||
"Cannot send a ping when the websocket interface "
|
"Cannot send a ping when the websocket interface " "is closed."
|
||||||
"is closed."
|
|
||||||
)
|
)
|
||||||
if (not self.io_proto) or (not self.io_proto.loop):
|
if (not self.io_proto) or (not self.io_proto.loop):
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
@ -747,9 +723,7 @@ class WebsocketImplProtocol:
|
|||||||
|
|
||||||
# Protect against duplicates if a payload is explicitly set.
|
# Protect against duplicates if a payload is explicitly set.
|
||||||
if data in self.pings:
|
if data in self.pings:
|
||||||
raise ValueError(
|
raise ValueError("already waiting for a pong with the same data")
|
||||||
"already waiting for a pong with the same data"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate a unique random payload otherwise.
|
# Generate a unique random payload otherwise.
|
||||||
while data is None or data in self.pings:
|
while data is None or data in self.pings:
|
||||||
@ -842,9 +816,7 @@ class WebsocketImplProtocol:
|
|||||||
self.ws_proto.receive_eof()
|
self.ws_proto.receive_eof()
|
||||||
data_to_send = self.ws_proto.data_to_send()
|
data_to_send = self.ws_proto.data_to_send()
|
||||||
events_to_process = self.ws_proto.events_received()
|
events_to_process = self.ws_proto.events_received()
|
||||||
asyncio.create_task(
|
asyncio.create_task(self.async_eof_received(data_to_send, events_to_process))
|
||||||
self.async_eof_received(data_to_send, events_to_process)
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
|
@ -303,11 +303,7 @@ class SignalRouter(BaseRouter):
|
|||||||
|
|
||||||
def _build_event_parts(self, event: str) -> tuple[str, str, str]:
|
def _build_event_parts(self, event: str) -> tuple[str, str, str]:
|
||||||
parts = path_to_parts(event, self.delimiter)
|
parts = path_to_parts(event, self.delimiter)
|
||||||
if (
|
if len(parts) != 3 or parts[0].startswith("<") or parts[1].startswith("<"):
|
||||||
len(parts) != 3
|
|
||||||
or parts[0].startswith("<")
|
|
||||||
or parts[1].startswith("<")
|
|
||||||
):
|
|
||||||
raise InvalidSignal("Invalid signal event: %s" % event)
|
raise InvalidSignal("Invalid signal event: %s" % event)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -315,9 +311,7 @@ class SignalRouter(BaseRouter):
|
|||||||
and event not in RESERVED_NAMESPACES[parts[0]]
|
and event not in RESERVED_NAMESPACES[parts[0]]
|
||||||
and not (parts[2].startswith("<") and parts[2].endswith(">"))
|
and not (parts[2].startswith("<") and parts[2].endswith(">"))
|
||||||
):
|
):
|
||||||
raise InvalidSignal(
|
raise InvalidSignal("Cannot declare reserved signal event: %s" % event)
|
||||||
"Cannot declare reserved signal event: %s" % event
|
|
||||||
)
|
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
def _clean_trigger(self, trigger: str) -> str:
|
def _clean_trigger(self, trigger: str) -> str:
|
||||||
|
@ -11,8 +11,6 @@ def create_simple_server(directory: Path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
app = Sanic("SimpleServer")
|
app = Sanic("SimpleServer")
|
||||||
app.static(
|
app.static("/", directory, name="main", directory_view=True, index="index.html")
|
||||||
"/", directory, name="main", directory_view=True, index="index.html"
|
|
||||||
)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -22,9 +22,7 @@ class RemoveAltSvc(NodeTransformer):
|
|||||||
def __init__(self, app: Sanic, verbosity: int = 0) -> None:
|
def __init__(self, app: Sanic, verbosity: int = 0) -> None:
|
||||||
self._app = app
|
self._app = app
|
||||||
self._verbosity = verbosity
|
self._verbosity = verbosity
|
||||||
self._versions = {
|
self._versions = {info.settings["version"] for info in app.state.server_info}
|
||||||
info.settings["version"] for info in app.state.server_info
|
|
||||||
}
|
|
||||||
|
|
||||||
def visit_Assign(self, node: Assign) -> Any:
|
def visit_Assign(self, node: Assign) -> Any:
|
||||||
if any(self._matches(target) for target in node.targets):
|
if any(self._matches(target) for target in node.targets):
|
||||||
|
@ -14,9 +14,7 @@ class OptionalDispatchEvent(BaseScheme):
|
|||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
|
|
||||||
self._sync_events()
|
self._sync_events()
|
||||||
self._registered_events = [
|
self._registered_events = [signal.name for signal in app.signal_router.routes]
|
||||||
signal.name for signal in app.signal_router.routes
|
|
||||||
]
|
|
||||||
|
|
||||||
def visitors(self) -> List[NodeTransformer]:
|
def visitors(self) -> List[NodeTransformer]:
|
||||||
return [RemoveDispatch(self._registered_events)]
|
return [RemoveDispatch(self._registered_events)]
|
||||||
@ -26,9 +24,7 @@ class OptionalDispatchEvent(BaseScheme):
|
|||||||
app_events = {}
|
app_events = {}
|
||||||
for app in self.app.__class__._app_registry.values():
|
for app in self.app.__class__._app_registry.values():
|
||||||
if app.state.server_info:
|
if app.state.server_info:
|
||||||
app_events[app] = {
|
app_events[app] = {signal.name for signal in app.signal_router.routes}
|
||||||
signal.name for signal in app.signal_router.routes
|
|
||||||
}
|
|
||||||
all_events.update(app_events[app])
|
all_events.update(app_events[app])
|
||||||
|
|
||||||
for app, events in app_events.items():
|
for app, events in app_events.items():
|
||||||
|
@ -14,9 +14,7 @@ class SharedContext(SimpleNamespace):
|
|||||||
|
|
||||||
def __setattr__(self, name: str, value: Any) -> None:
|
def __setattr__(self, name: str, value: Any) -> None:
|
||||||
if self.is_locked:
|
if self.is_locked:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Cannot set {name} on locked SharedContext object")
|
||||||
f"Cannot set {name} on locked SharedContext object"
|
|
||||||
)
|
|
||||||
if not os.environ.get("SANIC_WORKER_NAME"):
|
if not os.environ.get("SANIC_WORKER_NAME"):
|
||||||
to_check: Iterable[Any]
|
to_check: Iterable[Any]
|
||||||
if not isinstance(value, (tuple, frozenset)):
|
if not isinstance(value, (tuple, frozenset)):
|
||||||
@ -35,8 +33,7 @@ class SharedContext(SimpleNamespace):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
module = ""
|
module = ""
|
||||||
if not any(
|
if not any(
|
||||||
module.startswith(prefix)
|
module.startswith(prefix) for prefix in ("multiprocessing", "ctypes")
|
||||||
for prefix in ("multiprocessing", "ctypes")
|
|
||||||
):
|
):
|
||||||
error_logger.warning(
|
error_logger.warning(
|
||||||
f"{Colors.YELLOW}Unsafe object {Colors.PURPLE}{name} "
|
f"{Colors.YELLOW}Unsafe object {Colors.PURPLE}{name} "
|
||||||
|
@ -80,9 +80,7 @@ def load_module_from_file_location(
|
|||||||
env_vars_in_location = set(re_findall(r"\${(.+?)}", location))
|
env_vars_in_location = set(re_findall(r"\${(.+?)}", location))
|
||||||
|
|
||||||
# B) Check these variables exists in environment.
|
# B) Check these variables exists in environment.
|
||||||
not_defined_env_vars = env_vars_in_location.difference(
|
not_defined_env_vars = env_vars_in_location.difference(os_environ.keys())
|
||||||
os_environ.keys()
|
|
||||||
)
|
|
||||||
if not_defined_env_vars:
|
if not_defined_env_vars:
|
||||||
raise LoadFileException(
|
raise LoadFileException(
|
||||||
"The following environment variables are not set: "
|
"The following environment variables are not set: "
|
||||||
@ -91,18 +89,14 @@ def load_module_from_file_location(
|
|||||||
|
|
||||||
# C) Substitute them in location.
|
# C) Substitute them in location.
|
||||||
for env_var in env_vars_in_location:
|
for env_var in env_vars_in_location:
|
||||||
location = location.replace(
|
location = location.replace("${" + env_var + "}", os_environ[env_var])
|
||||||
"${" + env_var + "}", os_environ[env_var]
|
|
||||||
)
|
|
||||||
|
|
||||||
location = str(location)
|
location = str(location)
|
||||||
if ".py" in location:
|
if ".py" in location:
|
||||||
name = location.split("/")[-1].split(".")[
|
name = location.split("/")[-1].split(".")[
|
||||||
0
|
0
|
||||||
] # get just the file name without path and .py extension
|
] # get just the file name without path and .py extension
|
||||||
_mod_spec = spec_from_file_location(
|
_mod_spec = spec_from_file_location(name, location, *args, **kwargs)
|
||||||
name, location, *args, **kwargs
|
|
||||||
)
|
|
||||||
assert _mod_spec is not None # type assertion for mypy
|
assert _mod_spec is not None # type assertion for mypy
|
||||||
module = module_from_spec(_mod_spec)
|
module = module_from_spec(_mod_spec)
|
||||||
_mod_spec.loader.exec_module(module) # type: ignore
|
_mod_spec.loader.exec_module(module) # type: ignore
|
||||||
|
@ -99,9 +99,7 @@ class WorkerManager:
|
|||||||
Worker: The Worker instance
|
Worker: The Worker instance
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
container = self.transient if transient else self.durable
|
container = self.transient if transient else self.durable
|
||||||
worker = Worker(
|
worker = Worker(ident, func, kwargs, self.context, self.worker_state, workers)
|
||||||
ident, func, kwargs, self.context, self.worker_state, workers
|
|
||||||
)
|
|
||||||
container[worker.ident] = worker
|
container[worker.ident] = worker
|
||||||
return worker
|
return worker
|
||||||
|
|
||||||
@ -204,9 +202,7 @@ class WorkerManager:
|
|||||||
|
|
||||||
change = num_worker - self.num_server
|
change = num_worker - self.num_server
|
||||||
if change == 0:
|
if change == 0:
|
||||||
logger.info(
|
logger.info(f"No change needed. There are already {num_worker} workers.")
|
||||||
f"No change needed. There are already {num_worker} workers."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Scaling from {self.num_server} to {num_worker} workers")
|
logger.info(f"Scaling from {self.num_server} to {num_worker} workers")
|
||||||
@ -237,9 +233,7 @@ class WorkerManager:
|
|||||||
try:
|
try:
|
||||||
if self.monitor_subscriber.poll(0.1):
|
if self.monitor_subscriber.poll(0.1):
|
||||||
message = self.monitor_subscriber.recv()
|
message = self.monitor_subscriber.recv()
|
||||||
logger.debug(
|
logger.debug(f"Monitor message: {message}", extra={"verbosity": 2})
|
||||||
f"Monitor message: {message}", extra={"verbosity": 2}
|
|
||||||
)
|
|
||||||
if not message:
|
if not message:
|
||||||
break
|
break
|
||||||
elif message == "__TERMINATE__":
|
elif message == "__TERMINATE__":
|
||||||
@ -258,9 +252,7 @@ class WorkerManager:
|
|||||||
reloaded_files = (
|
reloaded_files = (
|
||||||
split_message[1] if len(split_message) > 1 else None
|
split_message[1] if len(split_message) > 1 else None
|
||||||
)
|
)
|
||||||
process_names = [
|
process_names = [name.strip() for name in processes.split(",")]
|
||||||
name.strip() for name in processes.split(",")
|
|
||||||
]
|
|
||||||
if "__ALL_PROCESSES__" in process_names:
|
if "__ALL_PROCESSES__" in process_names:
|
||||||
process_names = None
|
process_names = None
|
||||||
order = (
|
order = (
|
||||||
|
@ -54,8 +54,7 @@ class WorkerMultiplexer:
|
|||||||
"""
|
"""
|
||||||
if name and all_workers:
|
if name and all_workers:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Ambiguous restart with both a named process and"
|
"Ambiguous restart with both a named process and" " all_workers=True"
|
||||||
" all_workers=True"
|
|
||||||
)
|
)
|
||||||
if not name:
|
if not name:
|
||||||
name = "__ALL_PROCESSES__:" if all_workers else self.name
|
name = "__ALL_PROCESSES__:" if all_workers else self.name
|
||||||
|
@ -29,9 +29,7 @@ class WorkerProcess:
|
|||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.worker_state = worker_state
|
self.worker_state = worker_state
|
||||||
if self.name not in self.worker_state:
|
if self.name not in self.worker_state:
|
||||||
self.worker_state[self.name] = {
|
self.worker_state[self.name] = {"server": self.SERVER_LABEL in self.name}
|
||||||
"server": self.SERVER_LABEL in self.name
|
|
||||||
}
|
|
||||||
self.spawn()
|
self.spawn()
|
||||||
|
|
||||||
def set_state(self, state: ProcessState, force=False):
|
def set_state(self, state: ProcessState, force=False):
|
||||||
@ -94,9 +92,7 @@ class WorkerProcess:
|
|||||||
self._terminate_now()
|
self._terminate_now()
|
||||||
else:
|
else:
|
||||||
self._old_process = self._current_process
|
self._old_process = self._current_process
|
||||||
self.kwargs.update(
|
self.kwargs.update({"config": {k.upper(): v for k, v in kwargs.items()}})
|
||||||
{"config": {k.upper(): v for k, v in kwargs.items()}}
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
self.spawn()
|
self.spawn()
|
||||||
self.start()
|
self.start()
|
||||||
|
@ -27,9 +27,7 @@ def worker_serve(
|
|||||||
app_loader: AppLoader,
|
app_loader: AppLoader,
|
||||||
worker_state: Optional[Dict[str, Any]] = None,
|
worker_state: Optional[Dict[str, Any]] = None,
|
||||||
server_info: Optional[Dict[str, List[ApplicationServerInfo]]] = None,
|
server_info: Optional[Dict[str, List[ApplicationServerInfo]]] = None,
|
||||||
ssl: Optional[
|
ssl: Optional[Union[SSLContext, Dict[str, Union[str, os.PathLike]]]] = None,
|
||||||
Union[SSLContext, Dict[str, Union[str, os.PathLike]]]
|
|
||||||
] = None,
|
|
||||||
sock: Optional[socket.socket] = None,
|
sock: Optional[socket.socket] = None,
|
||||||
unix: Optional[str] = None,
|
unix: Optional[str] = None,
|
||||||
reuse_port: bool = False,
|
reuse_port: bool = False,
|
||||||
@ -86,9 +84,7 @@ def worker_serve(
|
|||||||
# Hydrate apps with any passed server info
|
# Hydrate apps with any passed server info
|
||||||
|
|
||||||
if monitor_publisher is None:
|
if monitor_publisher is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError("No restart publisher found in worker process")
|
||||||
"No restart publisher found in worker process"
|
|
||||||
)
|
|
||||||
if worker_state is None:
|
if worker_state is None:
|
||||||
raise RuntimeError("No worker state found in worker process")
|
raise RuntimeError("No worker state found in worker process")
|
||||||
|
|
||||||
@ -96,9 +92,7 @@ def worker_serve(
|
|||||||
apps = list(Sanic._app_registry.values())
|
apps = list(Sanic._app_registry.values())
|
||||||
app.before_server_start(partial(app._start_servers, apps=apps))
|
app.before_server_start(partial(app._start_servers, apps=apps))
|
||||||
for a in apps:
|
for a in apps:
|
||||||
a.multiplexer = WorkerMultiplexer(
|
a.multiplexer = WorkerMultiplexer(monitor_publisher, worker_state)
|
||||||
monitor_publisher, worker_state
|
|
||||||
)
|
|
||||||
|
|
||||||
if app.debug:
|
if app.debug:
|
||||||
loop.set_debug(app.debug)
|
loop.set_debug(app.debug)
|
||||||
|
@ -62,9 +62,7 @@ class WorkerState(Mapping):
|
|||||||
|
|
||||||
def update(self, mapping: MappingType[str, Any]) -> None:
|
def update(self, mapping: MappingType[str, Any]) -> None:
|
||||||
if any(k in self.RESTRICTED for k in mapping.keys()):
|
if any(k in self.RESTRICTED for k in mapping.keys()):
|
||||||
self._write_error(
|
self._write_error([k for k in mapping.keys() if k in self.RESTRICTED])
|
||||||
[k for k in mapping.keys() if k in self.RESTRICTED]
|
|
||||||
)
|
|
||||||
self._state[self._name] = {
|
self._state[self._name] = {
|
||||||
**self._state[self._name],
|
**self._state[self._name],
|
||||||
**mapping,
|
**mapping,
|
||||||
|
@ -22,9 +22,7 @@ if __name__ == "__main__":
|
|||||||
help="Render the news fragments, don't write to files, "
|
help="Render the news fragments, don't write to files, "
|
||||||
"don't check versions.",
|
"don't check versions.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option("--dir", "directory", default=path.dirname(path.abspath(__file__)))
|
||||||
"--dir", "directory", default=path.dirname(path.abspath(__file__))
|
|
||||||
)
|
|
||||||
@click.option("--name", "project_name", default=None)
|
@click.option("--name", "project_name", default=None)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--version",
|
"--version",
|
||||||
|
@ -25,8 +25,7 @@ GIT_COMMANDS = {
|
|||||||
],
|
],
|
||||||
"push_tag": ["git push origin {new_version}"],
|
"push_tag": ["git push origin {new_version}"],
|
||||||
"get_change_log": [
|
"get_change_log": [
|
||||||
'git log --no-merges --pretty=format:"%h::: %cn::: %s" '
|
'git log --no-merges --pretty=format:"%h::: %cn::: %s" ' "{current_version}.."
|
||||||
"{current_version}.."
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +71,7 @@ class Directory:
|
|||||||
|
|
||||||
def _run_shell_command(command: list):
|
def _run_shell_command(command: list):
|
||||||
try:
|
try:
|
||||||
process = Popen(
|
process = Popen(command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True)
|
||||||
command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True
|
|
||||||
)
|
|
||||||
output, error = process.communicate()
|
output, error = process.communicate()
|
||||||
return_code = process.returncode
|
return_code = process.returncode
|
||||||
return output.decode("utf-8"), error, return_code
|
return output.decode("utf-8"), error, return_code
|
||||||
@ -143,9 +140,9 @@ def _update_release_version_for_sanic(
|
|||||||
current_version_line = config_parser.get(
|
current_version_line = config_parser.get(
|
||||||
"version", "current_version_pattern"
|
"version", "current_version_pattern"
|
||||||
).format(current_version=current_version)
|
).format(current_version=current_version)
|
||||||
new_version_line = config_parser.get(
|
new_version_line = config_parser.get("version", "new_version_pattern").format(
|
||||||
"version", "new_version_pattern"
|
new_version=new_version
|
||||||
).format(new_version=new_version)
|
)
|
||||||
|
|
||||||
for version_file in version_files.split(","):
|
for version_file in version_files.split(","):
|
||||||
with open(version_file) as init_file:
|
with open(version_file) as init_file:
|
||||||
|
8
setup.py
8
setup.py
@ -59,9 +59,7 @@ def str_to_bool(val: str) -> bool:
|
|||||||
|
|
||||||
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
|
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
|
||||||
try:
|
try:
|
||||||
version = re.findall(
|
version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0]
|
||||||
r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M
|
|
||||||
)[0]
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise RuntimeError("Unable to determine version.")
|
raise RuntimeError("Unable to determine version.")
|
||||||
|
|
||||||
@ -96,9 +94,7 @@ setup_kwargs = {
|
|||||||
"entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]},
|
"entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
env_dependency = (
|
env_dependency = '; sys_platform != "win32" and implementation_name == "cpython"'
|
||||||
'; sys_platform != "win32" and implementation_name == "cpython"'
|
|
||||||
)
|
|
||||||
ujson = "ujson>=1.35" + env_dependency
|
ujson = "ujson>=1.35" + env_dependency
|
||||||
uvloop = "uvloop>=0.15.0" + env_dependency
|
uvloop = "uvloop>=0.15.0" + env_dependency
|
||||||
types_ujson = "types-ujson" + env_dependency
|
types_ujson = "types-ujson" + env_dependency
|
||||||
|
@ -16,9 +16,7 @@ class TestSanicRouteResolution:
|
|||||||
async def test_resolve_route_no_arg_string_path(
|
async def test_resolve_route_no_arg_string_path(
|
||||||
self, sanic_router, route_generator, benchmark
|
self, sanic_router, route_generator, benchmark
|
||||||
):
|
):
|
||||||
simple_routes = route_generator.generate_random_direct_route(
|
simple_routes = route_generator.generate_random_direct_route(max_route_depth=4)
|
||||||
max_route_depth=4
|
|
||||||
)
|
|
||||||
router, simple_routes = sanic_router(route_details=simple_routes)
|
router, simple_routes = sanic_router(route_details=simple_routes)
|
||||||
route_to_call = choice(simple_routes)
|
route_to_call = choice(simple_routes)
|
||||||
request = Request(
|
request = Request(
|
||||||
@ -52,9 +50,7 @@ class TestSanicRouteResolution:
|
|||||||
)
|
)
|
||||||
router, typed_routes = sanic_router(route_details=typed_routes)
|
router, typed_routes = sanic_router(route_details=typed_routes)
|
||||||
route_to_call = choice(typed_routes)
|
route_to_call = choice(typed_routes)
|
||||||
url = route_generator.generate_url_for_template(
|
url = route_generator.generate_url_for_template(template=route_to_call[-1])
|
||||||
template=route_to_call[-1]
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"{route_to_call[-1]} -> {url}")
|
print(f"{route_to_call[-1]} -> {url}")
|
||||||
request = Request(
|
request = Request(
|
||||||
|
@ -51,9 +51,7 @@ def selfsigned(key, common_name, san):
|
|||||||
.not_valid_before(datetime.utcnow())
|
.not_valid_before(datetime.utcnow())
|
||||||
.not_valid_after(datetime.utcnow() + timedelta(days=365.25 * 8))
|
.not_valid_after(datetime.utcnow() + timedelta(days=365.25 * 8))
|
||||||
.add_extension(
|
.add_extension(
|
||||||
KeyUsage(
|
KeyUsage(True, False, False, False, False, False, False, False, False),
|
||||||
True, False, False, False, False, False, False, False, False
|
|
||||||
),
|
|
||||||
critical=True,
|
critical=True,
|
||||||
)
|
)
|
||||||
.add_extension(
|
.add_extension(
|
||||||
|
@ -13,9 +13,7 @@ class RawClient:
|
|||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
self.reader, self.writer = await asyncio.open_connection(
|
self.reader, self.writer = await asyncio.open_connection(self.host, self.port)
|
||||||
self.host, self.port
|
|
||||||
)
|
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
@ -39,8 +37,4 @@ class RawClient:
|
|||||||
return await self.reader.read(nbytes)
|
return await self.reader.read(nbytes)
|
||||||
|
|
||||||
def _clean(self, message: str) -> str:
|
def _clean(self, message: str) -> str:
|
||||||
return (
|
return dedent(message).lstrip("\n").replace("\n", self.CRLF.decode("utf-8"))
|
||||||
dedent(message)
|
|
||||||
.lstrip("\n")
|
|
||||||
.replace("\n", self.CRLF.decode("utf-8"))
|
|
||||||
)
|
|
||||||
|
@ -42,14 +42,10 @@ async def _handler(request):
|
|||||||
|
|
||||||
|
|
||||||
TYPE_TO_GENERATOR_MAP = {
|
TYPE_TO_GENERATOR_MAP = {
|
||||||
"str": lambda: "".join(
|
"str": lambda: "".join([random.choice(string.ascii_lowercase) for _ in range(4)]),
|
||||||
[random.choice(string.ascii_lowercase) for _ in range(4)]
|
|
||||||
),
|
|
||||||
"int": lambda: random.choice(range(1000000)),
|
"int": lambda: random.choice(range(1000000)),
|
||||||
"float": lambda: random.random(),
|
"float": lambda: random.random(),
|
||||||
"alpha": lambda: "".join(
|
"alpha": lambda: "".join([random.choice(string.ascii_lowercase) for _ in range(4)]),
|
||||||
[random.choice(string.ascii_lowercase) for _ in range(4)]
|
|
||||||
),
|
|
||||||
"uuid": lambda: str(uuid.uuid1()),
|
"uuid": lambda: str(uuid.uuid1()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +84,7 @@ async def test_http_receiver_run_request(app: Sanic, http_request: Request):
|
|||||||
|
|
||||||
app.__class__ = mock_handle
|
app.__class__ = mock_handle
|
||||||
receiver = generate_http_receiver(app, http_request)
|
receiver = generate_http_receiver(app, http_request)
|
||||||
receiver.protocol.quic_event_received(
|
receiver.protocol.quic_event_received(ProtocolNegotiated(alpn_protocol="h3"))
|
||||||
ProtocolNegotiated(alpn_protocol="h3")
|
|
||||||
)
|
|
||||||
await receiver.run()
|
await receiver.run()
|
||||||
handler.assert_awaited_once_with(receiver.request)
|
handler.assert_awaited_once_with(receiver.request)
|
||||||
|
|
||||||
@ -99,9 +97,7 @@ async def test_http_receiver_run_exception(app: Sanic, http_request: Request):
|
|||||||
|
|
||||||
app.__class__ = mock_handle
|
app.__class__ = mock_handle
|
||||||
receiver = generate_http_receiver(app, http_request)
|
receiver = generate_http_receiver(app, http_request)
|
||||||
receiver.protocol.quic_event_received(
|
receiver.protocol.quic_event_received(ProtocolNegotiated(alpn_protocol="h3"))
|
||||||
ProtocolNegotiated(alpn_protocol="h3")
|
|
||||||
)
|
|
||||||
exception = Exception("Oof")
|
exception = Exception("Oof")
|
||||||
await receiver.run(exception)
|
await receiver.run(exception)
|
||||||
handler.assert_awaited_once_with(receiver.request, exception)
|
handler.assert_awaited_once_with(receiver.request, exception)
|
||||||
@ -139,9 +135,7 @@ def test_http_receiver_receive_body(app: Sanic, http_request: Request):
|
|||||||
receiver.receive_body(b"..")
|
receiver.receive_body(b"..")
|
||||||
assert receiver.request.body == b"...."
|
assert receiver.request.body == b"...."
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(PayloadTooLarge, match="Request body exceeds the size limit"):
|
||||||
PayloadTooLarge, match="Request body exceeds the size limit"
|
|
||||||
):
|
|
||||||
receiver.receive_body(b"..")
|
receiver.receive_body(b"..")
|
||||||
|
|
||||||
|
|
||||||
@ -176,17 +170,13 @@ async def test_send_headers(app: Sanic, http_request: Request):
|
|||||||
send_headers_mock = Mock()
|
send_headers_mock = Mock()
|
||||||
existing_send_headers = H3Connection.send_headers
|
existing_send_headers = H3Connection.send_headers
|
||||||
receiver = generate_http_receiver(app, http_request)
|
receiver = generate_http_receiver(app, http_request)
|
||||||
receiver.protocol.quic_event_received(
|
receiver.protocol.quic_event_received(ProtocolNegotiated(alpn_protocol="h3"))
|
||||||
ProtocolNegotiated(alpn_protocol="h3")
|
|
||||||
)
|
|
||||||
|
|
||||||
http_request._protocol = receiver.protocol
|
http_request._protocol = receiver.protocol
|
||||||
|
|
||||||
def send_headers(*args, **kwargs):
|
def send_headers(*args, **kwargs):
|
||||||
send_headers_mock(*args, **kwargs)
|
send_headers_mock(*args, **kwargs)
|
||||||
return existing_send_headers(
|
return existing_send_headers(receiver.protocol.connection, *args, **kwargs)
|
||||||
receiver.protocol.connection, *args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
receiver.protocol.connection.send_headers = send_headers
|
receiver.protocol.connection.send_headers = send_headers
|
||||||
receiver.head_only = False
|
receiver.head_only = False
|
||||||
@ -310,10 +300,7 @@ def test_request_header_encoding(app):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert exc_info.value.status_code == 400
|
assert exc_info.value.status_code == 400
|
||||||
assert (
|
assert str(exc_info.value) == "Header names may only contain US-ASCII characters."
|
||||||
str(exc_info.value)
|
|
||||||
== "Header names may only contain US-ASCII characters."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_request_url_encoding(app):
|
def test_request_url_encoding(app):
|
||||||
|
@ -5,9 +5,7 @@ import timeit
|
|||||||
|
|
||||||
from sanic.response import json
|
from sanic.response import json
|
||||||
|
|
||||||
currentdir = os.path.dirname(
|
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
os.path.abspath(inspect.getfile(inspect.currentframe()))
|
|
||||||
)
|
|
||||||
sys.path.insert(0, currentdir + "/../../../")
|
sys.path.insert(0, currentdir + "/../../../")
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,9 +5,7 @@ import sys
|
|||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json
|
from sanic.response import json
|
||||||
|
|
||||||
currentdir = os.path.dirname(
|
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
os.path.abspath(inspect.getfile(inspect.currentframe()))
|
|
||||||
)
|
|
||||||
sys.path.insert(0, currentdir + "/../../../")
|
sys.path.insert(0, currentdir + "/../../../")
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,9 +6,7 @@ from sanic import Sanic
|
|||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
|
|
||||||
currentdir = os.path.dirname(
|
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
os.path.abspath(inspect.getfile(inspect.currentframe()))
|
|
||||||
)
|
|
||||||
sys.path.insert(0, currentdir + "/../../../")
|
sys.path.insert(0, currentdir + "/../../../")
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,9 +97,7 @@ def test_create_server_no_startup(app: Sanic):
|
|||||||
asyncio_server_kwargs={"start_serving": False},
|
asyncio_server_kwargs={"start_serving": False},
|
||||||
)
|
)
|
||||||
srv = loop.run_until_complete(asyncio_srv_coro)
|
srv = loop.run_until_complete(asyncio_srv_coro)
|
||||||
message = (
|
message = "Cannot run Sanic server without first running await server.startup()"
|
||||||
"Cannot run Sanic server without first running await server.startup()"
|
|
||||||
)
|
|
||||||
with pytest.raises(SanicException, match=message):
|
with pytest.raises(SanicException, match=message):
|
||||||
loop.run_until_complete(srv.start_serving())
|
loop.run_until_complete(srv.start_serving())
|
||||||
|
|
||||||
@ -212,14 +210,8 @@ def test_app_websocket_parameters(websocket_protocol_mock, app: Sanic):
|
|||||||
websocket_protocol_call_args = websocket_protocol_mock.call_args
|
websocket_protocol_call_args = websocket_protocol_mock.call_args
|
||||||
ws_kwargs = websocket_protocol_call_args[1]
|
ws_kwargs = websocket_protocol_call_args[1]
|
||||||
assert ws_kwargs["websocket_max_size"] == app.config.WEBSOCKET_MAX_SIZE
|
assert ws_kwargs["websocket_max_size"] == app.config.WEBSOCKET_MAX_SIZE
|
||||||
assert (
|
assert ws_kwargs["websocket_ping_timeout"] == app.config.WEBSOCKET_PING_TIMEOUT
|
||||||
ws_kwargs["websocket_ping_timeout"]
|
assert ws_kwargs["websocket_ping_interval"] == app.config.WEBSOCKET_PING_INTERVAL
|
||||||
== app.config.WEBSOCKET_PING_TIMEOUT
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
ws_kwargs["websocket_ping_interval"]
|
|
||||||
== app.config.WEBSOCKET_PING_INTERVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_handle_request_with_nested_exception(app: Sanic, monkeypatch):
|
def test_handle_request_with_nested_exception(app: Sanic, monkeypatch):
|
||||||
@ -228,9 +220,7 @@ def test_handle_request_with_nested_exception(app: Sanic, monkeypatch):
|
|||||||
def mock_error_handler_response(*args, **kwargs):
|
def mock_error_handler_response(*args, **kwargs):
|
||||||
raise Exception(err_msg)
|
raise Exception(err_msg)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response)
|
||||||
app.error_handler, "response", mock_error_handler_response
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
@ -247,9 +237,7 @@ def test_handle_request_with_nested_exception_debug(app: Sanic, monkeypatch):
|
|||||||
def mock_error_handler_response(*args, **kwargs):
|
def mock_error_handler_response(*args, **kwargs):
|
||||||
raise Exception(err_msg)
|
raise Exception(err_msg)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response)
|
||||||
app.error_handler, "response", mock_error_handler_response
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
@ -263,15 +251,11 @@ def test_handle_request_with_nested_exception_debug(app: Sanic, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_handle_request_with_nested_sanic_exception(
|
def test_handle_request_with_nested_sanic_exception(app: Sanic, monkeypatch, caplog):
|
||||||
app: Sanic, monkeypatch, caplog
|
|
||||||
):
|
|
||||||
def mock_error_handler_response(*args, **kwargs):
|
def mock_error_handler_response(*args, **kwargs):
|
||||||
raise SanicException("Mock SanicException")
|
raise SanicException("Mock SanicException")
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response)
|
||||||
app.error_handler, "response", mock_error_handler_response
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
@ -326,9 +310,7 @@ def test_app_registry_wrong_type():
|
|||||||
def test_app_registry_name_reuse():
|
def test_app_registry_name_reuse():
|
||||||
Sanic("test")
|
Sanic("test")
|
||||||
Sanic.test_mode = False
|
Sanic.test_mode = False
|
||||||
with pytest.raises(
|
with pytest.raises(SanicException, match='Sanic app name "test" already in use.'):
|
||||||
SanicException, match='Sanic app name "test" already in use.'
|
|
||||||
):
|
|
||||||
Sanic("test")
|
Sanic("test")
|
||||||
Sanic.test_mode = True
|
Sanic.test_mode = True
|
||||||
Sanic("test")
|
Sanic("test")
|
||||||
@ -361,9 +343,7 @@ def test_get_app_does_not_exist():
|
|||||||
|
|
||||||
|
|
||||||
def test_get_app_does_not_exist_force_create():
|
def test_get_app_does_not_exist_force_create():
|
||||||
assert isinstance(
|
assert isinstance(Sanic.get_app("does-not-exist", force_create=True), Sanic)
|
||||||
Sanic.get_app("does-not-exist", force_create=True), Sanic
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_app_default():
|
def test_get_app_default():
|
||||||
@ -372,9 +352,7 @@ def test_get_app_default():
|
|||||||
|
|
||||||
|
|
||||||
def test_get_app_no_default():
|
def test_get_app_no_default():
|
||||||
with pytest.raises(
|
with pytest.raises(SanicException, match="No Sanic apps have been registered."):
|
||||||
SanicException, match="No Sanic apps have been registered."
|
|
||||||
):
|
|
||||||
Sanic.get_app()
|
Sanic.get_app()
|
||||||
|
|
||||||
|
|
||||||
@ -383,9 +361,7 @@ def test_get_app_default_ambiguous():
|
|||||||
Sanic("test2")
|
Sanic("test2")
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
SanicException,
|
SanicException,
|
||||||
match=re.escape(
|
match=re.escape('Multiple Sanic apps found, use Sanic.get_app("app_name")'),
|
||||||
'Multiple Sanic apps found, use Sanic.get_app("app_name")'
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
Sanic.get_app()
|
Sanic.get_app()
|
||||||
|
|
||||||
@ -417,8 +393,7 @@ def test_bad_custom_config():
|
|||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
SanicException,
|
SanicException,
|
||||||
match=(
|
match=(
|
||||||
"When instantiating Sanic with config, you cannot also pass "
|
"When instantiating Sanic with config, you cannot also pass " "env_prefix"
|
||||||
"env_prefix"
|
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
Sanic("test", config=1, env_prefix=1)
|
Sanic("test", config=1, env_prefix=1)
|
||||||
@ -500,9 +475,7 @@ def test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
counter = Counter([(r[1], r[2]) for r in caplog.record_tuples])
|
counter = Counter([(r[1], r[2]) for r in caplog.record_tuples])
|
||||||
modified = sum(
|
modified = sum(1 for app in apps if not isinstance(app.config.USE_UVLOOP, Default))
|
||||||
1 for app in apps if not isinstance(app.config.USE_UVLOOP, Default)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert counter[(logging.WARNING, message)] == modified
|
assert counter[(logging.WARNING, message)] == modified
|
||||||
|
|
||||||
@ -559,12 +532,8 @@ def test_no_workers(app: Sanic):
|
|||||||
{"auto_reload": True},
|
{"auto_reload": True},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_cannot_run_single_process_and_workers_or_auto_reload(
|
def test_cannot_run_single_process_and_workers_or_auto_reload(app: Sanic, extra):
|
||||||
app: Sanic, extra
|
message = "Single process cannot be run with multiple workers or auto-reload"
|
||||||
):
|
|
||||||
message = (
|
|
||||||
"Single process cannot be run with multiple workers or auto-reload"
|
|
||||||
)
|
|
||||||
with pytest.raises(RuntimeError, match=message):
|
with pytest.raises(RuntimeError, match=message):
|
||||||
app.run(single_process=True, **extra)
|
app.run(single_process=True, **extra)
|
||||||
|
|
||||||
|
@ -352,9 +352,7 @@ async def test_websocket_bytes_receive(send, receive, message_stack):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_websocket_accept_with_no_subprotocols(
|
async def test_websocket_accept_with_no_subprotocols(send, receive, message_stack):
|
||||||
send, receive, message_stack
|
|
||||||
):
|
|
||||||
ws = WebSocketConnection(send, receive)
|
ws = WebSocketConnection(send, receive)
|
||||||
await ws.accept()
|
await ws.accept()
|
||||||
|
|
||||||
|
@ -28,9 +28,7 @@ def test_bp_group_with_additional_route_params(app: Sanic):
|
|||||||
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
|
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
|
||||||
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
||||||
|
|
||||||
@blueprint_1.route(
|
@blueprint_1.route("/request_path", methods=frozenset({"PUT", "POST"}), version=2)
|
||||||
"/request_path", methods=frozenset({"PUT", "POST"}), version=2
|
|
||||||
)
|
|
||||||
def blueprint_1_v2_method_with_put_and_post(request: Request):
|
def blueprint_1_v2_method_with_put_and_post(request: Request):
|
||||||
if request.method == "PUT":
|
if request.method == "PUT":
|
||||||
return text("PUT_OK")
|
return text("PUT_OK")
|
||||||
@ -46,9 +44,7 @@ def test_bp_group_with_additional_route_params(app: Sanic):
|
|||||||
elif request.method == "PATCH":
|
elif request.method == "PATCH":
|
||||||
return text(f"PATCH_{param}")
|
return text(f"PATCH_{param}")
|
||||||
|
|
||||||
blueprint_group = Blueprint.group(
|
blueprint_group = Blueprint.group(blueprint_1, blueprint_2, url_prefix="/api")
|
||||||
blueprint_1, blueprint_2, url_prefix="/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
@blueprint_group.middleware("request")
|
@blueprint_group.middleware("request")
|
||||||
def authenticate_request(request: Request):
|
def authenticate_request(request: Request):
|
||||||
@ -68,15 +64,11 @@ def test_bp_group_with_additional_route_params(app: Sanic):
|
|||||||
app.blueprint(blueprint_group)
|
app.blueprint(blueprint_group)
|
||||||
|
|
||||||
header = {"authorization": " ".join(["Basic", AUTH])}
|
header = {"authorization": " ".join(["Basic", AUTH])}
|
||||||
_, response = app.test_client.put(
|
_, response = app.test_client.put("/v2/api/bp1/request_path", headers=header)
|
||||||
"/v2/api/bp1/request_path", headers=header
|
|
||||||
)
|
|
||||||
assert response.text == "PUT_OK"
|
assert response.text == "PUT_OK"
|
||||||
assert response.headers.get("x-test-middleware") == "value"
|
assert response.headers.get("x-test-middleware") == "value"
|
||||||
|
|
||||||
_, response = app.test_client.post(
|
_, response = app.test_client.post("/v2/api/bp1/request_path", headers=header)
|
||||||
"/v2/api/bp1/request_path", headers=header
|
|
||||||
)
|
|
||||||
assert response.text == "POST_OK"
|
assert response.text == "POST_OK"
|
||||||
|
|
||||||
_, response = app.test_client.delete("/api/bp2/route/bp2", headers=header)
|
_, response = app.test_client.delete("/api/bp2/route/bp2", headers=header)
|
||||||
@ -109,9 +101,7 @@ def test_bp_group(app: Sanic):
|
|||||||
def blueprint_2_error(request: Request):
|
def blueprint_2_error(request: Request):
|
||||||
raise ServerError("Error")
|
raise ServerError("Error")
|
||||||
|
|
||||||
blueprint_group_1 = Blueprint.group(
|
blueprint_group_1 = Blueprint.group(blueprint_1, blueprint_2, url_prefix="/bp")
|
||||||
blueprint_1, blueprint_2, url_prefix="/bp"
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint_3 = Blueprint("blueprint_3", url_prefix="/bp3")
|
blueprint_3 = Blueprint("blueprint_3", url_prefix="/bp3")
|
||||||
|
|
||||||
@ -213,9 +203,7 @@ def test_bp_group_list_operations(app: Sanic):
|
|||||||
def blueprint_2_default_route(request):
|
def blueprint_2_default_route(request):
|
||||||
return text("BP2_OK")
|
return text("BP2_OK")
|
||||||
|
|
||||||
blueprint_group_1 = Blueprint.group(
|
blueprint_group_1 = Blueprint.group(blueprint_1, blueprint_2, url_prefix="/bp")
|
||||||
blueprint_1, blueprint_2, url_prefix="/bp"
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprint_3 = Blueprint("blueprint_2", url_prefix="/bp3")
|
blueprint_3 = Blueprint("blueprint_2", url_prefix="/bp3")
|
||||||
|
|
||||||
@ -247,9 +235,7 @@ def test_bp_group_as_list():
|
|||||||
def test_bp_group_as_nested_group():
|
def test_bp_group_as_nested_group():
|
||||||
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
|
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
|
||||||
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
||||||
blueprint_group_1 = Blueprint.group(
|
blueprint_group_1 = Blueprint.group(Blueprint.group(blueprint_1, blueprint_2))
|
||||||
Blueprint.group(blueprint_1, blueprint_2)
|
|
||||||
)
|
|
||||||
assert len(blueprint_group_1) == 1
|
assert len(blueprint_group_1) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -259,9 +245,7 @@ def test_blueprint_group_insert():
|
|||||||
)
|
)
|
||||||
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
||||||
blueprint_3 = Blueprint("blueprint_3", url_prefix=None)
|
blueprint_3 = Blueprint("blueprint_3", url_prefix=None)
|
||||||
group = BlueprintGroup(
|
group = BlueprintGroup(url_prefix="/test", version=1.3, strict_slashes=False)
|
||||||
url_prefix="/test", version=1.3, strict_slashes=False
|
|
||||||
)
|
|
||||||
group.insert(0, blueprint_1)
|
group.insert(0, blueprint_1)
|
||||||
group.insert(0, blueprint_2)
|
group.insert(0, blueprint_2)
|
||||||
group.insert(0, blueprint_3)
|
group.insert(0, blueprint_3)
|
||||||
@ -350,12 +334,8 @@ async def test_multiple_nested_bp_group():
|
|||||||
bp1.add_route(lambda _: ..., "/", name="route1")
|
bp1.add_route(lambda _: ..., "/", name="route1")
|
||||||
bp2.add_route(lambda _: ..., "/", name="route2")
|
bp2.add_route(lambda _: ..., "/", name="route2")
|
||||||
|
|
||||||
group_a = Blueprint.group(
|
group_a = Blueprint.group(bp1, bp2, url_prefix="/group-a", name_prefix="group-a")
|
||||||
bp1, bp2, url_prefix="/group-a", name_prefix="group-a"
|
group_b = Blueprint.group(bp1, bp2, url_prefix="/group-b", name_prefix="group-b")
|
||||||
)
|
|
||||||
group_b = Blueprint.group(
|
|
||||||
bp1, bp2, url_prefix="/group-b", name_prefix="group-b"
|
|
||||||
)
|
|
||||||
|
|
||||||
app = Sanic("PropTest")
|
app = Sanic("PropTest")
|
||||||
app.blueprint(group_a)
|
app.blueprint(group_a)
|
||||||
|
@ -751,9 +751,7 @@ def test_bp_group_with_default_url_prefix(app: Sanic):
|
|||||||
def api_v1_info(request):
|
def api_v1_info(request):
|
||||||
return text("api_version: v1")
|
return text("api_version: v1")
|
||||||
|
|
||||||
bp_api_v1_group = Blueprint.group(
|
bp_api_v1_group = Blueprint.group(bp_api_v1, bp_resources_group, url_prefix="/v1")
|
||||||
bp_api_v1, bp_resources_group, url_prefix="/v1"
|
|
||||||
)
|
|
||||||
bp_api_group = Blueprint.group(bp_api_v1_group, url_prefix="/api")
|
bp_api_group = Blueprint.group(bp_api_v1_group, url_prefix="/api")
|
||||||
app.blueprint(bp_api_group)
|
app.blueprint(bp_api_group)
|
||||||
|
|
||||||
@ -794,9 +792,7 @@ def test_blueprint_middleware_with_args(app: Sanic):
|
|||||||
)
|
)
|
||||||
assert response.text == "value"
|
assert response.text == "value"
|
||||||
|
|
||||||
_, response = app.test_client.get(
|
_, response = app.test_client.get("/wa", headers={"content-type": "plain/text"})
|
||||||
"/wa", headers={"content-type": "plain/text"}
|
|
||||||
)
|
|
||||||
assert response.json.get("test") == "value"
|
assert response.json.get("test") == "value"
|
||||||
|
|
||||||
|
|
||||||
@ -985,13 +981,9 @@ def test_blueprint_group_strict_slashes():
|
|||||||
app = Sanic(name="blueprint-group-test")
|
app = Sanic(name="blueprint-group-test")
|
||||||
bp1 = Blueprint(name="bp1", url_prefix=None, strict_slashes=False)
|
bp1 = Blueprint(name="bp1", url_prefix=None, strict_slashes=False)
|
||||||
|
|
||||||
bp2 = Blueprint(
|
bp2 = Blueprint(name="bp2", version=3, url_prefix="/bp2", strict_slashes=None)
|
||||||
name="bp2", version=3, url_prefix="/bp2", strict_slashes=None
|
|
||||||
)
|
|
||||||
|
|
||||||
bp3 = Blueprint(
|
bp3 = Blueprint(name="bp3", version=None, url_prefix="/bp3/", strict_slashes=None)
|
||||||
name="bp3", version=None, url_prefix="/bp3/", strict_slashes=None
|
|
||||||
)
|
|
||||||
|
|
||||||
@bp1.get("/r1")
|
@bp1.get("/r1")
|
||||||
async def bp1_r1(request):
|
async def bp1_r1(request):
|
||||||
|
@ -133,9 +133,7 @@ def test_add_converter_multiple_times(caplog):
|
|||||||
def converter():
|
def converter():
|
||||||
...
|
...
|
||||||
|
|
||||||
message = (
|
message = "Configuration value converter 'converter' has already been registered"
|
||||||
"Configuration value converter 'converter' has already been registered"
|
|
||||||
)
|
|
||||||
config = Config()
|
config = Config()
|
||||||
config.register_type(converter)
|
config.register_type(converter)
|
||||||
with caplog.at_level(logging.WARNING):
|
with caplog.at_level(logging.WARNING):
|
||||||
@ -306,14 +304,10 @@ async def test_config_access_log_passing_in_create_server(app: Sanic):
|
|||||||
async def _request(sanic, loop):
|
async def _request(sanic, loop):
|
||||||
app.stop()
|
app.stop()
|
||||||
|
|
||||||
await app.create_server(
|
await app.create_server(port=1341, access_log=False, return_asyncio_server=True)
|
||||||
port=1341, access_log=False, return_asyncio_server=True
|
|
||||||
)
|
|
||||||
assert app.config.ACCESS_LOG is False
|
assert app.config.ACCESS_LOG is False
|
||||||
|
|
||||||
await app.create_server(
|
await app.create_server(port=1342, access_log=True, return_asyncio_server=True)
|
||||||
port=1342, access_log=True, return_asyncio_server=True
|
|
||||||
)
|
|
||||||
assert app.config.ACCESS_LOG is True
|
assert app.config.ACCESS_LOG is True
|
||||||
|
|
||||||
|
|
||||||
@ -334,9 +328,7 @@ def test_config_rewrite_keep_alive():
|
|||||||
|
|
||||||
_test_setting_as_dict = {"TEST_SETTING_VALUE": 1}
|
_test_setting_as_dict = {"TEST_SETTING_VALUE": 1}
|
||||||
_test_setting_as_class = type("C", (), {"TEST_SETTING_VALUE": 1})
|
_test_setting_as_class = type("C", (), {"TEST_SETTING_VALUE": 1})
|
||||||
_test_setting_as_module = str(
|
_test_setting_as_module = str(Path(__file__).parent / "static/app_test_config.py")
|
||||||
Path(__file__).parent / "static/app_test_config.py"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -42,9 +42,7 @@ async def test_cookies_asgi(app):
|
|||||||
response.cookies["right_back"] = "at you"
|
response.cookies["right_back"] = "at you"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
request, response = await app.asgi_client.get(
|
request, response = await app.asgi_client.get("/", cookies={"test": "working!"})
|
||||||
"/", cookies={"test": "working!"}
|
|
||||||
)
|
|
||||||
response_cookies = SimpleCookie()
|
response_cookies = SimpleCookie()
|
||||||
response_cookies.load(response.headers.get("set-cookie", {}))
|
response_cookies.load(response.headers.get("set-cookie", {}))
|
||||||
|
|
||||||
@ -101,9 +99,7 @@ def test_cookie_options(app):
|
|||||||
response = text("OK")
|
response = text("OK")
|
||||||
response.cookies["test"] = "at you"
|
response.cookies["test"] = "at you"
|
||||||
response.cookies["test"]["httponly"] = True
|
response.cookies["test"]["httponly"] = True
|
||||||
response.cookies["test"]["expires"] = datetime.now() + timedelta(
|
response.cookies["test"]["expires"] = datetime.now() + timedelta(seconds=10)
|
||||||
seconds=10
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
@ -179,17 +175,11 @@ def test_cookie_max_age(app, max_age):
|
|||||||
response.cookies["test"]["max-age"] = max_age
|
response.cookies["test"]["max-age"] = max_age
|
||||||
return response
|
return response
|
||||||
|
|
||||||
request, response = app.test_client.get(
|
request, response = app.test_client.get("/", cookies=cookies, raw_cookies=True)
|
||||||
"/", cookies=cookies, raw_cookies=True
|
|
||||||
)
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
cookie = response.cookies.get("test")
|
cookie = response.cookies.get("test")
|
||||||
if (
|
if str(max_age).isdigit() and int(max_age) == float(max_age) and int(max_age) != 0:
|
||||||
str(max_age).isdigit()
|
|
||||||
and int(max_age) == float(max_age)
|
|
||||||
and int(max_age) != 0
|
|
||||||
):
|
|
||||||
cookie_expires = datetime.utcfromtimestamp(
|
cookie_expires = datetime.utcfromtimestamp(
|
||||||
response.raw_cookies["test"].expires
|
response.raw_cookies["test"].expires
|
||||||
).replace(microsecond=0)
|
).replace(microsecond=0)
|
||||||
@ -202,9 +192,8 @@ def test_cookie_max_age(app, max_age):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert cookie == "pass"
|
assert cookie == "pass"
|
||||||
assert (
|
assert cookie_expires == expires or cookie_expires == expires + timedelta(
|
||||||
cookie_expires == expires
|
seconds=-1
|
||||||
or cookie_expires == expires + timedelta(seconds=-1)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert cookie is None
|
assert cookie is None
|
||||||
@ -221,9 +210,7 @@ def test_cookie_bad_max_age(app, max_age):
|
|||||||
response.cookies["test"]["max-age"] = max_age
|
response.cookies["test"]["max-age"] = max_age
|
||||||
return response
|
return response
|
||||||
|
|
||||||
request, response = app.test_client.get(
|
request, response = app.test_client.get("/", cookies=cookies, raw_cookies=True)
|
||||||
"/", cookies=cookies, raw_cookies=True
|
|
||||||
)
|
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
|
|
||||||
|
|
||||||
@ -239,9 +226,7 @@ def test_cookie_expires(app: Sanic, expires: timedelta):
|
|||||||
response.cookies["test"]["expires"] = expires_time
|
response.cookies["test"]["expires"] = expires_time
|
||||||
return response
|
return response
|
||||||
|
|
||||||
request, response = app.test_client.get(
|
request, response = app.test_client.get("/", cookies=cookies, raw_cookies=True)
|
||||||
"/", cookies=cookies, raw_cookies=True
|
|
||||||
)
|
|
||||||
|
|
||||||
cookie_expires = datetime.utcfromtimestamp(
|
cookie_expires = datetime.utcfromtimestamp(
|
||||||
response.raw_cookies["test"].expires
|
response.raw_cookies["test"].expires
|
||||||
@ -399,17 +384,13 @@ def test_bad_cookie_prarms():
|
|||||||
ServerError,
|
ServerError,
|
||||||
match="Cannot set host_prefix on a cookie unless path='/'",
|
match="Cannot set host_prefix on a cookie unless path='/'",
|
||||||
):
|
):
|
||||||
jar.add_cookie(
|
jar.add_cookie("foo", "bar", host_prefix=True, secure=True, path="/foo")
|
||||||
"foo", "bar", host_prefix=True, secure=True, path="/foo"
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ServerError,
|
ServerError,
|
||||||
match="Cannot set host_prefix on a cookie with a defined domain",
|
match="Cannot set host_prefix on a cookie with a defined domain",
|
||||||
):
|
):
|
||||||
jar.add_cookie(
|
jar.add_cookie("foo", "bar", host_prefix=True, secure=True, domain="foo.bar")
|
||||||
"foo", "bar", host_prefix=True, secure=True, domain="foo.bar"
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ServerError,
|
ServerError,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user