v23.3 Deprecation Removal (#2717)
This commit is contained in:
parent
a8c2d77c91
commit
d680af3709
19
sanic/app.py
19
sanic/app.py
|
@ -64,12 +64,7 @@ from sanic.exceptions import (
|
||||||
from sanic.handlers import ErrorHandler
|
from sanic.handlers import ErrorHandler
|
||||||
from sanic.helpers import Default, _default
|
from sanic.helpers import Default, _default
|
||||||
from sanic.http import Stage
|
from sanic.http import Stage
|
||||||
from sanic.log import (
|
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||||
LOGGING_CONFIG_DEFAULTS,
|
|
||||||
deprecation,
|
|
||||||
error_logger,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
from sanic.middleware import Middleware, MiddlewareLocation
|
from sanic.middleware import Middleware, MiddlewareLocation
|
||||||
from sanic.mixins.listeners import ListenerEvent
|
from sanic.mixins.listeners import ListenerEvent
|
||||||
from sanic.mixins.startup import StartupMixin
|
from sanic.mixins.startup import StartupMixin
|
||||||
|
@ -1584,17 +1579,19 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
|
||||||
self.signalize(self.config.TOUCHUP)
|
self.signalize(self.config.TOUCHUP)
|
||||||
self.finalize()
|
self.finalize()
|
||||||
|
|
||||||
route_names = [route.name 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)
|
||||||
deprecation(
|
message = (
|
||||||
f"Duplicate route names detected: {names}. In the future, "
|
f"Duplicate route names detected: {names}. You should rename "
|
||||||
"Sanic will enforce uniqueness in route naming.",
|
"one or more of them explicitly by using the `name` param, "
|
||||||
23.3,
|
"or changing the implicit name derived from the class and "
|
||||||
|
"function name. For more details, please see ___."
|
||||||
)
|
)
|
||||||
|
raise ServerError(message)
|
||||||
|
|
||||||
Sanic._check_uvloop_conflict()
|
Sanic._check_uvloop_conflict()
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,7 @@ class Blueprint(BaseSanic):
|
||||||
"_future_listeners",
|
"_future_listeners",
|
||||||
"_future_exceptions",
|
"_future_exceptions",
|
||||||
"_future_signals",
|
"_future_signals",
|
||||||
|
"copied_from",
|
||||||
"ctx",
|
"ctx",
|
||||||
"exceptions",
|
"exceptions",
|
||||||
"host",
|
"host",
|
||||||
|
@ -118,6 +119,7 @@ class Blueprint(BaseSanic):
|
||||||
):
|
):
|
||||||
super().__init__(name=name)
|
super().__init__(name=name)
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.copied_from = ""
|
||||||
self.ctx = SimpleNamespace()
|
self.ctx = SimpleNamespace()
|
||||||
self.host = host
|
self.host = host
|
||||||
self.strict_slashes = strict_slashes
|
self.strict_slashes = strict_slashes
|
||||||
|
@ -213,6 +215,7 @@ class Blueprint(BaseSanic):
|
||||||
self.reset()
|
self.reset()
|
||||||
new_bp = deepcopy(self)
|
new_bp = deepcopy(self)
|
||||||
new_bp.name = name
|
new_bp.name = name
|
||||||
|
new_bp.copied_from = self.name
|
||||||
|
|
||||||
if not isinstance(url_prefix, Default):
|
if not isinstance(url_prefix, Default):
|
||||||
new_bp.url_prefix = url_prefix
|
new_bp.url_prefix = url_prefix
|
||||||
|
@ -352,6 +355,16 @@ class Blueprint(BaseSanic):
|
||||||
|
|
||||||
registered.add(apply_route)
|
registered.add(apply_route)
|
||||||
route = app._apply_route(apply_route)
|
route = app._apply_route(apply_route)
|
||||||
|
|
||||||
|
# If it is a copied BP, then make sure all of the names of routes
|
||||||
|
# matchup with the new BP name
|
||||||
|
if self.copied_from:
|
||||||
|
for r in route:
|
||||||
|
r.name = r.name.replace(self.copied_from, self.name)
|
||||||
|
r.extra.ident = r.extra.ident.replace(
|
||||||
|
self.copied_from, self.name
|
||||||
|
)
|
||||||
|
|
||||||
operation = (
|
operation = (
|
||||||
routes.extend if isinstance(route, list) else routes.append
|
routes.extend if isinstance(route, list) else routes.append
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
@ -6,7 +5,7 @@ import sys
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from textwrap import indent
|
from textwrap import indent
|
||||||
from typing import List, Union, cast
|
from typing import List, Union
|
||||||
|
|
||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
from sanic.application.logo import get_logo
|
from sanic.application.logo import get_logo
|
||||||
|
@ -14,7 +13,7 @@ from sanic.cli.arguments import Group
|
||||||
from sanic.cli.base import SanicArgumentParser, SanicHelpFormatter
|
from sanic.cli.base import SanicArgumentParser, SanicHelpFormatter
|
||||||
from sanic.cli.inspector import make_inspector_parser
|
from sanic.cli.inspector import make_inspector_parser
|
||||||
from sanic.cli.inspector_client import InspectorClient
|
from sanic.cli.inspector_client import InspectorClient
|
||||||
from sanic.log import Colors, error_logger
|
from sanic.log import error_logger
|
||||||
from sanic.worker.loader import AppLoader
|
from sanic.worker.loader import AppLoader
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,10 +102,6 @@ Or, a path to a directory to run as a simple HTTP server:
|
||||||
self.args.target, self.args.factory, self.args.simple, self.args
|
self.args.target, self.args.factory, self.args.simple, self.args
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.args.inspect or self.args.inspect_raw or self.args.trigger:
|
|
||||||
self._inspector_legacy(app_loader)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app = self._get_app(app_loader)
|
app = self._get_app(app_loader)
|
||||||
kwargs = self._build_run_kwargs()
|
kwargs = self._build_run_kwargs()
|
||||||
|
@ -117,38 +112,10 @@ Or, a path to a directory to run as a simple HTTP server:
|
||||||
app.prepare(**kwargs, version=http_version)
|
app.prepare(**kwargs, version=http_version)
|
||||||
if self.args.single:
|
if self.args.single:
|
||||||
serve = Sanic.serve_single
|
serve = Sanic.serve_single
|
||||||
elif self.args.legacy:
|
|
||||||
serve = Sanic.serve_legacy
|
|
||||||
else:
|
else:
|
||||||
serve = partial(Sanic.serve, app_loader=app_loader)
|
serve = partial(Sanic.serve, app_loader=app_loader)
|
||||||
serve(app)
|
serve(app)
|
||||||
|
|
||||||
def _inspector_legacy(self, app_loader: AppLoader):
|
|
||||||
host = port = None
|
|
||||||
target = cast(str, self.args.target)
|
|
||||||
if ":" in target:
|
|
||||||
maybe_host, maybe_port = target.rsplit(":", 1)
|
|
||||||
if maybe_port.isnumeric():
|
|
||||||
host, port = maybe_host, int(maybe_port)
|
|
||||||
if not host:
|
|
||||||
app = self._get_app(app_loader)
|
|
||||||
host, port = app.config.INSPECTOR_HOST, app.config.INSPECTOR_PORT
|
|
||||||
|
|
||||||
action = self.args.trigger or "info"
|
|
||||||
|
|
||||||
InspectorClient(
|
|
||||||
str(host), int(port or 6457), False, self.args.inspect_raw, ""
|
|
||||||
).do(action)
|
|
||||||
sys.stdout.write(
|
|
||||||
f"\n{Colors.BOLD}{Colors.YELLOW}WARNING:{Colors.END} "
|
|
||||||
"You are using the legacy CLI command that will be removed in "
|
|
||||||
f"{Colors.RED}v23.3{Colors.END}. See "
|
|
||||||
"https://sanic.dev/en/guide/release-notes/v22.12.html"
|
|
||||||
"#deprecations-and-removals or checkout the new "
|
|
||||||
"style commands:\n\n\t"
|
|
||||||
f"{Colors.YELLOW}sanic inspect --help{Colors.END}\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _inspector(self):
|
def _inspector(self):
|
||||||
args = sys.argv[2:]
|
args = sys.argv[2:]
|
||||||
self.args, unknown = self.parser.parse_known_args(args=args)
|
self.args, unknown = self.parser.parse_known_args(args=args)
|
||||||
|
@ -202,8 +169,6 @@ Or, a path to a directory to run as a simple HTTP server:
|
||||||
)
|
)
|
||||||
error_logger.error(message)
|
error_logger.error(message)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if self.args.inspect or self.args.inspect_raw:
|
|
||||||
logging.disable(logging.CRITICAL)
|
|
||||||
|
|
||||||
def _get_app(self, app_loader: AppLoader):
|
def _get_app(self, app_loader: AppLoader):
|
||||||
try:
|
try:
|
||||||
|
@ -251,7 +216,6 @@ Or, a path to a directory to run as a simple HTTP server:
|
||||||
"workers": self.args.workers,
|
"workers": self.args.workers,
|
||||||
"auto_tls": self.args.auto_tls,
|
"auto_tls": self.args.auto_tls,
|
||||||
"single_process": self.args.single,
|
"single_process": self.args.single,
|
||||||
"legacy": self.args.legacy,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for maybe_arg in ("auto_reload", "dev"):
|
for maybe_arg in ("auto_reload", "dev"):
|
||||||
|
|
|
@ -93,32 +93,6 @@ class ApplicationGroup(Group):
|
||||||
"a directory\n(module arg should be a path)"
|
"a directory\n(module arg should be a path)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
|
||||||
"--inspect",
|
|
||||||
dest="inspect",
|
|
||||||
action="store_true",
|
|
||||||
help=("Inspect the state of a running instance, human readable"),
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--inspect-raw",
|
|
||||||
dest="inspect_raw",
|
|
||||||
action="store_true",
|
|
||||||
help=("Inspect the state of a running instance, JSON output"),
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--trigger-reload",
|
|
||||||
dest="trigger",
|
|
||||||
action="store_const",
|
|
||||||
const="reload",
|
|
||||||
help=("Trigger worker processes to reload"),
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--trigger-shutdown",
|
|
||||||
dest="trigger",
|
|
||||||
action="store_const",
|
|
||||||
const="shutdown",
|
|
||||||
help=("Trigger all processes to shutdown"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPVersionGroup(Group):
|
class HTTPVersionGroup(Group):
|
||||||
|
@ -247,11 +221,6 @@ class WorkerGroup(Group):
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Do not use multiprocessing, run server in a single process",
|
help="Do not use multiprocessing, run server in a single process",
|
||||||
)
|
)
|
||||||
self.container.add_argument(
|
|
||||||
"--legacy",
|
|
||||||
action="store_true",
|
|
||||||
help="Use the legacy server manager",
|
|
||||||
)
|
|
||||||
self.add_bool_arguments(
|
self.add_bool_arguments(
|
||||||
"--access-logs",
|
"--access-logs",
|
||||||
dest="access_log",
|
dest="access_log",
|
||||||
|
|
|
@ -3,7 +3,8 @@ from __future__ import annotations
|
||||||
from typing import Dict, List, Optional, Tuple, Type
|
from typing import Dict, List, Optional, Tuple, Type
|
||||||
|
|
||||||
from sanic.errorpages import BaseRenderer, TextRenderer, exception_response
|
from sanic.errorpages import BaseRenderer, TextRenderer, exception_response
|
||||||
from sanic.log import deprecation, error_logger
|
from sanic.exceptions import ServerError
|
||||||
|
from sanic.log import error_logger
|
||||||
from sanic.models.handler_types import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
|
||||||
|
@ -43,16 +44,11 @@ class ErrorHandler:
|
||||||
if name is None:
|
if name is None:
|
||||||
name = "__ALL_ROUTES__"
|
name = "__ALL_ROUTES__"
|
||||||
|
|
||||||
error_logger.warning(
|
message = (
|
||||||
f"Duplicate exception handler definition on: route={name} "
|
f"Duplicate exception handler definition on: route={name} "
|
||||||
f"and exception={exc}"
|
f"and exception={exc}"
|
||||||
)
|
)
|
||||||
deprecation(
|
raise ServerError(message)
|
||||||
"A duplicate exception handler definition was discovered. "
|
|
||||||
"This may cause unintended consequences. A warning has been "
|
|
||||||
"issued now, but it will not be allowed starting in v23.3.",
|
|
||||||
23.3,
|
|
||||||
)
|
|
||||||
self.cached_handlers[key] = handler
|
self.cached_handlers[key] = handler
|
||||||
|
|
||||||
def add(self, exception, handler, route_names: Optional[List[str]] = None):
|
def add(self, exception, handler, route_names: Optional[List[str]] = None):
|
||||||
|
|
|
@ -47,17 +47,16 @@ from sanic.helpers import Default, _default
|
||||||
from sanic.http.constants import HTTP
|
from sanic.http.constants import HTTP
|
||||||
from sanic.http.tls import get_ssl_context, process_to_context
|
from sanic.http.tls import get_ssl_context, process_to_context
|
||||||
from sanic.http.tls.context import SanicSSLContext
|
from sanic.http.tls.context import SanicSSLContext
|
||||||
from sanic.log import Colors, deprecation, error_logger, logger
|
from sanic.log import Colors, error_logger, logger
|
||||||
from sanic.models.handler_types import ListenerType
|
from sanic.models.handler_types import ListenerType
|
||||||
from sanic.server import Signal as ServerSignal
|
from sanic.server import Signal as ServerSignal
|
||||||
from sanic.server import try_use_uvloop
|
from sanic.server import try_use_uvloop
|
||||||
from sanic.server.async_server import AsyncioServer
|
from sanic.server.async_server import AsyncioServer
|
||||||
from sanic.server.events import trigger_events
|
from sanic.server.events import trigger_events
|
||||||
from sanic.server.legacy import watchdog
|
|
||||||
from sanic.server.loop import try_windows_loop
|
from sanic.server.loop import try_windows_loop
|
||||||
from sanic.server.protocols.http_protocol import HttpProtocol
|
from sanic.server.protocols.http_protocol import HttpProtocol
|
||||||
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
||||||
from sanic.server.runners import serve, serve_multiple, serve_single
|
from sanic.server.runners import serve
|
||||||
from sanic.server.socket import configure_socket, remove_unix_socket
|
from sanic.server.socket import configure_socket, remove_unix_socket
|
||||||
from sanic.worker.loader import AppLoader
|
from sanic.worker.loader import AppLoader
|
||||||
from sanic.worker.manager import WorkerManager
|
from sanic.worker.manager import WorkerManager
|
||||||
|
@ -135,7 +134,6 @@ class StartupMixin(metaclass=SanicMeta):
|
||||||
motd_display: Optional[Dict[str, str]] = None,
|
motd_display: Optional[Dict[str, str]] = None,
|
||||||
auto_tls: bool = False,
|
auto_tls: bool = False,
|
||||||
single_process: bool = False,
|
single_process: bool = False,
|
||||||
legacy: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Run the HTTP Server and listen until keyboard interrupt or term
|
Run the HTTP Server and listen until keyboard interrupt or term
|
||||||
|
@ -197,13 +195,10 @@ class StartupMixin(metaclass=SanicMeta):
|
||||||
motd_display=motd_display,
|
motd_display=motd_display,
|
||||||
auto_tls=auto_tls,
|
auto_tls=auto_tls,
|
||||||
single_process=single_process,
|
single_process=single_process,
|
||||||
legacy=legacy,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if single_process:
|
if single_process:
|
||||||
serve = self.__class__.serve_single
|
serve = self.__class__.serve_single
|
||||||
elif legacy:
|
|
||||||
serve = self.__class__.serve_legacy
|
|
||||||
else:
|
else:
|
||||||
serve = self.__class__.serve
|
serve = self.__class__.serve
|
||||||
serve(primary=self) # type: ignore
|
serve(primary=self) # type: ignore
|
||||||
|
@ -235,7 +230,6 @@ class StartupMixin(metaclass=SanicMeta):
|
||||||
coffee: bool = False,
|
coffee: bool = False,
|
||||||
auto_tls: bool = False,
|
auto_tls: bool = False,
|
||||||
single_process: bool = False,
|
single_process: bool = False,
|
||||||
legacy: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if version == 3 and self.state.server_info:
|
if version == 3 and self.state.server_info:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
@ -264,13 +258,10 @@ class StartupMixin(metaclass=SanicMeta):
|
||||||
"or auto-reload"
|
"or auto-reload"
|
||||||
)
|
)
|
||||||
|
|
||||||
if single_process and legacy:
|
if register_sys_signals is False and not single_process:
|
||||||
raise RuntimeError("Cannot run single process and legacy mode")
|
|
||||||
|
|
||||||
if register_sys_signals is False and not (single_process or legacy):
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Cannot run Sanic.serve with register_sys_signals=False. "
|
"Cannot run Sanic.serve with register_sys_signals=False. "
|
||||||
"Use either Sanic.serve_single or Sanic.serve_legacy."
|
"Use Sanic.serve_single."
|
||||||
)
|
)
|
||||||
|
|
||||||
if motd_display:
|
if motd_display:
|
||||||
|
@ -956,76 +947,6 @@ class StartupMixin(metaclass=SanicMeta):
|
||||||
cls._cleanup_env_vars()
|
cls._cleanup_env_vars()
|
||||||
cls._cleanup_apps()
|
cls._cleanup_apps()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def serve_legacy(cls, primary: Optional[Sanic] = None) -> None:
|
|
||||||
apps = list(cls._app_registry.values())
|
|
||||||
|
|
||||||
if not primary:
|
|
||||||
try:
|
|
||||||
primary = apps[0]
|
|
||||||
except IndexError:
|
|
||||||
raise RuntimeError("Did not find any applications.")
|
|
||||||
|
|
||||||
reloader_start = primary.listeners.get("reload_process_start")
|
|
||||||
reloader_stop = primary.listeners.get("reload_process_stop")
|
|
||||||
# We want to run auto_reload if ANY of the applications have it enabled
|
|
||||||
if (
|
|
||||||
cls.should_auto_reload()
|
|
||||||
and os.environ.get("SANIC_SERVER_RUNNING") != "true"
|
|
||||||
): # no cov
|
|
||||||
loop = new_event_loop()
|
|
||||||
trigger_events(reloader_start, loop, primary)
|
|
||||||
reload_dirs: Set[Path] = primary.state.reload_dirs.union(
|
|
||||||
*(app.state.reload_dirs for app in apps)
|
|
||||||
)
|
|
||||||
watchdog(1.0, reload_dirs)
|
|
||||||
trigger_events(reloader_stop, loop, primary)
|
|
||||||
return
|
|
||||||
|
|
||||||
# This exists primarily for unit testing
|
|
||||||
if not primary.state.server_info: # no cov
|
|
||||||
for app in apps:
|
|
||||||
app.state.server_info.clear()
|
|
||||||
return
|
|
||||||
|
|
||||||
primary_server_info = primary.state.server_info[0]
|
|
||||||
primary.before_server_start(partial(primary._start_servers, apps=apps))
|
|
||||||
|
|
||||||
deprecation(
|
|
||||||
f"{Colors.YELLOW}Running {Colors.SANIC}Sanic {Colors.YELLOW}w/ "
|
|
||||||
f"LEGACY manager.{Colors.END} Support for will be dropped in "
|
|
||||||
"version 23.3.",
|
|
||||||
23.3,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
primary_server_info.stage = ServerStage.SERVING
|
|
||||||
|
|
||||||
if primary.state.workers > 1 and os.name != "posix": # no cov
|
|
||||||
logger.warning(
|
|
||||||
f"Multiprocessing is currently not supported on {os.name},"
|
|
||||||
" using workers=1 instead"
|
|
||||||
)
|
|
||||||
primary.state.workers = 1
|
|
||||||
if primary.state.workers == 1:
|
|
||||||
serve_single(primary_server_info.settings)
|
|
||||||
elif primary.state.workers == 0:
|
|
||||||
raise RuntimeError("Cannot serve with no workers")
|
|
||||||
else:
|
|
||||||
serve_multiple(
|
|
||||||
primary_server_info.settings, primary.state.workers
|
|
||||||
)
|
|
||||||
except BaseException:
|
|
||||||
error_logger.exception(
|
|
||||||
"Experienced exception while trying to serve"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
primary_server_info.stage = ServerStage.STOPPED
|
|
||||||
logger.info("Server Stopped")
|
|
||||||
|
|
||||||
cls._cleanup_env_vars()
|
|
||||||
cls._cleanup_apps()
|
|
||||||
|
|
||||||
async def _start_servers(
|
async def _start_servers(
|
||||||
self,
|
self,
|
||||||
primary: Sanic,
|
primary: Sanic,
|
||||||
|
|
|
@ -3,7 +3,7 @@ from functools import partial, wraps
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from os import PathLike, path
|
from os import PathLike, path
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import Optional, Sequence, Set, Union, cast
|
from typing import Optional, Sequence, Set, Union
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from sanic_routing.route import Route
|
from sanic_routing.route import Route
|
||||||
|
@ -14,7 +14,7 @@ from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
|
||||||
from sanic.exceptions import FileNotFound, HeaderNotFound, RangeNotSatisfiable
|
from sanic.exceptions import FileNotFound, HeaderNotFound, RangeNotSatisfiable
|
||||||
from sanic.handlers import ContentRangeHandler
|
from sanic.handlers import ContentRangeHandler
|
||||||
from sanic.handlers.directory import DirectoryHandler
|
from sanic.handlers.directory import DirectoryHandler
|
||||||
from sanic.log import deprecation, error_logger
|
from sanic.log import error_logger
|
||||||
from sanic.mixins.base import BaseMixin
|
from sanic.mixins.base import BaseMixin
|
||||||
from sanic.models.futures import FutureStatic
|
from sanic.models.futures import FutureStatic
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
|
@ -31,7 +31,7 @@ class StaticMixin(BaseMixin, metaclass=SanicMeta):
|
||||||
def static(
|
def static(
|
||||||
self,
|
self,
|
||||||
uri: str,
|
uri: str,
|
||||||
file_or_directory: Union[PathLike, str, bytes],
|
file_or_directory: Union[PathLike, str],
|
||||||
pattern: str = r"/?.+",
|
pattern: str = r"/?.+",
|
||||||
use_modified_since: bool = True,
|
use_modified_since: bool = True,
|
||||||
use_content_range: bool = False,
|
use_content_range: bool = False,
|
||||||
|
@ -94,14 +94,12 @@ class StaticMixin(BaseMixin, metaclass=SanicMeta):
|
||||||
f"Static route must be a valid path, not {file_or_directory}"
|
f"Static route must be a valid path, not {file_or_directory}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(file_or_directory, bytes):
|
try:
|
||||||
deprecation(
|
file_or_directory = Path(file_or_directory)
|
||||||
"Serving a static directory with a bytes string is "
|
except TypeError:
|
||||||
"deprecated and will be removed in v22.9.",
|
raise TypeError(
|
||||||
22.9,
|
"Static file or directory must be a path-like object or string"
|
||||||
)
|
)
|
||||||
file_or_directory = cast(str, file_or_directory.decode())
|
|
||||||
file_or_directory = Path(file_or_directory)
|
|
||||||
|
|
||||||
if directory_handler and (directory_view or index):
|
if directory_handler and (directory_view or index):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
|
@ -55,7 +55,7 @@ from sanic.headers import (
|
||||||
parse_xforwarded,
|
parse_xforwarded,
|
||||||
)
|
)
|
||||||
from sanic.http import Stage
|
from sanic.http import Stage
|
||||||
from sanic.log import deprecation, error_logger
|
from sanic.log import error_logger
|
||||||
from sanic.models.protocol_types import TransportProtocol
|
from sanic.models.protocol_types import TransportProtocol
|
||||||
from sanic.response import BaseHTTPResponse, HTTPResponse
|
from sanic.response import BaseHTTPResponse, HTTPResponse
|
||||||
|
|
||||||
|
@ -205,16 +205,6 @@ class Request:
|
||||||
def generate_id(*_):
|
def generate_id(*_):
|
||||||
return uuid.uuid4()
|
return uuid.uuid4()
|
||||||
|
|
||||||
@property
|
|
||||||
def request_middleware_started(self):
|
|
||||||
deprecation(
|
|
||||||
"Request.request_middleware_started has been deprecated and will"
|
|
||||||
"be removed. You should set a flag on the request context using"
|
|
||||||
"either middleware or signals if you need this feature.",
|
|
||||||
23.3,
|
|
||||||
)
|
|
||||||
return self._request_middleware_started
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stream_id(self):
|
def stream_id(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -44,7 +44,9 @@ 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=e.allowed_methods,
|
allowed_methods=tuple(e.allowed_methods)
|
||||||
|
if e.allowed_methods
|
||||||
|
else None,
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||||
|
@ -133,7 +135,16 @@ class Router(BaseRouter):
|
||||||
if host:
|
if host:
|
||||||
params.update({"requirements": {"host": host}})
|
params.update({"requirements": {"host": host}})
|
||||||
|
|
||||||
|
ident = name
|
||||||
|
if len(hosts) > 1:
|
||||||
|
ident = (
|
||||||
|
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.ignore_body = ignore_body
|
route.extra.ignore_body = ignore_body
|
||||||
route.extra.stream = stream
|
route.extra.stream = stream
|
||||||
route.extra.hosts = hosts
|
route.extra.hosts = hosts
|
||||||
|
|
|
@ -2,7 +2,7 @@ from sanic.models.server_types import ConnInfo, Signal
|
||||||
from sanic.server.async_server import AsyncioServer
|
from sanic.server.async_server import AsyncioServer
|
||||||
from sanic.server.loop import try_use_uvloop
|
from sanic.server.loop import try_use_uvloop
|
||||||
from sanic.server.protocols.http_protocol import HttpProtocol
|
from sanic.server.protocols.http_protocol import HttpProtocol
|
||||||
from sanic.server.runners import serve, serve_multiple, serve_single
|
from sanic.server.runners import serve
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -11,7 +11,5 @@ __all__ = (
|
||||||
"HttpProtocol",
|
"HttpProtocol",
|
||||||
"Signal",
|
"Signal",
|
||||||
"serve",
|
"serve",
|
||||||
"serve_multiple",
|
|
||||||
"serve_single",
|
|
||||||
"try_use_uvloop",
|
"try_use_uvloop",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
import itertools
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
|
|
||||||
def _iter_module_files():
|
|
||||||
"""This iterates over all relevant Python files.
|
|
||||||
It goes through all
|
|
||||||
loaded files from modules, all files in folders of already loaded modules
|
|
||||||
as well as all files reachable through a package.
|
|
||||||
"""
|
|
||||||
# The list call is necessary on Python 3 in case the module
|
|
||||||
# dictionary modifies during iteration.
|
|
||||||
for module in list(sys.modules.values()):
|
|
||||||
if module is None:
|
|
||||||
continue
|
|
||||||
filename = getattr(module, "__file__", None)
|
|
||||||
if filename:
|
|
||||||
old = None
|
|
||||||
while not os.path.isfile(filename):
|
|
||||||
old = filename
|
|
||||||
filename = os.path.dirname(filename)
|
|
||||||
if filename == old:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if filename[-4:] in (".pyc", ".pyo"):
|
|
||||||
filename = filename[:-1]
|
|
||||||
yield filename
|
|
||||||
|
|
||||||
|
|
||||||
def _get_args_for_reloading():
|
|
||||||
"""Returns the executable."""
|
|
||||||
main_module = sys.modules["__main__"]
|
|
||||||
mod_spec = getattr(main_module, "__spec__", None)
|
|
||||||
if sys.argv[0] in ("", "-c"):
|
|
||||||
raise RuntimeError(
|
|
||||||
f"Autoreloader cannot work with argv[0]={sys.argv[0]!r}"
|
|
||||||
)
|
|
||||||
if mod_spec:
|
|
||||||
# Parent exe was launched as a module rather than a script
|
|
||||||
return [sys.executable, "-m", mod_spec.name] + sys.argv[1:]
|
|
||||||
return [sys.executable] + sys.argv
|
|
||||||
|
|
||||||
|
|
||||||
def restart_with_reloader(changed=None):
|
|
||||||
"""Create a new process and a subprocess in it with the same arguments as
|
|
||||||
this one.
|
|
||||||
"""
|
|
||||||
reloaded = ",".join(changed) if changed else ""
|
|
||||||
return subprocess.Popen( # nosec B603
|
|
||||||
_get_args_for_reloading(),
|
|
||||||
env={
|
|
||||||
**os.environ,
|
|
||||||
"SANIC_SERVER_RUNNING": "true",
|
|
||||||
"SANIC_RELOADER_PROCESS": "true",
|
|
||||||
"SANIC_RELOADED_FILES": reloaded,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_file(filename, mtimes):
|
|
||||||
need_reload = False
|
|
||||||
|
|
||||||
mtime = os.stat(filename).st_mtime
|
|
||||||
old_time = mtimes.get(filename)
|
|
||||||
if old_time is None:
|
|
||||||
mtimes[filename] = mtime
|
|
||||||
elif mtime > old_time:
|
|
||||||
mtimes[filename] = mtime
|
|
||||||
need_reload = True
|
|
||||||
|
|
||||||
return need_reload
|
|
||||||
|
|
||||||
|
|
||||||
def watchdog(sleep_interval, reload_dirs):
|
|
||||||
"""Watch project files, restart worker process if a change happened.
|
|
||||||
:param sleep_interval: interval in second.
|
|
||||||
:return: Nothing
|
|
||||||
"""
|
|
||||||
|
|
||||||
def interrupt_self(*args):
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
|
|
||||||
mtimes = {}
|
|
||||||
signal.signal(signal.SIGTERM, interrupt_self)
|
|
||||||
if os.name == "nt":
|
|
||||||
signal.signal(signal.SIGBREAK, interrupt_self)
|
|
||||||
|
|
||||||
worker_process = restart_with_reloader()
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
changed = set()
|
|
||||||
for filename in itertools.chain(
|
|
||||||
_iter_module_files(),
|
|
||||||
*(d.glob("**/*") for d in reload_dirs),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
if _check_file(filename, mtimes):
|
|
||||||
path = (
|
|
||||||
filename
|
|
||||||
if isinstance(filename, str)
|
|
||||||
else filename.resolve()
|
|
||||||
)
|
|
||||||
changed.add(str(path))
|
|
||||||
except OSError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
worker_process.terminate()
|
|
||||||
worker_process.wait()
|
|
||||||
worker_process = restart_with_reloader(changed)
|
|
||||||
|
|
||||||
sleep(sleep_interval)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
worker_process.terminate()
|
|
||||||
worker_process.wait()
|
|
|
@ -9,19 +9,17 @@ from sanic.config import Config
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
from sanic.http.constants import HTTP
|
from sanic.http.constants import HTTP
|
||||||
from sanic.http.tls import get_ssl_context
|
from sanic.http.tls import get_ssl_context
|
||||||
from sanic.server.events import trigger_events
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import multiprocessing
|
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
from signal import SIG_IGN, SIGINT, SIGTERM
|
||||||
from signal import signal as signal_func
|
from signal import signal as signal_func
|
||||||
|
|
||||||
from sanic.application.ext import setup_ext
|
from sanic.application.ext import setup_ext
|
||||||
|
@ -31,11 +29,7 @@ from sanic.log import error_logger, server_logger
|
||||||
from sanic.models.server_types import Signal
|
from sanic.models.server_types import Signal
|
||||||
from sanic.server.async_server import AsyncioServer
|
from sanic.server.async_server import AsyncioServer
|
||||||
from sanic.server.protocols.http_protocol import Http3Protocol, HttpProtocol
|
from sanic.server.protocols.http_protocol import Http3Protocol, HttpProtocol
|
||||||
from sanic.server.socket import (
|
from sanic.server.socket import bind_unix_socket, remove_unix_socket
|
||||||
bind_socket,
|
|
||||||
bind_unix_socket,
|
|
||||||
remove_unix_socket,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -319,94 +313,6 @@ def _serve_http_3(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def serve_single(server_settings):
|
|
||||||
main_start = server_settings.pop("main_start", None)
|
|
||||||
main_stop = server_settings.pop("main_stop", None)
|
|
||||||
|
|
||||||
if not server_settings.get("run_async"):
|
|
||||||
# create new event_loop after fork
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
server_settings["loop"] = loop
|
|
||||||
|
|
||||||
trigger_events(main_start, server_settings["loop"])
|
|
||||||
serve(**server_settings)
|
|
||||||
trigger_events(main_stop, server_settings["loop"])
|
|
||||||
|
|
||||||
server_settings["loop"].close()
|
|
||||||
|
|
||||||
|
|
||||||
def serve_multiple(server_settings, workers):
|
|
||||||
"""Start multiple server processes simultaneously. Stop on interrupt
|
|
||||||
and terminate signals, and drain connections when complete.
|
|
||||||
|
|
||||||
:param server_settings: kw arguments to be passed to the serve function
|
|
||||||
:param workers: number of workers to launch
|
|
||||||
:param stop_event: if provided, is used as a stop signal
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
server_settings["reuse_port"] = True
|
|
||||||
server_settings["run_multiple"] = True
|
|
||||||
|
|
||||||
main_start = server_settings.pop("main_start", None)
|
|
||||||
main_stop = server_settings.pop("main_stop", None)
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
|
|
||||||
trigger_events(main_start, loop)
|
|
||||||
|
|
||||||
# Create a listening socket or use the one in settings
|
|
||||||
sock = server_settings.get("sock")
|
|
||||||
unix = server_settings["unix"]
|
|
||||||
backlog = server_settings["backlog"]
|
|
||||||
if unix:
|
|
||||||
sock = bind_unix_socket(unix, backlog=backlog)
|
|
||||||
server_settings["unix"] = unix
|
|
||||||
if sock is None:
|
|
||||||
sock = bind_socket(
|
|
||||||
server_settings["host"], server_settings["port"], backlog=backlog
|
|
||||||
)
|
|
||||||
sock.set_inheritable(True)
|
|
||||||
server_settings["sock"] = sock
|
|
||||||
server_settings["host"] = None
|
|
||||||
server_settings["port"] = None
|
|
||||||
|
|
||||||
processes = []
|
|
||||||
|
|
||||||
def sig_handler(signal, frame):
|
|
||||||
server_logger.info(
|
|
||||||
"Received signal %s. Shutting down.", Signals(signal).name
|
|
||||||
)
|
|
||||||
for process in processes:
|
|
||||||
os.kill(process.pid, SIGTERM)
|
|
||||||
|
|
||||||
signal_func(SIGINT, lambda s, f: sig_handler(s, f))
|
|
||||||
signal_func(SIGTERM, lambda s, f: sig_handler(s, f))
|
|
||||||
mp = multiprocessing.get_context("fork")
|
|
||||||
|
|
||||||
for _ in range(workers):
|
|
||||||
process = mp.Process(
|
|
||||||
target=serve,
|
|
||||||
kwargs=server_settings,
|
|
||||||
)
|
|
||||||
process.daemon = True
|
|
||||||
process.start()
|
|
||||||
processes.append(process)
|
|
||||||
|
|
||||||
for process in processes:
|
|
||||||
process.join()
|
|
||||||
|
|
||||||
# the above processes will block this until they're stopped
|
|
||||||
for process in processes:
|
|
||||||
process.terminate()
|
|
||||||
|
|
||||||
trigger_events(main_stop, loop)
|
|
||||||
|
|
||||||
sock.close()
|
|
||||||
loop.close()
|
|
||||||
remove_unix_socket(unix)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_protocol_kwargs(
|
def _build_protocol_kwargs(
|
||||||
protocol: Type[asyncio.Protocol], config: Config
|
protocol: Type[asyncio.Protocol], config: Config
|
||||||
) -> Dict[str, Union[int, float]]:
|
) -> Dict[str, Union[int, float]]:
|
||||||
|
|
|
@ -29,7 +29,7 @@ except ImportError: # websockets >= 11.0
|
||||||
|
|
||||||
from websockets.typing import Data
|
from websockets.typing import Data
|
||||||
|
|
||||||
from sanic.log import deprecation, error_logger, logger
|
from sanic.log import error_logger, logger
|
||||||
from sanic.server.protocols.base_protocol import SanicProtocol
|
from sanic.server.protocols.base_protocol import SanicProtocol
|
||||||
|
|
||||||
from ...exceptions import ServerError, WebsocketClosed
|
from ...exceptions import ServerError, WebsocketClosed
|
||||||
|
@ -99,15 +99,6 @@ class WebsocketImplProtocol:
|
||||||
def subprotocol(self):
|
def subprotocol(self):
|
||||||
return self.ws_proto.subprotocol
|
return self.ws_proto.subprotocol
|
||||||
|
|
||||||
@property
|
|
||||||
def connection(self):
|
|
||||||
deprecation(
|
|
||||||
"The connection property has been deprecated and will be removed. "
|
|
||||||
"Please use the ws_proto property instead going forward.",
|
|
||||||
22.6,
|
|
||||||
)
|
|
||||||
return self.ws_proto
|
|
||||||
|
|
||||||
def pause_frames(self):
|
def pause_frames(self):
|
||||||
if not self.can_pause:
|
if not self.can_pause:
|
||||||
return False
|
return False
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -116,7 +116,7 @@ requirements = [
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
"sanic-testing@git+https://github.com/sanic-org/sanic-testing.git@main#egg=sanic-testing>=22.12.0",
|
"sanic-testing>=23.3.0",
|
||||||
"pytest==7.1.*",
|
"pytest==7.1.*",
|
||||||
"coverage",
|
"coverage",
|
||||||
"beautifulsoup4",
|
"beautifulsoup4",
|
||||||
|
|
|
@ -448,7 +448,7 @@ def test_custom_context():
|
||||||
|
|
||||||
@pytest.mark.parametrize("use", (False, True))
|
@pytest.mark.parametrize("use", (False, True))
|
||||||
def test_uvloop_config(app: Sanic, monkeypatch, use):
|
def test_uvloop_config(app: Sanic, monkeypatch, use):
|
||||||
@app.get("/test")
|
@app.get("/test", name="test")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("ok")
|
return text("ok")
|
||||||
|
|
||||||
|
@ -571,21 +571,6 @@ def test_cannot_run_single_process_and_workers_or_auto_reload(
|
||||||
app.run(single_process=True, **extra)
|
app.run(single_process=True, **extra)
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_run_single_process_and_legacy(app: Sanic):
|
|
||||||
message = "Cannot run single process and legacy mode"
|
|
||||||
with pytest.raises(RuntimeError, match=message):
|
|
||||||
app.run(single_process=True, legacy=True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_run_without_sys_signals_with_workers(app: Sanic):
|
|
||||||
message = (
|
|
||||||
"Cannot run Sanic.serve with register_sys_signals=False. "
|
|
||||||
"Use either Sanic.serve_single or Sanic.serve_legacy."
|
|
||||||
)
|
|
||||||
with pytest.raises(RuntimeError, match=message):
|
|
||||||
app.run(register_sys_signals=False, single_process=False, legacy=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_default_configure_logging():
|
def test_default_configure_logging():
|
||||||
with patch("sanic.app.logging") as mock:
|
with patch("sanic.app.logging") as mock:
|
||||||
Sanic("Test")
|
Sanic("Test")
|
||||||
|
|
|
@ -66,3 +66,11 @@ def test_bp_copy(app: Sanic):
|
||||||
|
|
||||||
_, response = app.test_client.get("/version6/page")
|
_, response = app.test_client.get("/version6/page")
|
||||||
assert "Hello world!" in response.text
|
assert "Hello world!" in response.text
|
||||||
|
|
||||||
|
route_names = [route.name for route in app.router.routes]
|
||||||
|
assert "test_bp_copy.test_bp1.handle_request" in route_names
|
||||||
|
assert "test_bp_copy.test_bp2.handle_request" in route_names
|
||||||
|
assert "test_bp_copy.test_bp3.handle_request" in route_names
|
||||||
|
assert "test_bp_copy.test_bp4.handle_request" in route_names
|
||||||
|
assert "test_bp_copy.test_bp5.handle_request" in route_names
|
||||||
|
assert "test_bp_copy.test_bp6.handle_request" in route_names
|
||||||
|
|
|
@ -303,6 +303,10 @@ def test_bp_with_host_list(app: Sanic):
|
||||||
|
|
||||||
assert response.text == "Hello subdomain!"
|
assert response.text == "Hello subdomain!"
|
||||||
|
|
||||||
|
route_names = [r.name for r in app.router.routes]
|
||||||
|
assert "test_bp_with_host_list.test_bp_host.handler1" in route_names
|
||||||
|
assert "test_bp_with_host_list.test_bp_host.handler2" in route_names
|
||||||
|
|
||||||
|
|
||||||
def test_several_bp_with_host_list(app: Sanic):
|
def test_several_bp_with_host_list(app: Sanic):
|
||||||
bp = Blueprint(
|
bp = Blueprint(
|
||||||
|
|
|
@ -248,9 +248,9 @@ def test_fallback_with_content_type_mismatch_accept(app):
|
||||||
|
|
||||||
app.router.reset()
|
app.router.reset()
|
||||||
|
|
||||||
@app.route("/alt1")
|
@app.route("/alt1", name="alt1")
|
||||||
@app.route("/alt2", error_format="text")
|
@app.route("/alt2", error_format="text", name="alt2")
|
||||||
@app.route("/alt3", error_format="html")
|
@app.route("/alt3", error_format="html", name="alt3")
|
||||||
def handler(_):
|
def handler(_):
|
||||||
raise Exception("problem here")
|
raise Exception("problem here")
|
||||||
# Yes, we know this return value is unreachable. This is on purpose.
|
# Yes, we know this return value is unreachable. This is on purpose.
|
||||||
|
|
|
@ -285,9 +285,15 @@ def test_contextual_exception_context(debug):
|
||||||
def fail():
|
def fail():
|
||||||
raise TeapotError(context={"foo": "bar"})
|
raise TeapotError(context={"foo": "bar"})
|
||||||
|
|
||||||
app.post("/coffee/json", error_format="json")(lambda _: fail())
|
app.post("/coffee/json", error_format="json", name="json")(
|
||||||
app.post("/coffee/html", error_format="html")(lambda _: fail())
|
lambda _: fail()
|
||||||
app.post("/coffee/text", error_format="text")(lambda _: fail())
|
)
|
||||||
|
app.post("/coffee/html", error_format="html", name="html")(
|
||||||
|
lambda _: fail()
|
||||||
|
)
|
||||||
|
app.post("/coffee/text", error_format="text", name="text")(
|
||||||
|
lambda _: fail()
|
||||||
|
)
|
||||||
|
|
||||||
_, response = app.test_client.post("/coffee/json", debug=debug)
|
_, response = app.test_client.post("/coffee/json", debug=debug)
|
||||||
assert response.status == 418
|
assert response.status == 418
|
||||||
|
@ -323,9 +329,15 @@ def test_contextual_exception_extra(debug):
|
||||||
def fail():
|
def fail():
|
||||||
raise TeapotError(extra={"foo": "bar"})
|
raise TeapotError(extra={"foo": "bar"})
|
||||||
|
|
||||||
app.post("/coffee/json", error_format="json")(lambda _: fail())
|
app.post("/coffee/json", error_format="json", name="json")(
|
||||||
app.post("/coffee/html", error_format="html")(lambda _: fail())
|
lambda _: fail()
|
||||||
app.post("/coffee/text", error_format="text")(lambda _: fail())
|
)
|
||||||
|
app.post("/coffee/html", error_format="html", name="html")(
|
||||||
|
lambda _: fail()
|
||||||
|
)
|
||||||
|
app.post("/coffee/text", error_format="text", name="text")(
|
||||||
|
lambda _: fail()
|
||||||
|
)
|
||||||
|
|
||||||
_, response = app.test_client.post("/coffee/json", debug=debug)
|
_, response = app.test_client.post("/coffee/json", debug=debug)
|
||||||
assert response.status == 418
|
assert response.status == 418
|
||||||
|
|
|
@ -266,20 +266,17 @@ def test_exception_handler_response_was_sent(
|
||||||
assert "Error" in response.text
|
assert "Error" in response.text
|
||||||
|
|
||||||
|
|
||||||
def test_warn_on_duplicate(
|
def test_errir_on_duplicate(app: Sanic):
|
||||||
app: Sanic, caplog: LogCaptureFixture, recwarn: WarningsRecorder
|
|
||||||
):
|
|
||||||
@app.exception(ServerError)
|
@app.exception(ServerError)
|
||||||
async def exception_handler_1(request, exception):
|
async def exception_handler_1(request, exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
@app.exception(ServerError)
|
message = (
|
||||||
async def exception_handler_2(request, exception):
|
|
||||||
...
|
|
||||||
|
|
||||||
assert len(caplog.records) == 1
|
|
||||||
assert len(recwarn) == 1
|
|
||||||
assert caplog.records[0].message == (
|
|
||||||
"Duplicate exception handler definition on: route=__ALL_ROUTES__ and "
|
"Duplicate exception handler definition on: route=__ALL_ROUTES__ and "
|
||||||
"exception=<class 'sanic.exceptions.ServerError'>"
|
"exception=<class 'sanic.exceptions.ServerError'>"
|
||||||
)
|
)
|
||||||
|
with pytest.raises(ServerError, match=message):
|
||||||
|
|
||||||
|
@app.exception(ServerError)
|
||||||
|
async def exception_handler_2(request, exception):
|
||||||
|
...
|
||||||
|
|
|
@ -49,96 +49,6 @@ def test_multiprocessing(app):
|
||||||
assert len(process_list) == num_workers + 1
|
assert len(process_list) == num_workers + 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(signal, "SIGALRM"),
|
|
||||||
reason="SIGALRM is not implemented for this platform, we have to come "
|
|
||||||
"up with another timeout strategy to test these",
|
|
||||||
)
|
|
||||||
def test_multiprocessing_legacy(app):
|
|
||||||
"""Tests that the number of children we produce is correct"""
|
|
||||||
# Selects a number at random so we can spot check
|
|
||||||
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
|
|
||||||
process_list = set()
|
|
||||||
|
|
||||||
@app.after_server_start
|
|
||||||
async def shutdown(app):
|
|
||||||
await sleep(2.1)
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
def stop_on_alarm(*args):
|
|
||||||
for process in multiprocessing.active_children():
|
|
||||||
process_list.add(process.pid)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGALRM, stop_on_alarm)
|
|
||||||
signal.alarm(2)
|
|
||||||
app.run(HOST, 4121, workers=num_workers, debug=True, legacy=True)
|
|
||||||
|
|
||||||
assert len(process_list) == num_workers
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(signal, "SIGALRM"),
|
|
||||||
reason="SIGALRM is not implemented for this platform, we have to come "
|
|
||||||
"up with another timeout strategy to test these",
|
|
||||||
)
|
|
||||||
def test_multiprocessing_legacy_sock(app):
|
|
||||||
"""Tests that the number of children we produce is correct"""
|
|
||||||
# Selects a number at random so we can spot check
|
|
||||||
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
|
|
||||||
process_list = set()
|
|
||||||
|
|
||||||
@app.after_server_start
|
|
||||||
async def shutdown(app):
|
|
||||||
await sleep(2.1)
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
def stop_on_alarm(*args):
|
|
||||||
for process in multiprocessing.active_children():
|
|
||||||
process_list.add(process.pid)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGALRM, stop_on_alarm)
|
|
||||||
signal.alarm(2)
|
|
||||||
sock = configure_socket(
|
|
||||||
{
|
|
||||||
"host": HOST,
|
|
||||||
"port": 4121,
|
|
||||||
"unix": None,
|
|
||||||
"backlog": 100,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
app.run(workers=num_workers, debug=True, legacy=True, sock=sock)
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
assert len(process_list) == num_workers
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(signal, "SIGALRM"),
|
|
||||||
reason="SIGALRM is not implemented for this platform, we have to come "
|
|
||||||
"up with another timeout strategy to test these",
|
|
||||||
)
|
|
||||||
def test_multiprocessing_legacy_unix(app):
|
|
||||||
"""Tests that the number of children we produce is correct"""
|
|
||||||
# Selects a number at random so we can spot check
|
|
||||||
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
|
|
||||||
process_list = set()
|
|
||||||
|
|
||||||
@app.after_server_start
|
|
||||||
async def shutdown(app):
|
|
||||||
await sleep(2.1)
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
def stop_on_alarm(*args):
|
|
||||||
for process in multiprocessing.active_children():
|
|
||||||
process_list.add(process.pid)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGALRM, stop_on_alarm)
|
|
||||||
signal.alarm(2)
|
|
||||||
app.run(workers=num_workers, debug=True, legacy=True, unix="./test.sock")
|
|
||||||
|
|
||||||
assert len(process_list) == num_workers
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
not hasattr(signal, "SIGALRM"),
|
not hasattr(signal, "SIGALRM"),
|
||||||
reason="SIGALRM is not implemented for this platform",
|
reason="SIGALRM is not implemented for this platform",
|
||||||
|
|
|
@ -105,11 +105,11 @@ def test_html(app):
|
||||||
return html("<h1>Hello</h1>")
|
return html("<h1>Hello</h1>")
|
||||||
|
|
||||||
@app.route("/foo")
|
@app.route("/foo")
|
||||||
async def handler(request):
|
async def handler_foo(request):
|
||||||
return html(Foo())
|
return html(Foo())
|
||||||
|
|
||||||
@app.route("/bar")
|
@app.route("/bar")
|
||||||
async def handler(request):
|
async def handler_bar(request):
|
||||||
return html(Bar())
|
return html(Bar())
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
|
@ -2199,10 +2199,25 @@ def test_safe_method_with_body(app):
|
||||||
assert response.body == b"OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
|
|
||||||
def test_conflicting_body_methods_overload(app):
|
@pytest.mark.asyncio
|
||||||
|
async def test_conflicting_body_methods_overload_error(app: Sanic):
|
||||||
@app.put("/")
|
@app.put("/")
|
||||||
@app.put("/p/")
|
@app.put("/p/")
|
||||||
@app.put("/p/<foo>")
|
@app.put("/p/<foo>")
|
||||||
|
async def put(request, foo=None):
|
||||||
|
...
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ServerError,
|
||||||
|
match="Duplicate route names detected: test_conflicting_body_methods_overload_error\.put.*",
|
||||||
|
):
|
||||||
|
await app._startup()
|
||||||
|
|
||||||
|
|
||||||
|
def test_conflicting_body_methods_overload(app: Sanic):
|
||||||
|
@app.put("/", name="one")
|
||||||
|
@app.put("/p/", name="two")
|
||||||
|
@app.put("/p/<foo>", name="three")
|
||||||
async def put(request, foo=None):
|
async def put(request, foo=None):
|
||||||
return json(
|
return json(
|
||||||
{"name": request.route.name, "body": str(request.body), "foo": foo}
|
{"name": request.route.name, "body": str(request.body), "foo": foo}
|
||||||
|
@ -2220,21 +2235,21 @@ def test_conflicting_body_methods_overload(app):
|
||||||
_, response = app.test_client.put("/", json=payload)
|
_, response = app.test_client.put("/", json=payload)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.json == {
|
assert response.json == {
|
||||||
"name": "test_conflicting_body_methods_overload.put",
|
"name": "test_conflicting_body_methods_overload.one",
|
||||||
"foo": None,
|
"foo": None,
|
||||||
"body": data,
|
"body": data,
|
||||||
}
|
}
|
||||||
_, response = app.test_client.put("/p", json=payload)
|
_, response = app.test_client.put("/p", json=payload)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.json == {
|
assert response.json == {
|
||||||
"name": "test_conflicting_body_methods_overload.put",
|
"name": "test_conflicting_body_methods_overload.two",
|
||||||
"foo": None,
|
"foo": None,
|
||||||
"body": data,
|
"body": data,
|
||||||
}
|
}
|
||||||
_, response = app.test_client.put("/p/test", json=payload)
|
_, response = app.test_client.put("/p/test", json=payload)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.json == {
|
assert response.json == {
|
||||||
"name": "test_conflicting_body_methods_overload.put",
|
"name": "test_conflicting_body_methods_overload.three",
|
||||||
"foo": "test",
|
"foo": "test",
|
||||||
"body": data,
|
"body": data,
|
||||||
}
|
}
|
||||||
|
@ -2247,9 +2262,26 @@ def test_conflicting_body_methods_overload(app):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_handler_overload(app):
|
@pytest.mark.asyncio
|
||||||
|
async def test_handler_overload_error(app: Sanic):
|
||||||
@app.get("/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>")
|
@app.get("/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>")
|
||||||
@app.post("/long/sub/route/")
|
@app.post("/long/sub/route/")
|
||||||
|
def handler(request, **kwargs):
|
||||||
|
...
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ServerError,
|
||||||
|
match="Duplicate route names detected: test_handler_overload_error\.handler.*",
|
||||||
|
):
|
||||||
|
await app._startup()
|
||||||
|
|
||||||
|
|
||||||
|
def test_handler_overload(app: Sanic):
|
||||||
|
@app.get(
|
||||||
|
"/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>",
|
||||||
|
name="one",
|
||||||
|
)
|
||||||
|
@app.post("/long/sub/route/", name="two")
|
||||||
def handler(request, **kwargs):
|
def handler(request, **kwargs):
|
||||||
return json(kwargs)
|
return json(kwargs)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from sanic_testing.testing import SanicTestClient
|
||||||
|
|
||||||
from sanic import Blueprint, Sanic
|
from sanic import Blueprint, Sanic
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import NotFound, SanicException
|
from sanic.exceptions import NotFound, SanicException, ServerError
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import empty, json, text
|
from sanic.response import empty, json, text
|
||||||
|
|
||||||
|
@ -744,8 +744,8 @@ def test_route_duplicate(app):
|
||||||
|
|
||||||
|
|
||||||
def test_double_stack_route(app):
|
def test_double_stack_route(app):
|
||||||
@app.route("/test/1")
|
@app.route("/test/1", name="test1")
|
||||||
@app.route("/test/2")
|
@app.route("/test/2", name="test2")
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
|
@ -759,8 +759,8 @@ def test_double_stack_route(app):
|
||||||
async def test_websocket_route_asgi(app):
|
async def test_websocket_route_asgi(app):
|
||||||
ev = asyncio.Event()
|
ev = asyncio.Event()
|
||||||
|
|
||||||
@app.websocket("/test/1")
|
@app.websocket("/test/1", name="test1")
|
||||||
@app.websocket("/test/2")
|
@app.websocket("/test/2", name="test2")
|
||||||
async def handler(request, ws):
|
async def handler(request, ws):
|
||||||
ev.set()
|
ev.set()
|
||||||
|
|
||||||
|
@ -1279,7 +1279,7 @@ async def test_added_callable_route_ctx_kwargs(app):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_duplicate_route_deprecation(app):
|
async def test_duplicate_route_error(app):
|
||||||
@app.route("/foo", name="duped")
|
@app.route("/foo", name="duped")
|
||||||
async def handler_foo(request):
|
async def handler_foo(request):
|
||||||
return text("...")
|
return text("...")
|
||||||
|
@ -1289,9 +1289,7 @@ async def test_duplicate_route_deprecation(app):
|
||||||
return text("...")
|
return text("...")
|
||||||
|
|
||||||
message = (
|
message = (
|
||||||
r"\[DEPRECATION v23\.3\] Duplicate route names detected: "
|
"Duplicate route names detected: test_duplicate_route_error.duped."
|
||||||
r"test_duplicate_route_deprecation\.duped\. In the future, "
|
|
||||||
r"Sanic will enforce uniqueness in route naming\."
|
|
||||||
)
|
)
|
||||||
with pytest.warns(DeprecationWarning, match=message):
|
with pytest.raises(ServerError, match=message):
|
||||||
await app._startup()
|
await app._startup()
|
||||||
|
|
|
@ -66,8 +66,8 @@ def test_no_register_system_signals_fails(app):
|
||||||
app.listener("after_server_stop")(after)
|
app.listener("after_server_stop")(after)
|
||||||
|
|
||||||
message = (
|
message = (
|
||||||
"Cannot run Sanic.serve with register_sys_signals=False. Use "
|
r"Cannot run Sanic\.serve with register_sys_signals=False\. Use "
|
||||||
"either Sanic.serve_single or Sanic.serve_legacy."
|
r"Sanic.serve_single\."
|
||||||
)
|
)
|
||||||
with pytest.raises(RuntimeError, match=message):
|
with pytest.raises(RuntimeError, match=message):
|
||||||
app.prepare(HOST, PORT, register_sys_signals=False)
|
app.prepare(HOST, PORT, register_sys_signals=False)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from time import gmtime, strftime
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic import Sanic, text
|
from sanic import Sanic, text
|
||||||
from sanic.exceptions import FileNotFound
|
from sanic.exceptions import FileNotFound, ServerError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
|
@ -108,14 +108,9 @@ def test_static_file_pathlib(app, static_file_directory, file_name):
|
||||||
def test_static_file_bytes(app, static_file_directory, file_name):
|
def test_static_file_bytes(app, static_file_directory, file_name):
|
||||||
bsep = os.path.sep.encode("utf-8")
|
bsep = os.path.sep.encode("utf-8")
|
||||||
file_path = static_file_directory.encode("utf-8") + bsep + file_name
|
file_path = static_file_directory.encode("utf-8") + bsep + file_name
|
||||||
message = (
|
message = "Static file or directory must be a path-like object or string"
|
||||||
"Serving a static directory with a bytes "
|
with pytest.raises(TypeError, match=message):
|
||||||
"string is deprecated and will be removed in v22.9."
|
|
||||||
)
|
|
||||||
with pytest.warns(DeprecationWarning, match=message):
|
|
||||||
app.static("/testing.file", file_path)
|
app.static("/testing.file", file_path)
|
||||||
request, response = app.test_client.get("/testing.file")
|
|
||||||
assert response.status == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -523,10 +518,26 @@ def test_no_stack_trace_on_not_found(app, static_file_directory, caplog):
|
||||||
assert response.text == "No file: /static/non_existing_file.file"
|
assert response.text == "No file: /static/non_existing_file.file"
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_statics(app, static_file_directory):
|
@pytest.mark.asyncio
|
||||||
|
async def test_multiple_statics_error(app, static_file_directory):
|
||||||
app.static("/file", get_file_path(static_file_directory, "test.file"))
|
app.static("/file", get_file_path(static_file_directory, "test.file"))
|
||||||
app.static("/png", get_file_path(static_file_directory, "python.png"))
|
app.static("/png", get_file_path(static_file_directory, "python.png"))
|
||||||
|
|
||||||
|
message = (
|
||||||
|
r"Duplicate route names detected: test_multiple_statics_error\.static"
|
||||||
|
)
|
||||||
|
with pytest.raises(ServerError, match=message):
|
||||||
|
await app._startup()
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_statics(app, static_file_directory):
|
||||||
|
app.static(
|
||||||
|
"/file", get_file_path(static_file_directory, "test.file"), name="file"
|
||||||
|
)
|
||||||
|
app.static(
|
||||||
|
"/png", get_file_path(static_file_directory, "python.png"), name="png"
|
||||||
|
)
|
||||||
|
|
||||||
_, response = app.test_client.get("/file")
|
_, response = app.test_client.get("/file")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(
|
assert response.body == get_file_content(
|
||||||
|
@ -540,10 +551,22 @@ def test_multiple_statics(app, static_file_directory):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_resource_type_default(app, static_file_directory):
|
@pytest.mark.asyncio
|
||||||
|
async def test_resource_type_default_error(app, static_file_directory):
|
||||||
app.static("/static", static_file_directory)
|
app.static("/static", static_file_directory)
|
||||||
app.static("/file", get_file_path(static_file_directory, "test.file"))
|
app.static("/file", get_file_path(static_file_directory, "test.file"))
|
||||||
|
|
||||||
|
message = r"Duplicate route names detected: test_resource_type_default_error\.static"
|
||||||
|
with pytest.raises(ServerError, match=message):
|
||||||
|
await app._startup()
|
||||||
|
|
||||||
|
|
||||||
|
def test_resource_type_default(app, static_file_directory):
|
||||||
|
app.static("/static", static_file_directory, name="static")
|
||||||
|
app.static(
|
||||||
|
"/file", get_file_path(static_file_directory, "test.file"), name="file"
|
||||||
|
)
|
||||||
|
|
||||||
_, response = app.test_client.get("/static")
|
_, response = app.test_client.get("/static")
|
||||||
assert response.status == 404
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
|
@ -72,24 +72,6 @@ def test_not_have_multiplexer_single(app: Sanic):
|
||||||
assert not event.is_set()
|
assert not event.is_set()
|
||||||
|
|
||||||
|
|
||||||
def test_not_have_multiplexer_legacy(app: Sanic):
|
|
||||||
event = Event()
|
|
||||||
|
|
||||||
@app.main_process_start
|
|
||||||
async def setup(app, _):
|
|
||||||
app.shared_ctx.event = event
|
|
||||||
|
|
||||||
@app.after_server_start
|
|
||||||
def stop(app):
|
|
||||||
if hasattr(app, "m") and isinstance(app.m, WorkerMultiplexer):
|
|
||||||
app.shared_ctx.event.set()
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
app.run(legacy=True)
|
|
||||||
|
|
||||||
assert not event.is_set()
|
|
||||||
|
|
||||||
|
|
||||||
def test_ack(worker_state: Dict[str, Any], m: WorkerMultiplexer):
|
def test_ack(worker_state: Dict[str, Any], m: WorkerMultiplexer):
|
||||||
worker_state["Test"] = {"foo": "bar"}
|
worker_state["Test"] = {"foo": "bar"}
|
||||||
m.ack()
|
m.ack()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user