Compare commits
1 Commits
ruff
...
websocket-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50cec6b41c |
@@ -19,7 +19,7 @@ import sys
|
|||||||
root_directory = os.path.dirname(os.getcwd())
|
root_directory = os.path.dirname(os.getcwd())
|
||||||
sys.path.insert(0, root_directory)
|
sys.path.insert(0, root_directory)
|
||||||
|
|
||||||
import sanic # noqa: E402
|
import sanic
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
|
|||||||
@@ -25,6 +25,5 @@ def key_exist_handler(request):
|
|||||||
|
|
||||||
return text("num does not exist in request")
|
return text("num does not exist in request")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||||
|
|||||||
@@ -69,6 +69,5 @@ async def runner(app: Sanic, app_server: AsyncioServer):
|
|||||||
app.is_running = False
|
app.is_running = False
|
||||||
app.is_stopping = True
|
app.is_stopping = True
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
https.run(port=HTTPS_PORT, debug=True)
|
https.run(port=HTTPS_PORT, debug=True)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
# Warning: This is a heavy process.
|
# Warning: This is a heavy process.
|
||||||
|
|
||||||
data = ""
|
data = ""
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ async def after_server_stop(app, loop):
|
|||||||
async def test(request):
|
async def test(request):
|
||||||
return response.json({"answer": "42"})
|
return response.json({"answer": "42"})
|
||||||
|
|
||||||
|
|
||||||
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(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from textwrap import indent
|
from textwrap import indent
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
[tool.ruff]
|
|
||||||
extend = "../pyproject.toml"
|
|
||||||
|
|
||||||
[tool.ruff.isort]
|
|
||||||
known-first-party = ["webapp"]
|
|
||||||
lines-after-imports = 1
|
|
||||||
lines-between-types = 1
|
|
||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from html5tagger import Builder, Document # type: ignore
|
from html5tagger import Builder, Document # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class BaseRenderer:
|
class BaseRenderer:
|
||||||
def __init__(self, base_title: str):
|
def __init__(self, base_title: str):
|
||||||
self.base_title = base_title
|
self.base_title = base_title
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from pygments.token import ( # Error,; Generic,; Number,; Operator,
|
|||||||
Token,
|
Token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SanicCodeStyle(Style):
|
class SanicCodeStyle(Style):
|
||||||
styles = {
|
styles = {
|
||||||
Token: "#777",
|
Token: "#777",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Generator
|
|||||||
from html5tagger import Builder
|
from html5tagger import Builder
|
||||||
from sanic import Request
|
from sanic import Request
|
||||||
|
|
||||||
|
|
||||||
class BaseLayout:
|
class BaseLayout:
|
||||||
def __init__(self, builder: Builder):
|
def __init__(self, builder: Builder):
|
||||||
self.builder = builder
|
self.builder = builder
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from datetime import datetime
|
|||||||
from html5tagger import Builder, E # type: ignore
|
from html5tagger import Builder, E # type: ignore
|
||||||
from sanic import Request
|
from sanic import Request
|
||||||
|
|
||||||
|
|
||||||
def do_footer(builder: Builder, request: Request) -> None:
|
def do_footer(builder: Builder, request: Request) -> None:
|
||||||
builder.footer(
|
builder.footer(
|
||||||
_pagination(request),
|
_pagination(request),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
from webapp.display.layouts.models import MenuItem
|
||||||
|
|
||||||
from html5tagger import Builder, E # type: ignore
|
from html5tagger import Builder, E # type: ignore
|
||||||
from sanic import Request
|
from sanic import Request
|
||||||
|
|
||||||
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 = [
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
from webapp.display.layouts.models import MenuItem
|
||||||
|
from webapp.display.text import slugify
|
||||||
|
|
||||||
from html5tagger import Builder, E # type: ignore
|
from html5tagger import Builder, E # type: ignore
|
||||||
from sanic import Request
|
from sanic import Request
|
||||||
|
|
||||||
from webapp.display.layouts.models import MenuItem
|
|
||||||
from webapp.display.text import slugify
|
|
||||||
|
|
||||||
def do_sidebar(builder: Builder, request: Request) -> None:
|
def do_sidebar(builder: Builder, request: Request) -> None:
|
||||||
builder.a(class_="burger")(E.span().span().span().span())
|
builder.a(class_="burger")(E.span().span().span().span())
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from sanic import Request
|
|||||||
|
|
||||||
from .base import BaseLayout
|
from .base import BaseLayout
|
||||||
|
|
||||||
|
|
||||||
class HomeLayout(BaseLayout):
|
class HomeLayout(BaseLayout):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def layout(
|
def layout(
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
from sanic import Request
|
|
||||||
|
|
||||||
from webapp.display.layouts.elements.footer import do_footer
|
from webapp.display.layouts.elements.footer import do_footer
|
||||||
from webapp.display.layouts.elements.navbar import do_navbar
|
from webapp.display.layouts.elements.navbar import do_navbar
|
||||||
from webapp.display.layouts.elements.sidebar import do_sidebar
|
from webapp.display.layouts.elements.sidebar import do_sidebar
|
||||||
|
|
||||||
|
from sanic import Request
|
||||||
|
|
||||||
from .base import BaseLayout
|
from .base import BaseLayout
|
||||||
|
|
||||||
|
|
||||||
class MainLayout(BaseLayout):
|
class MainLayout(BaseLayout):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def layout(
|
def layout(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from msgspec import Struct, field
|
from msgspec import Struct, field
|
||||||
|
|
||||||
|
|
||||||
class MenuItem(Struct, kw_only=False, omit_defaults=True):
|
class MenuItem(Struct, kw_only=False, omit_defaults=True):
|
||||||
label: str
|
label: str
|
||||||
path: str | None = None
|
path: str | None = None
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
from html5tagger import HTML, Builder, E # type: ignore
|
|
||||||
from mistune import HTMLRenderer, create_markdown, escape
|
from mistune import HTMLRenderer, create_markdown, escape
|
||||||
from mistune.directives import RSTDirective, TableOfContents
|
from mistune.directives import RSTDirective, TableOfContents
|
||||||
from mistune.util import safe_entity
|
from mistune.util import safe_entity
|
||||||
@@ -10,6 +8,8 @@ from pygments import highlight
|
|||||||
from pygments.formatters import html
|
from pygments.formatters import html
|
||||||
from pygments.lexers import get_lexer_by_name
|
from pygments.lexers import get_lexer_by_name
|
||||||
|
|
||||||
|
from html5tagger import HTML, Builder, E # type: ignore
|
||||||
|
|
||||||
from .code_style import SanicCodeStyle
|
from .code_style import SanicCodeStyle
|
||||||
from .plugins.attrs import Attributes
|
from .plugins.attrs import Attributes
|
||||||
from .plugins.columns import Column
|
from .plugins.columns import Column
|
||||||
@@ -20,6 +20,7 @@ from .plugins.span import span
|
|||||||
from .plugins.tabs import Tabs
|
from .plugins.tabs import Tabs
|
||||||
from .text import slugify
|
from .text import slugify
|
||||||
|
|
||||||
|
|
||||||
class DocsRenderer(HTMLRenderer):
|
class DocsRenderer(HTMLRenderer):
|
||||||
def block_code(self, code: str, info: str | None = None):
|
def block_code(self, code: str, info: str | None = None):
|
||||||
builder = Builder("Block")
|
builder = Builder("Block")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from html import escape
|
from html import escape
|
||||||
@@ -11,10 +10,12 @@ from html import escape
|
|||||||
from docstring_parser import Docstring, DocstringParam, DocstringRaises
|
from docstring_parser import Docstring, DocstringParam, DocstringRaises
|
||||||
from docstring_parser import parse as parse_docstring
|
from docstring_parser import parse as parse_docstring
|
||||||
from docstring_parser.common import DocstringExample
|
from docstring_parser.common import DocstringExample
|
||||||
|
|
||||||
from html5tagger import HTML, Builder, E # type: ignore
|
from html5tagger import HTML, Builder, E # type: ignore
|
||||||
|
|
||||||
from ..markdown import render_markdown, slugify
|
from ..markdown import render_markdown, slugify
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DocObject:
|
class DocObject:
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ from __future__ import annotations
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from webapp.display.base import BaseRenderer
|
||||||
|
|
||||||
from html5tagger import HTML, Builder # type: ignore
|
from html5tagger import HTML, Builder # type: ignore
|
||||||
from sanic import Request
|
from sanic import Request
|
||||||
|
|
||||||
from webapp.display.base import BaseRenderer
|
|
||||||
|
|
||||||
from ..layouts.base import BaseLayout
|
from ..layouts.base import BaseLayout
|
||||||
from .page import Page
|
from .page import Page
|
||||||
|
|
||||||
|
|
||||||
class PageRenderer(BaseRenderer):
|
class PageRenderer(BaseRenderer):
|
||||||
def render(self, request: Request, language: str, path: str) -> Builder:
|
def render(self, request: Request, language: str, path: str) -> Builder:
|
||||||
builder = self.get_builder(
|
builder = self.get_builder(
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ from re import Match
|
|||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from html5tagger import HTML, E
|
|
||||||
from mistune.block_parser import BlockParser
|
from mistune.block_parser import BlockParser
|
||||||
from mistune.core import BlockState
|
from mistune.core import BlockState
|
||||||
from mistune.directives import DirectivePlugin
|
from mistune.directives import DirectivePlugin
|
||||||
|
|
||||||
|
from html5tagger import HTML, E
|
||||||
|
|
||||||
|
|
||||||
class Attributes(DirectivePlugin):
|
class Attributes(DirectivePlugin):
|
||||||
def __call__(self, directive, md):
|
def __call__(self, directive, md):
|
||||||
directive.register("attrs", self.parse)
|
directive.register("attrs", self.parse)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from mistune.core import BlockState
|
|||||||
from mistune.directives import DirectivePlugin, RSTDirective
|
from mistune.directives import DirectivePlugin, RSTDirective
|
||||||
from mistune.markdown import Markdown
|
from mistune.markdown import Markdown
|
||||||
|
|
||||||
|
|
||||||
class Column(DirectivePlugin):
|
class Column(DirectivePlugin):
|
||||||
def parse(
|
def parse(
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
self, block: BlockParser, m: Match, state: BlockState
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from mistune.core import BlockState
|
|||||||
from mistune.directives import DirectivePlugin, RSTDirective
|
from mistune.directives import DirectivePlugin, RSTDirective
|
||||||
from mistune.markdown import Markdown
|
from mistune.markdown import Markdown
|
||||||
|
|
||||||
|
|
||||||
class Hook(DirectivePlugin):
|
class Hook(DirectivePlugin):
|
||||||
def __call__( # type: ignore
|
def __call__( # type: ignore
|
||||||
self, directive: RSTDirective, md: Markdown
|
self, directive: RSTDirective, md: Markdown
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ from re import Match
|
|||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from html5tagger import HTML, E
|
|
||||||
from mistune import HTMLRenderer
|
from mistune import HTMLRenderer
|
||||||
from mistune.block_parser import BlockParser
|
from mistune.block_parser import BlockParser
|
||||||
from mistune.core import BlockState
|
from mistune.core import BlockState
|
||||||
from mistune.directives import DirectivePlugin, RSTDirective
|
from mistune.directives import DirectivePlugin, RSTDirective
|
||||||
from mistune.markdown import Markdown
|
from mistune.markdown import Markdown
|
||||||
|
|
||||||
|
from html5tagger import HTML, E
|
||||||
|
|
||||||
|
|
||||||
class Mermaid(DirectivePlugin):
|
class Mermaid(DirectivePlugin):
|
||||||
def parse(
|
def parse(
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
self, block: BlockParser, m: Match, state: BlockState
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from html5tagger import HTML, E
|
|
||||||
from mistune.directives import Admonition
|
from mistune.directives import Admonition
|
||||||
|
|
||||||
|
from html5tagger import HTML, E
|
||||||
|
|
||||||
|
|
||||||
class Notification(Admonition):
|
class Notification(Admonition):
|
||||||
SUPPORTED_NAMES = {
|
SUPPORTED_NAMES = {
|
||||||
"success",
|
"success",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import re
|
|||||||
|
|
||||||
from mistune.markdown import Markdown
|
from mistune.markdown import Markdown
|
||||||
|
|
||||||
|
|
||||||
def parse_inline_span(inline, m: re.Match, state):
|
def parse_inline_span(inline, m: re.Match, state):
|
||||||
state.append_token(
|
state.append_token(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from mistune.core import BlockState
|
|||||||
from mistune.directives import DirectivePlugin, RSTDirective
|
from mistune.directives import DirectivePlugin, RSTDirective
|
||||||
from mistune.markdown import Markdown
|
from mistune.markdown import Markdown
|
||||||
|
|
||||||
|
|
||||||
class Tabs(DirectivePlugin):
|
class Tabs(DirectivePlugin):
|
||||||
def parse(
|
def parse(
|
||||||
self, block: BlockParser, m: Match, state: BlockState
|
self, block: BlockParser, m: Match, state: BlockState
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
from webapp.display.search.search import Searcher
|
||||||
|
|
||||||
from html5tagger import Builder, E # type: ignore
|
from html5tagger import Builder, E # type: ignore
|
||||||
from sanic import Request
|
from sanic import Request
|
||||||
|
|
||||||
from webapp.display.search.search import Searcher
|
|
||||||
|
|
||||||
from ..base import BaseRenderer
|
from ..base import BaseRenderer
|
||||||
from ..layouts.main import MainLayout
|
from ..layouts.main import MainLayout
|
||||||
|
|
||||||
|
|
||||||
class SearchRenderer(BaseRenderer):
|
class SearchRenderer(BaseRenderer):
|
||||||
def render(
|
def render(
|
||||||
self, request: Request, language: str, searcher: Searcher, full: bool
|
self, request: Request, language: str, searcher: Searcher, full: bool
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ from pathlib import Path
|
|||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from msgspec import Struct
|
from msgspec import Struct
|
||||||
|
|
||||||
from webapp.display.page import Page
|
from webapp.display.page import Page
|
||||||
|
|
||||||
|
|
||||||
class Stemmer:
|
class Stemmer:
|
||||||
STOP_WORDS: ClassVar[set[str]] = set(
|
STOP_WORDS: ClassVar[set[str]] = set(
|
||||||
"a about above after again against all am an and any are aren't as at be because been before being below between both but by can't cannot could couldn't did didn't do does doesn't doing don't down during each few for from further had hadn't has hasn't have haven't having he he'd he'll he's her here here's hers herself him himself his how how's i i'd i'll i'm i've if in into is isn't it it's its itself let's me more most mustn't my myself no nor not of off on once only or other ought our ours ourselves out over own same shan't she she'd she'll she's should shouldn't so some such than that that's the their theirs them themselves then there there's these they they'd they'll they're they've this those through to too under until up very was wasn't we we'd we'll we're we've were weren't what what's when when's where where's which while who who's whom why why's with won't would wouldn't you you'd you'll you're you've your yours yourself yourselves".split() # noqa: E501
|
"a about above after again against all am an and any are aren't as at be because been before being below between both but by can't cannot could couldn't did didn't do does doesn't doing don't down during each few for from further had hadn't has hasn't have haven't having he he'd he'll he's her here here's hers herself him himself his how how's i i'd i'll i'm i've if in into is isn't it it's its itself let's me more most mustn't my myself no nor not of off on once only or other ought our ours ourselves out over own same shan't she she'd she'll she's should shouldn't so some such than that that's the their theirs them themselves then there there's these they they'd they'll they're they've this those through to too under until up very was wasn't we we'd we'll we're we've were weren't what what's when when's where where's which while who who's whom why why's with won't would wouldn't you you'd you'll you're you've your yours yourself yourselves".split() # noqa: E501
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# from urllib.parse import unquote
|
# from urllib.parse import unquote
|
||||||
|
|
||||||
from sanic import Blueprint, Request, Sanic, html
|
|
||||||
|
|
||||||
from webapp.display.page import Page
|
from webapp.display.page import Page
|
||||||
from webapp.display.search.renderer import SearchRenderer
|
from webapp.display.search.renderer import SearchRenderer
|
||||||
from webapp.display.search.search import Document, Searcher, Stemmer
|
from webapp.display.search.search import Document, Searcher, Stemmer
|
||||||
|
|
||||||
|
from sanic import Blueprint, Request, Sanic, html
|
||||||
|
|
||||||
bp = Blueprint("search", url_prefix="/<language>/search")
|
bp = Blueprint("search", url_prefix="/<language>/search")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from msgspec import yaml
|
from msgspec import yaml
|
||||||
|
|
||||||
from webapp.display.layouts.models import GeneralConfig, MenuItem
|
from webapp.display.layouts.models import GeneralConfig, MenuItem
|
||||||
|
|
||||||
|
|
||||||
def load_menu(path: Path) -> list[MenuItem]:
|
def load_menu(path: Path) -> list[MenuItem]:
|
||||||
loaded = yaml.decode(path.read_bytes(), type=dict[str, list[MenuItem]])
|
loaded = yaml.decode(path.read_bytes(), type=dict[str, list[MenuItem]])
|
||||||
return loaded["root"]
|
return loaded["root"]
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from sanic import Request, Sanic, html, redirect
|
|
||||||
|
|
||||||
from webapp.display.layouts.models import MenuItem
|
from webapp.display.layouts.models import MenuItem
|
||||||
from webapp.display.page import Page, PageRenderer
|
from webapp.display.page import Page, PageRenderer
|
||||||
from webapp.endpoint.view import bp
|
from webapp.endpoint.view import bp
|
||||||
@@ -9,6 +7,9 @@ from webapp.worker.config import load_config, load_menu
|
|||||||
from webapp.worker.reload import setup_livereload
|
from webapp.worker.reload import setup_livereload
|
||||||
from webapp.worker.style import setup_style
|
from webapp.worker.style import setup_style
|
||||||
|
|
||||||
|
from sanic import Request, Sanic, html, redirect
|
||||||
|
|
||||||
|
|
||||||
def _compile_sidebar_order(items: list[MenuItem]) -> list[str]:
|
def _compile_sidebar_order(items: list[MenuItem]) -> list[str]:
|
||||||
order = []
|
order = []
|
||||||
for item in items:
|
for item in items:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import ujson
|
|||||||
|
|
||||||
from sanic import Request, Sanic, Websocket
|
from sanic import Request, Sanic, Websocket
|
||||||
|
|
||||||
|
|
||||||
def setup_livereload(app: Sanic) -> None:
|
def setup_livereload(app: Sanic) -> None:
|
||||||
@app.main_process_start
|
@app.main_process_start
|
||||||
async def main_process_start(app: Sanic):
|
async def main_process_start(app: Sanic):
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# from scss.compiler import compile_string
|
# from scss.compiler import compile_string
|
||||||
|
|
||||||
from pygments.formatters import html
|
from pygments.formatters import html
|
||||||
from sanic import Sanic
|
|
||||||
from sass import compile as compile_scss
|
from sass import compile as compile_scss
|
||||||
|
|
||||||
from webapp.display.code_style import SanicCodeStyle
|
from webapp.display.code_style import SanicCodeStyle
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
|
||||||
|
|
||||||
def setup_style(app: Sanic) -> None:
|
def setup_style(app: Sanic) -> None:
|
||||||
index = app.config.STYLE_DIR / "index.scss"
|
index = app.config.STYLE_DIR / "index.scss"
|
||||||
style_output = app.config.PUBLIC_DIR / "assets" / "style.css"
|
style_output = app.config.PUBLIC_DIR / "assets" / "style.css"
|
||||||
|
|||||||
@@ -2,19 +2,6 @@
|
|||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.ruff]
|
|
||||||
extend-select = ["I"]
|
|
||||||
ignore = ["D100", "D101", "D102", "D103", "E402", "E741", "F811", "F821"]
|
|
||||||
line-length = 79
|
|
||||||
show-source = true
|
|
||||||
show-fixes = true
|
|
||||||
|
|
||||||
[tool.ruff.isort]
|
|
||||||
known-first-party = ["sanic"]
|
|
||||||
known-third-party = ["pytest"]
|
|
||||||
lines-after-imports = 2
|
|
||||||
lines-between-types = 1
|
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 79
|
line-length = 79
|
||||||
|
|
||||||
|
|||||||
20
sanic/app.py
20
sanic/app.py
@@ -1369,6 +1369,12 @@ class Sanic(
|
|||||||
protocol = request.transport.get_protocol()
|
protocol = request.transport.get_protocol()
|
||||||
ws = await protocol.websocket_handshake(request, subprotocols)
|
ws = await protocol.websocket_handshake(request, subprotocols)
|
||||||
|
|
||||||
|
await self.dispatch(
|
||||||
|
"websocket.handler.before",
|
||||||
|
inline=True,
|
||||||
|
context={"request": request, "websocket": ws},
|
||||||
|
fail_not_found=False,
|
||||||
|
)
|
||||||
# schedule the application handler
|
# schedule the application handler
|
||||||
# its future is kept in self.websocket_tasks in case it
|
# its future is kept in self.websocket_tasks in case it
|
||||||
# needs to be cancelled due to the server being stopped
|
# needs to be cancelled due to the server being stopped
|
||||||
@@ -1377,10 +1383,24 @@ class Sanic(
|
|||||||
cancelled = False
|
cancelled = False
|
||||||
try:
|
try:
|
||||||
await fut
|
await fut
|
||||||
|
await self.dispatch(
|
||||||
|
"websocket.handler.after",
|
||||||
|
inline=True,
|
||||||
|
context={"request": request, "websocket": ws},
|
||||||
|
reverse=True,
|
||||||
|
fail_not_found=False,
|
||||||
|
)
|
||||||
except (CancelledError, ConnectionClosed): # type: ignore
|
except (CancelledError, ConnectionClosed): # type: ignore
|
||||||
cancelled = True
|
cancelled = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.error_handler.log(request, e)
|
self.error_handler.log(request, e)
|
||||||
|
await self.dispatch(
|
||||||
|
"websocket.handler.exception",
|
||||||
|
inline=True,
|
||||||
|
context={"request": request, "websocket": ws, "exception": e},
|
||||||
|
reverse=True,
|
||||||
|
fail_not_found=False,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
self.websocket_tasks.remove(fut)
|
self.websocket_tasks.remove(fut)
|
||||||
if cancelled:
|
if cancelled:
|
||||||
|
|||||||
@@ -349,7 +349,8 @@ def parse_content_header(value: str) -> Tuple[str, Options]:
|
|||||||
options: Dict[str, Union[int, str]] = {}
|
options: Dict[str, Union[int, str]] = {}
|
||||||
else:
|
else:
|
||||||
options = {
|
options = {
|
||||||
m.group(1).lower(): (m.group(2) or m.group(3))
|
m.group(1)
|
||||||
|
.lower(): (m.group(2) or m.group(3))
|
||||||
.replace("%22", '"')
|
.replace("%22", '"')
|
||||||
.replace("%0D%0A", "\n")
|
.replace("%0D%0A", "\n")
|
||||||
for m in _param.finditer(value[pos:])
|
for m in _param.finditer(value[pos:])
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ExceptionMixin(metaclass=SanicMeta):
|
|||||||
def exception(
|
def exception(
|
||||||
self,
|
self,
|
||||||
*exceptions: Union[Type[Exception], List[Type[Exception]]],
|
*exceptions: Union[Type[Exception], List[Type[Exception]]],
|
||||||
apply: bool = True,
|
apply: bool = True
|
||||||
) -> Callable:
|
) -> Callable:
|
||||||
"""Decorator used to register an exception handler for the current application or blueprint instance.
|
"""Decorator used to register an exception handler for the current application or blueprint instance.
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class MiddlewareMixin(metaclass=SanicMeta):
|
|||||||
attach_to: str = "request",
|
attach_to: str = "request",
|
||||||
apply: bool = True,
|
apply: bool = True,
|
||||||
*,
|
*,
|
||||||
priority: int = 0,
|
priority: int = 0
|
||||||
) -> MiddlewareType:
|
) -> MiddlewareType:
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class MiddlewareMixin(metaclass=SanicMeta):
|
|||||||
attach_to: str = "request",
|
attach_to: str = "request",
|
||||||
apply: bool = True,
|
apply: bool = True,
|
||||||
*,
|
*,
|
||||||
priority: int = 0,
|
priority: int = 0
|
||||||
) -> Callable[[MiddlewareType], MiddlewareType]:
|
) -> Callable[[MiddlewareType], MiddlewareType]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class MiddlewareMixin(metaclass=SanicMeta):
|
|||||||
attach_to: str = "request",
|
attach_to: str = "request",
|
||||||
apply: bool = True,
|
apply: bool = True,
|
||||||
*,
|
*,
|
||||||
priority: int = 0,
|
priority: int = 0
|
||||||
) -> Union[MiddlewareType, Callable[[MiddlewareType], MiddlewareType]]:
|
) -> Union[MiddlewareType, Callable[[MiddlewareType], MiddlewareType]]:
|
||||||
"""Decorator for registering middleware.
|
"""Decorator for registering middleware.
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,12 @@ class ConnInfo:
|
|||||||
self.server_name = ""
|
self.server_name = ""
|
||||||
self.cert: Dict[str, Any] = {}
|
self.cert: Dict[str, Any] = {}
|
||||||
self.network_paths: List[Any] = []
|
self.network_paths: List[Any] = []
|
||||||
sslobj: Optional[SSLObject] = transport.get_extra_info("ssl_object") # type: ignore
|
sslobj: Optional[SSLObject] = transport.get_extra_info(
|
||||||
sslctx: Optional[SSLContext] = transport.get_extra_info("ssl_context") # type: ignore
|
"ssl_object"
|
||||||
|
) # type: ignore
|
||||||
|
sslctx: Optional[SSLContext] = transport.get_extra_info(
|
||||||
|
"ssl_context"
|
||||||
|
) # type: ignore
|
||||||
if sslobj:
|
if sslobj:
|
||||||
self.ssl = True
|
self.ssl = True
|
||||||
self.server_name = getattr(sslobj, "sanic_server_name", None) or ""
|
self.server_name = getattr(sslobj, "sanic_server_name", None) or ""
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ class WebsocketFrameAssembler:
|
|||||||
self.read_mutex = asyncio.Lock()
|
self.read_mutex = asyncio.Lock()
|
||||||
self.write_mutex = asyncio.Lock()
|
self.write_mutex = asyncio.Lock()
|
||||||
|
|
||||||
self.completed_queue = asyncio.Queue(maxsize=1) # type: asyncio.Queue[Data]
|
self.completed_queue = asyncio.Queue(
|
||||||
|
maxsize=1
|
||||||
|
) # type: asyncio.Queue[Data]
|
||||||
|
|
||||||
# put() sets this event to tell get() that a message can be fetched.
|
# put() sets this event to tell get() that a message can be fetched.
|
||||||
self.message_complete = asyncio.Event()
|
self.message_complete = asyncio.Event()
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ class Event(Enum):
|
|||||||
HTTP_LIFECYCLE_SEND = "http.lifecycle.send"
|
HTTP_LIFECYCLE_SEND = "http.lifecycle.send"
|
||||||
HTTP_MIDDLEWARE_AFTER = "http.middleware.after"
|
HTTP_MIDDLEWARE_AFTER = "http.middleware.after"
|
||||||
HTTP_MIDDLEWARE_BEFORE = "http.middleware.before"
|
HTTP_MIDDLEWARE_BEFORE = "http.middleware.before"
|
||||||
|
WEBSOCKET_HANDLER_AFTER = "websocket.handler.after"
|
||||||
|
WEBSOCKET_HANDLER_BEFORE = "websocket.handler.before"
|
||||||
|
WEBSOCKET_HANDLER_EXCEPTION = "websocket.handler.exception"
|
||||||
|
|
||||||
|
|
||||||
RESERVED_NAMESPACES = {
|
RESERVED_NAMESPACES = {
|
||||||
@@ -65,6 +68,11 @@ RESERVED_NAMESPACES = {
|
|||||||
Event.HTTP_MIDDLEWARE_AFTER.value,
|
Event.HTTP_MIDDLEWARE_AFTER.value,
|
||||||
Event.HTTP_MIDDLEWARE_BEFORE.value,
|
Event.HTTP_MIDDLEWARE_BEFORE.value,
|
||||||
),
|
),
|
||||||
|
"websocket": {
|
||||||
|
Event.WEBSOCKET_HANDLER_AFTER.value,
|
||||||
|
Event.WEBSOCKET_HANDLER_BEFORE.value,
|
||||||
|
Event.WEBSOCKET_HANDLER_EXCEPTION.value,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any, Dict, ItemsView, Iterator, KeysView, List, ValuesView
|
from typing import Any, Dict, ItemsView, Iterator, KeysView, List
|
||||||
from typing import Mapping as MappingType
|
from typing import Mapping as MappingType
|
||||||
|
from typing import ValuesView
|
||||||
|
|
||||||
|
|
||||||
dict
|
dict
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
import click
|
|
||||||
import towncrier
|
import towncrier
|
||||||
|
import click
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(
|
print(
|
||||||
"Please make sure you have a installed towncrier and click before using this tool"
|
"Please make sure you have a installed towncrier and click before using this tool"
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser, Namespace
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from configparser import RawConfigParser
|
from configparser import RawConfigParser
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from os import chdir, path
|
from os import path, chdir
|
||||||
from subprocess import PIPE, Popen
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
import towncrier
|
from jinja2 import Environment, BaseLoader
|
||||||
|
|
||||||
from jinja2 import BaseLoader, Environment
|
|
||||||
from requests import patch
|
from requests import patch
|
||||||
|
import sys
|
||||||
|
import towncrier
|
||||||
|
|
||||||
GIT_COMMANDS = {
|
GIT_COMMANDS = {
|
||||||
"get_tag": ["git describe --tags --abbrev=0"],
|
"get_tag": ["git describe --tags --abbrev=0"],
|
||||||
@@ -81,7 +78,7 @@ def _run_shell_command(command: list):
|
|||||||
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
|
||||||
except Exception:
|
except:
|
||||||
return None, None, -1
|
return None, None, -1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import bottle
|
import bottle
|
||||||
import ujson
|
import ujson
|
||||||
|
|
||||||
from bottle import route
|
from bottle import route, run
|
||||||
|
|
||||||
|
|
||||||
@route("/")
|
@route("/")
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import timeit
|
import timeit
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
|
||||||
from sanic.response import json
|
from sanic.response import json
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import ujson
|
import ujson
|
||||||
|
|
||||||
from wheezy.http import HTTPResponse, WSGIApplication
|
from wheezy.http import HTTPResponse, WSGIApplication
|
||||||
|
from wheezy.http.response import json_response
|
||||||
from wheezy.routing import url
|
from wheezy.routing import url
|
||||||
from wheezy.web.handlers import BaseHandler
|
from wheezy.web.handlers import BaseHandler
|
||||||
from wheezy.web.middleware import (
|
from wheezy.web.middleware import (
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from collections import deque, namedtuple
|
from collections import deque, namedtuple
|
||||||
@@ -11,7 +12,7 @@ from pytest import MonkeyPatch
|
|||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.application.state import Mode
|
from sanic.application.state import Mode
|
||||||
from sanic.asgi import Lifespan, MockTransport
|
from sanic.asgi import ASGIApp, Lifespan, MockTransport
|
||||||
from sanic.exceptions import BadRequest, Forbidden, ServiceUnavailable
|
from sanic.exceptions import BadRequest, Forbidden, ServiceUnavailable
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def test_bp_group_indexing(app: Sanic):
|
|||||||
group = Blueprint.group(blueprint_1, blueprint_2)
|
group = Blueprint.group(blueprint_1, blueprint_2)
|
||||||
assert group[0] == blueprint_1
|
assert group[0] == blueprint_1
|
||||||
|
|
||||||
with raises(expected_exception=IndexError):
|
with raises(expected_exception=IndexError) as e:
|
||||||
_ = group[3]
|
_ = group[3]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
from asyncio import CancelledError
|
from asyncio import CancelledError
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sanic import Request, Sanic, json
|
from sanic import Request, Sanic, json
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from unittest.mock import Mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from pytest import LogCaptureFixture, MonkeyPatch
|
from pytest import LogCaptureFixture, MonkeyPatch, WarningsRecorder
|
||||||
|
|
||||||
from sanic import Sanic, handlers
|
from sanic import Sanic, handlers
|
||||||
from sanic.exceptions import BadRequest, Forbidden, NotFound, ServerError
|
from sanic.exceptions import BadRequest, Forbidden, NotFound, ServerError
|
||||||
@@ -169,7 +169,7 @@ def test_exception_handler_lookup(exception_handler_app: Sanic):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ModuleNotFoundError # noqa: F823
|
ModuleNotFoundError
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
||||||
class ModuleNotFoundError(ImportError):
|
class ModuleNotFoundError(ImportError):
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import sanic_ext # noqa: F401
|
import sanic_ext
|
||||||
|
|
||||||
SANIC_EXT_IN_ENV = True
|
SANIC_EXT_IN_ENV = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ def raised_ceiling():
|
|||||||
# Chrome, Firefox:
|
# Chrome, Firefox:
|
||||||
# Content-Disposition: form-data; name="foo%22;bar\"; filename="😀"
|
# Content-Disposition: form-data; name="foo%22;bar\"; filename="😀"
|
||||||
'form-data; name="foo%22;bar\\"; filename="😀"',
|
'form-data; name="foo%22;bar\\"; filename="😀"',
|
||||||
("form-data", {"name": 'foo";bar\\', "filename": "😀"}),
|
("form-data", {"name": 'foo";bar\\', "filename": "😀"})
|
||||||
# cgi: ('form-data', {'name': 'foo%22;bar"; filename="😀'})
|
# cgi: ('form-data', {'name': 'foo%22;bar"; filename="😀'})
|
||||||
# werkzeug (pre 2.3.0): ('form-data', {'name': 'foo%22;bar"; filename='})
|
# werkzeug (pre 2.3.0): ('form-data', {'name': 'foo%22;bar"; filename='})
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from sanic_testing.testing import HOST, PORT
|
|||||||
from sanic import Blueprint, text
|
from sanic import Blueprint, text
|
||||||
from sanic.compat import use_context
|
from sanic.compat import use_context
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
|
from sanic.server.socket import configure_socket
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ def test_streaming_echo():
|
|||||||
|
|
||||||
async def client(app, reader, writer):
|
async def client(app, reader, writer):
|
||||||
# Unfortunately httpx does not support 2-way streaming, so do it by hand.
|
# Unfortunately httpx does not support 2-way streaming, so do it by hand.
|
||||||
host = "host: localhost:8000\r\n".encode()
|
host = f"host: localhost:8000\r\n".encode()
|
||||||
writer.write(
|
writer.write(
|
||||||
b"POST /echo HTTP/1.1\r\n" + host + b"content-length: 2\r\n"
|
b"POST /echo HTTP/1.1\r\n" + host + b"content-length: 2\r\n"
|
||||||
b"content-type: text/plain; charset=utf-8\r\n"
|
b"content-type: text/plain; charset=utf-8\r\n"
|
||||||
@@ -581,7 +581,7 @@ def test_streaming_echo():
|
|||||||
)
|
)
|
||||||
# Read response
|
# Read response
|
||||||
res = b""
|
res = b""
|
||||||
while b"\r\n\r\n" not in res:
|
while not b"\r\n\r\n" in res:
|
||||||
res += await reader.read(4096)
|
res += await reader.read(4096)
|
||||||
assert res.startswith(b"HTTP/1.1 200 OK\r\n")
|
assert res.startswith(b"HTTP/1.1 200 OK\r\n")
|
||||||
assert res.endswith(b"\r\n\r\n")
|
assert res.endswith(b"\r\n\r\n")
|
||||||
@@ -589,7 +589,7 @@ def test_streaming_echo():
|
|||||||
|
|
||||||
async def read_chunk():
|
async def read_chunk():
|
||||||
nonlocal buffer
|
nonlocal buffer
|
||||||
while b"\r\n" not in buffer:
|
while not b"\r\n" in buffer:
|
||||||
data = await reader.read(4096)
|
data = await reader.read(4096)
|
||||||
assert data
|
assert data
|
||||||
buffer += data
|
buffer += data
|
||||||
@@ -618,6 +618,6 @@ def test_streaming_echo():
|
|||||||
assert res == b"-"
|
assert res == b"-"
|
||||||
|
|
||||||
res = await read_chunk()
|
res = await read_chunk()
|
||||||
assert res is None
|
assert res == None
|
||||||
|
|
||||||
app.run(access_log=False, single_process=True)
|
app.run(access_log=False, single_process=True)
|
||||||
|
|||||||
@@ -2011,11 +2011,11 @@ def test_server_name_and_url_for(app):
|
|||||||
app.config.SERVER_NAME = "my-server" # This means default port
|
app.config.SERVER_NAME = "my-server" # This means default port
|
||||||
assert app.url_for("handler", _external=True) == "http://my-server/foo"
|
assert app.url_for("handler", _external=True) == "http://my-server/foo"
|
||||||
request, response = app.test_client.get("/foo")
|
request, response = app.test_client.get("/foo")
|
||||||
assert request.url_for("handler") == "http://my-server/foo"
|
assert request.url_for("handler") == f"http://my-server/foo"
|
||||||
|
|
||||||
app.config.SERVER_NAME = "https://my-server/path"
|
app.config.SERVER_NAME = "https://my-server/path"
|
||||||
request, response = app.test_client.get("/foo")
|
request, response = app.test_client.get("/foo")
|
||||||
url = "https://my-server/path/foo"
|
url = f"https://my-server/path/foo"
|
||||||
assert app.url_for("handler", _external=True) == url
|
assert app.url_for("handler", _external=True) == url
|
||||||
assert request.url_for("handler") == url
|
assert request.url_for("handler") == url
|
||||||
|
|
||||||
@@ -2180,7 +2180,7 @@ def test_safe_method_with_body_ignored(app):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert request.body == b""
|
assert request.body == b""
|
||||||
assert request.json is None
|
assert request.json == None
|
||||||
assert response.body == b"OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -610,7 +610,7 @@ def test_multiple_responses(
|
|||||||
|
|
||||||
@app.get("/4")
|
@app.get("/4")
|
||||||
async def handler4(request: Request):
|
async def handler4(request: Request):
|
||||||
await request.respond(headers={"one": "one"})
|
response = await request.respond(headers={"one": "one"})
|
||||||
return json({"foo": "bar"}, headers={"one": "two"})
|
return json({"foo": "bar"}, headers={"one": "two"})
|
||||||
|
|
||||||
@app.get("/5")
|
@app.get("/5")
|
||||||
@@ -641,6 +641,10 @@ def test_multiple_responses(
|
|||||||
"been responded to."
|
"been responded to."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
error_msg3 = (
|
||||||
|
"Response stream was ended, no more "
|
||||||
|
"response data is allowed to be sent."
|
||||||
|
)
|
||||||
|
|
||||||
with caplog.at_level(ERROR):
|
with caplog.at_level(ERROR):
|
||||||
_, response = app.test_client.get("/1")
|
_, response = app.test_client.get("/1")
|
||||||
@@ -765,7 +769,7 @@ def test_file_response_headers(
|
|||||||
assert (
|
assert (
|
||||||
"cache-control" in headers
|
"cache-control" in headers
|
||||||
and f"max-age={test_max_age}" in headers.get("cache-control")
|
and f"max-age={test_max_age}" in headers.get("cache-control")
|
||||||
and "public" in headers.get("cache-control")
|
and f"public" in headers.get("cache-control")
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"expires" in headers
|
"expires" in headers
|
||||||
@@ -796,14 +800,14 @@ def test_file_response_headers(
|
|||||||
|
|
||||||
_, response = app.test_client.get(f"/files/no_cache/{file_name}")
|
_, response = app.test_client.get(f"/files/no_cache/{file_name}")
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
assert "cache-control" in headers and "no-cache" == headers.get(
|
assert "cache-control" in headers and f"no-cache" == headers.get(
|
||||||
"cache-control"
|
"cache-control"
|
||||||
)
|
)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
_, response = app.test_client.get(f"/files/no_store/{file_name}")
|
_, response = app.test_client.get(f"/files/no_store/{file_name}")
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
assert "cache-control" in headers and "no-store" == headers.get(
|
assert "cache-control" in headers and f"no-store" == headers.get(
|
||||||
"cache-control"
|
"cache-control"
|
||||||
)
|
)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|||||||
@@ -54,3 +54,65 @@ def test_ws_handler_async_for(
|
|||||||
)
|
)
|
||||||
assert ws_proxy.client_sent == ["test 1", "test 2", ""]
|
assert ws_proxy.client_sent == ["test 1", "test 2", ""]
|
||||||
assert ws_proxy.client_received == ["test 1", "test 2"]
|
assert ws_proxy.client_received == ["test 1", "test 2"]
|
||||||
|
|
||||||
|
|
||||||
|
def signalapp(app):
|
||||||
|
@app.signal("websocket.handler.before")
|
||||||
|
async def ws_before(request: Request, websocket: Websocket):
|
||||||
|
app.ctx.seq.append("before")
|
||||||
|
print("before")
|
||||||
|
await websocket.send("before: " + await websocket.recv())
|
||||||
|
print("before2")
|
||||||
|
|
||||||
|
@app.signal("websocket.handler.after")
|
||||||
|
async def ws_after(request: Request, websocket: Websocket):
|
||||||
|
app.ctx.seq.append("after")
|
||||||
|
await websocket.send("after: " + await websocket.recv())
|
||||||
|
await websocket.recv()
|
||||||
|
|
||||||
|
@app.signal("websocket.handler.exception")
|
||||||
|
async def ws_exception(
|
||||||
|
request: Request, websocket: Websocket, exception: Exception
|
||||||
|
):
|
||||||
|
app.ctx.seq.append("exception")
|
||||||
|
await websocket.send(f"exception: {exception}")
|
||||||
|
await websocket.recv()
|
||||||
|
|
||||||
|
@app.websocket("/ws")
|
||||||
|
async def ws_handler(request: Request, ws: Websocket):
|
||||||
|
app.ctx.seq.append("ws")
|
||||||
|
|
||||||
|
@app.websocket("/wserror")
|
||||||
|
async def ws_error(request: Request, ws: Websocket):
|
||||||
|
print("wserr")
|
||||||
|
app.ctx.seq.append("wserror")
|
||||||
|
raise Exception(await ws.recv())
|
||||||
|
print("wserr2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ws_signals(
|
||||||
|
app: Sanic,
|
||||||
|
simple_ws_mimic_client: MimicClientType,
|
||||||
|
):
|
||||||
|
signalapp(app)
|
||||||
|
|
||||||
|
app.ctx.seq = []
|
||||||
|
_, ws_proxy = app.test_client.websocket(
|
||||||
|
"/ws", mimic=simple_ws_mimic_client
|
||||||
|
)
|
||||||
|
assert ws_proxy.client_received == ["before: test 1", "after: test 2"]
|
||||||
|
assert app.ctx.seq == ["before", "ws", "after"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_ws_signals_exception(
|
||||||
|
app: Sanic,
|
||||||
|
simple_ws_mimic_client: MimicClientType,
|
||||||
|
):
|
||||||
|
signalapp(app)
|
||||||
|
|
||||||
|
app.ctx.seq = []
|
||||||
|
_, ws_proxy = app.test_client.websocket(
|
||||||
|
"/wserror", mimic=simple_ws_mimic_client
|
||||||
|
)
|
||||||
|
assert ws_proxy.client_received == ["before: test 1", "exception: test 2"]
|
||||||
|
assert app.ctx.seq == ["before", "wserror", "exception"]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# flake8: noqa: E501
|
# flake8: noqa: E501
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|||||||
Reference in New Issue
Block a user