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.helpers import Default, _default
|
||||
from sanic.http import Stage
|
||||
from sanic.log import (
|
||||
LOGGING_CONFIG_DEFAULTS,
|
||||
deprecation,
|
||||
error_logger,
|
||||
logger,
|
||||
)
|
||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||
from sanic.middleware import Middleware, MiddlewareLocation
|
||||
from sanic.mixins.listeners import ListenerEvent
|
||||
from sanic.mixins.startup import StartupMixin
|
||||
|
@ -1584,17 +1579,19 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
|
|||
self.signalize(self.config.TOUCHUP)
|
||||
self.finalize()
|
||||
|
||||
route_names = [route.name for route in self.router.routes]
|
||||
route_names = [route.extra.ident for route in self.router.routes]
|
||||
duplicates = {
|
||||
name for name in route_names if route_names.count(name) > 1
|
||||
}
|
||||
if duplicates:
|
||||
names = ", ".join(duplicates)
|
||||
deprecation(
|
||||
f"Duplicate route names detected: {names}. In the future, "
|
||||
"Sanic will enforce uniqueness in route naming.",
|
||||
23.3,
|
||||
message = (
|
||||
f"Duplicate route names detected: {names}. You should rename "
|
||||
"one or more of them explicitly by using the `name` param, "
|
||||
"or changing the implicit name derived from the class and "
|
||||
"function name. For more details, please see ___."
|
||||
)
|
||||
raise ServerError(message)
|
||||
|
||||
Sanic._check_uvloop_conflict()
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ class Blueprint(BaseSanic):
|
|||
"_future_listeners",
|
||||
"_future_exceptions",
|
||||
"_future_signals",
|
||||
"copied_from",
|
||||
"ctx",
|
||||
"exceptions",
|
||||
"host",
|
||||
|
@ -118,6 +119,7 @@ class Blueprint(BaseSanic):
|
|||
):
|
||||
super().__init__(name=name)
|
||||
self.reset()
|
||||
self.copied_from = ""
|
||||
self.ctx = SimpleNamespace()
|
||||
self.host = host
|
||||
self.strict_slashes = strict_slashes
|
||||
|
@ -213,6 +215,7 @@ class Blueprint(BaseSanic):
|
|||
self.reset()
|
||||
new_bp = deepcopy(self)
|
||||
new_bp.name = name
|
||||
new_bp.copied_from = self.name
|
||||
|
||||
if not isinstance(url_prefix, Default):
|
||||
new_bp.url_prefix = url_prefix
|
||||
|
@ -352,6 +355,16 @@ class Blueprint(BaseSanic):
|
|||
|
||||
registered.add(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 = (
|
||||
routes.extend if isinstance(route, list) else routes.append
|
||||
)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
@ -6,7 +5,7 @@ import sys
|
|||
from argparse import Namespace
|
||||
from functools import partial
|
||||
from textwrap import indent
|
||||
from typing import List, Union, cast
|
||||
from typing import List, Union
|
||||
|
||||
from sanic.app import Sanic
|
||||
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.inspector import make_inspector_parser
|
||||
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
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
if self.args.inspect or self.args.inspect_raw or self.args.trigger:
|
||||
self._inspector_legacy(app_loader)
|
||||
return
|
||||
|
||||
try:
|
||||
app = self._get_app(app_loader)
|
||||
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)
|
||||
if self.args.single:
|
||||
serve = Sanic.serve_single
|
||||
elif self.args.legacy:
|
||||
serve = Sanic.serve_legacy
|
||||
else:
|
||||
serve = partial(Sanic.serve, app_loader=app_loader)
|
||||
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):
|
||||
args = sys.argv[2:]
|
||||
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)
|
||||
sys.exit(1)
|
||||
if self.args.inspect or self.args.inspect_raw:
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def _get_app(self, app_loader: AppLoader):
|
||||
try:
|
||||
|
@ -251,7 +216,6 @@ Or, a path to a directory to run as a simple HTTP server:
|
|||
"workers": self.args.workers,
|
||||
"auto_tls": self.args.auto_tls,
|
||||
"single_process": self.args.single,
|
||||
"legacy": self.args.legacy,
|
||||
}
|
||||
|
||||
for maybe_arg in ("auto_reload", "dev"):
|
||||
|
|
|
@ -93,32 +93,6 @@ class ApplicationGroup(Group):
|
|||
"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):
|
||||
|
@ -247,11 +221,6 @@ class WorkerGroup(Group):
|
|||
action="store_true",
|
||||
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(
|
||||
"--access-logs",
|
||||
dest="access_log",
|
||||
|
|
|
@ -3,7 +3,8 @@ from __future__ import annotations
|
|||
from typing import Dict, List, Optional, Tuple, Type
|
||||
|
||||
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.response import text
|
||||
|
||||
|
@ -43,16 +44,11 @@ class ErrorHandler:
|
|||
if name is None:
|
||||
name = "__ALL_ROUTES__"
|
||||
|
||||
error_logger.warning(
|
||||
message = (
|
||||
f"Duplicate exception handler definition on: route={name} "
|
||||
f"and exception={exc}"
|
||||
)
|
||||
deprecation(
|
||||
"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,
|
||||
)
|
||||
raise ServerError(message)
|
||||
self.cached_handlers[key] = handler
|
||||
|
||||
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.tls import get_ssl_context, process_to_context
|
||||
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.server import Signal as ServerSignal
|
||||
from sanic.server import try_use_uvloop
|
||||
from sanic.server.async_server import AsyncioServer
|
||||
from sanic.server.events import trigger_events
|
||||
from sanic.server.legacy import watchdog
|
||||
from sanic.server.loop import try_windows_loop
|
||||
from sanic.server.protocols.http_protocol import HttpProtocol
|
||||
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.worker.loader import AppLoader
|
||||
from sanic.worker.manager import WorkerManager
|
||||
|
@ -135,7 +134,6 @@ class StartupMixin(metaclass=SanicMeta):
|
|||
motd_display: Optional[Dict[str, str]] = None,
|
||||
auto_tls: bool = False,
|
||||
single_process: bool = False,
|
||||
legacy: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Run the HTTP Server and listen until keyboard interrupt or term
|
||||
|
@ -197,13 +195,10 @@ class StartupMixin(metaclass=SanicMeta):
|
|||
motd_display=motd_display,
|
||||
auto_tls=auto_tls,
|
||||
single_process=single_process,
|
||||
legacy=legacy,
|
||||
)
|
||||
|
||||
if single_process:
|
||||
serve = self.__class__.serve_single
|
||||
elif legacy:
|
||||
serve = self.__class__.serve_legacy
|
||||
else:
|
||||
serve = self.__class__.serve
|
||||
serve(primary=self) # type: ignore
|
||||
|
@ -235,7 +230,6 @@ class StartupMixin(metaclass=SanicMeta):
|
|||
coffee: bool = False,
|
||||
auto_tls: bool = False,
|
||||
single_process: bool = False,
|
||||
legacy: bool = False,
|
||||
) -> None:
|
||||
if version == 3 and self.state.server_info:
|
||||
raise RuntimeError(
|
||||
|
@ -264,13 +258,10 @@ class StartupMixin(metaclass=SanicMeta):
|
|||
"or auto-reload"
|
||||
)
|
||||
|
||||
if single_process and legacy:
|
||||
raise RuntimeError("Cannot run single process and legacy mode")
|
||||
|
||||
if register_sys_signals is False and not (single_process or legacy):
|
||||
if register_sys_signals is False and not single_process:
|
||||
raise RuntimeError(
|
||||
"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:
|
||||
|
@ -956,76 +947,6 @@ class StartupMixin(metaclass=SanicMeta):
|
|||
cls._cleanup_env_vars()
|
||||
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(
|
||||
self,
|
||||
primary: Sanic,
|
||||
|
|
|
@ -3,7 +3,7 @@ from functools import partial, wraps
|
|||
from mimetypes import guess_type
|
||||
from os import PathLike, path
|
||||
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 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.handlers import ContentRangeHandler
|
||||
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.models.futures import FutureStatic
|
||||
from sanic.request import Request
|
||||
|
@ -31,7 +31,7 @@ class StaticMixin(BaseMixin, metaclass=SanicMeta):
|
|||
def static(
|
||||
self,
|
||||
uri: str,
|
||||
file_or_directory: Union[PathLike, str, bytes],
|
||||
file_or_directory: Union[PathLike, str],
|
||||
pattern: str = r"/?.+",
|
||||
use_modified_since: bool = True,
|
||||
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}"
|
||||
)
|
||||
|
||||
if isinstance(file_or_directory, bytes):
|
||||
deprecation(
|
||||
"Serving a static directory with a bytes string is "
|
||||
"deprecated and will be removed in v22.9.",
|
||||
22.9,
|
||||
try:
|
||||
file_or_directory = Path(file_or_directory)
|
||||
except TypeError:
|
||||
raise TypeError(
|
||||
"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):
|
||||
raise ValueError(
|
||||
|
|
|
@ -55,7 +55,7 @@ from sanic.headers import (
|
|||
parse_xforwarded,
|
||||
)
|
||||
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.response import BaseHTTPResponse, HTTPResponse
|
||||
|
||||
|
@ -205,16 +205,6 @@ class Request:
|
|||
def generate_id(*_):
|
||||
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
|
||||
def stream_id(self):
|
||||
"""
|
||||
|
|
|
@ -44,7 +44,9 @@ class Router(BaseRouter):
|
|||
raise MethodNotAllowed(
|
||||
f"Method {method} not allowed for URL {path}",
|
||||
method=method,
|
||||
allowed_methods=e.allowed_methods,
|
||||
allowed_methods=tuple(e.allowed_methods)
|
||||
if e.allowed_methods
|
||||
else None,
|
||||
) from None
|
||||
|
||||
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||
|
@ -133,7 +135,16 @@ class Router(BaseRouter):
|
|||
if 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.extra.ident = ident
|
||||
route.extra.ignore_body = ignore_body
|
||||
route.extra.stream = stream
|
||||
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.loop import try_use_uvloop
|
||||
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__ = (
|
||||
|
@ -11,7 +11,5 @@ __all__ = (
|
|||
"HttpProtocol",
|
||||
"Signal",
|
||||
"serve",
|
||||
"serve_multiple",
|
||||
"serve_single",
|
||||
"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.http.constants import HTTP
|
||||
from sanic.http.tls import get_ssl_context
|
||||
from sanic.server.events import trigger_events
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic.app import Sanic
|
||||
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
import os
|
||||
import socket
|
||||
|
||||
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 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.server.async_server import AsyncioServer
|
||||
from sanic.server.protocols.http_protocol import Http3Protocol, HttpProtocol
|
||||
from sanic.server.socket import (
|
||||
bind_socket,
|
||||
bind_unix_socket,
|
||||
remove_unix_socket,
|
||||
)
|
||||
from sanic.server.socket import bind_unix_socket, remove_unix_socket
|
||||
|
||||
|
||||
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(
|
||||
protocol: Type[asyncio.Protocol], config: Config
|
||||
) -> Dict[str, Union[int, float]]:
|
||||
|
|
|
@ -29,7 +29,7 @@ except ImportError: # websockets >= 11.0
|
|||
|
||||
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 ...exceptions import ServerError, WebsocketClosed
|
||||
|
@ -99,15 +99,6 @@ class WebsocketImplProtocol:
|
|||
def subprotocol(self):
|
||||
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):
|
||||
if not self.can_pause:
|
||||
return False
|
||||
|
|
2
setup.py
2
setup.py
|
@ -116,7 +116,7 @@ requirements = [
|
|||
]
|
||||
|
||||
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.*",
|
||||
"coverage",
|
||||
"beautifulsoup4",
|
||||
|
|
|
@ -448,7 +448,7 @@ def test_custom_context():
|
|||
|
||||
@pytest.mark.parametrize("use", (False, True))
|
||||
def test_uvloop_config(app: Sanic, monkeypatch, use):
|
||||
@app.get("/test")
|
||||
@app.get("/test", name="test")
|
||||
def handler(request):
|
||||
return text("ok")
|
||||
|
||||
|
@ -571,21 +571,6 @@ def test_cannot_run_single_process_and_workers_or_auto_reload(
|
|||
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():
|
||||
with patch("sanic.app.logging") as mock:
|
||||
Sanic("Test")
|
||||
|
|
|
@ -66,3 +66,11 @@ def test_bp_copy(app: Sanic):
|
|||
|
||||
_, response = app.test_client.get("/version6/page")
|
||||
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!"
|
||||
|
||||
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):
|
||||
bp = Blueprint(
|
||||
|
|
|
@ -248,9 +248,9 @@ def test_fallback_with_content_type_mismatch_accept(app):
|
|||
|
||||
app.router.reset()
|
||||
|
||||
@app.route("/alt1")
|
||||
@app.route("/alt2", error_format="text")
|
||||
@app.route("/alt3", error_format="html")
|
||||
@app.route("/alt1", name="alt1")
|
||||
@app.route("/alt2", error_format="text", name="alt2")
|
||||
@app.route("/alt3", error_format="html", name="alt3")
|
||||
def handler(_):
|
||||
raise Exception("problem here")
|
||||
# Yes, we know this return value is unreachable. This is on purpose.
|
||||
|
|
|
@ -285,9 +285,15 @@ def test_contextual_exception_context(debug):
|
|||
def fail():
|
||||
raise TeapotError(context={"foo": "bar"})
|
||||
|
||||
app.post("/coffee/json", error_format="json")(lambda _: fail())
|
||||
app.post("/coffee/html", error_format="html")(lambda _: fail())
|
||||
app.post("/coffee/text", error_format="text")(lambda _: fail())
|
||||
app.post("/coffee/json", error_format="json", name="json")(
|
||||
lambda _: fail()
|
||||
)
|
||||
app.post("/coffee/html", error_format="html", name="html")(
|
||||
lambda _: fail()
|
||||
)
|
||||
app.post("/coffee/text", error_format="text", name="text")(
|
||||
lambda _: fail()
|
||||
)
|
||||
|
||||
_, response = app.test_client.post("/coffee/json", debug=debug)
|
||||
assert response.status == 418
|
||||
|
@ -323,9 +329,15 @@ def test_contextual_exception_extra(debug):
|
|||
def fail():
|
||||
raise TeapotError(extra={"foo": "bar"})
|
||||
|
||||
app.post("/coffee/json", error_format="json")(lambda _: fail())
|
||||
app.post("/coffee/html", error_format="html")(lambda _: fail())
|
||||
app.post("/coffee/text", error_format="text")(lambda _: fail())
|
||||
app.post("/coffee/json", error_format="json", name="json")(
|
||||
lambda _: fail()
|
||||
)
|
||||
app.post("/coffee/html", error_format="html", name="html")(
|
||||
lambda _: fail()
|
||||
)
|
||||
app.post("/coffee/text", error_format="text", name="text")(
|
||||
lambda _: fail()
|
||||
)
|
||||
|
||||
_, response = app.test_client.post("/coffee/json", debug=debug)
|
||||
assert response.status == 418
|
||||
|
|
|
@ -266,20 +266,17 @@ def test_exception_handler_response_was_sent(
|
|||
assert "Error" in response.text
|
||||
|
||||
|
||||
def test_warn_on_duplicate(
|
||||
app: Sanic, caplog: LogCaptureFixture, recwarn: WarningsRecorder
|
||||
):
|
||||
def test_errir_on_duplicate(app: Sanic):
|
||||
@app.exception(ServerError)
|
||||
async def exception_handler_1(request, exception):
|
||||
...
|
||||
|
||||
@app.exception(ServerError)
|
||||
async def exception_handler_2(request, exception):
|
||||
...
|
||||
|
||||
assert len(caplog.records) == 1
|
||||
assert len(recwarn) == 1
|
||||
assert caplog.records[0].message == (
|
||||
message = (
|
||||
"Duplicate exception handler definition on: route=__ALL_ROUTES__ and "
|
||||
"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
|
||||
|
||||
|
||||
@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(
|
||||
not hasattr(signal, "SIGALRM"),
|
||||
reason="SIGALRM is not implemented for this platform",
|
||||
|
|
|
@ -105,11 +105,11 @@ def test_html(app):
|
|||
return html("<h1>Hello</h1>")
|
||||
|
||||
@app.route("/foo")
|
||||
async def handler(request):
|
||||
async def handler_foo(request):
|
||||
return html(Foo())
|
||||
|
||||
@app.route("/bar")
|
||||
async def handler(request):
|
||||
async def handler_bar(request):
|
||||
return html(Bar())
|
||||
|
||||
request, response = app.test_client.get("/")
|
||||
|
@ -2199,10 +2199,25 @@ def test_safe_method_with_body(app):
|
|||
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("/p/")
|
||||
@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):
|
||||
return json(
|
||||
{"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)
|
||||
assert response.status == 200
|
||||
assert response.json == {
|
||||
"name": "test_conflicting_body_methods_overload.put",
|
||||
"name": "test_conflicting_body_methods_overload.one",
|
||||
"foo": None,
|
||||
"body": data,
|
||||
}
|
||||
_, response = app.test_client.put("/p", json=payload)
|
||||
assert response.status == 200
|
||||
assert response.json == {
|
||||
"name": "test_conflicting_body_methods_overload.put",
|
||||
"name": "test_conflicting_body_methods_overload.two",
|
||||
"foo": None,
|
||||
"body": data,
|
||||
}
|
||||
_, response = app.test_client.put("/p/test", json=payload)
|
||||
assert response.status == 200
|
||||
assert response.json == {
|
||||
"name": "test_conflicting_body_methods_overload.put",
|
||||
"name": "test_conflicting_body_methods_overload.three",
|
||||
"foo": "test",
|
||||
"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.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):
|
||||
return json(kwargs)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from sanic_testing.testing import SanicTestClient
|
|||
|
||||
from sanic import Blueprint, Sanic
|
||||
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.response import empty, json, text
|
||||
|
||||
|
@ -744,8 +744,8 @@ def test_route_duplicate(app):
|
|||
|
||||
|
||||
def test_double_stack_route(app):
|
||||
@app.route("/test/1")
|
||||
@app.route("/test/2")
|
||||
@app.route("/test/1", name="test1")
|
||||
@app.route("/test/2", name="test2")
|
||||
async def handler1(request):
|
||||
return text("OK")
|
||||
|
||||
|
@ -759,8 +759,8 @@ def test_double_stack_route(app):
|
|||
async def test_websocket_route_asgi(app):
|
||||
ev = asyncio.Event()
|
||||
|
||||
@app.websocket("/test/1")
|
||||
@app.websocket("/test/2")
|
||||
@app.websocket("/test/1", name="test1")
|
||||
@app.websocket("/test/2", name="test2")
|
||||
async def handler(request, ws):
|
||||
ev.set()
|
||||
|
||||
|
@ -1279,7 +1279,7 @@ async def test_added_callable_route_ctx_kwargs(app):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_route_deprecation(app):
|
||||
async def test_duplicate_route_error(app):
|
||||
@app.route("/foo", name="duped")
|
||||
async def handler_foo(request):
|
||||
return text("...")
|
||||
|
@ -1289,9 +1289,7 @@ async def test_duplicate_route_deprecation(app):
|
|||
return text("...")
|
||||
|
||||
message = (
|
||||
r"\[DEPRECATION v23\.3\] Duplicate route names detected: "
|
||||
r"test_duplicate_route_deprecation\.duped\. In the future, "
|
||||
r"Sanic will enforce uniqueness in route naming\."
|
||||
"Duplicate route names detected: test_duplicate_route_error.duped."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=message):
|
||||
with pytest.raises(ServerError, match=message):
|
||||
await app._startup()
|
||||
|
|
|
@ -66,8 +66,8 @@ def test_no_register_system_signals_fails(app):
|
|||
app.listener("after_server_stop")(after)
|
||||
|
||||
message = (
|
||||
"Cannot run Sanic.serve with register_sys_signals=False. Use "
|
||||
"either Sanic.serve_single or Sanic.serve_legacy."
|
||||
r"Cannot run Sanic\.serve with register_sys_signals=False\. Use "
|
||||
r"Sanic.serve_single\."
|
||||
)
|
||||
with pytest.raises(RuntimeError, match=message):
|
||||
app.prepare(HOST, PORT, register_sys_signals=False)
|
||||
|
|
|
@ -9,7 +9,7 @@ from time import gmtime, strftime
|
|||
import pytest
|
||||
|
||||
from sanic import Sanic, text
|
||||
from sanic.exceptions import FileNotFound
|
||||
from sanic.exceptions import FileNotFound, ServerError
|
||||
|
||||
|
||||
@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):
|
||||
bsep = os.path.sep.encode("utf-8")
|
||||
file_path = static_file_directory.encode("utf-8") + bsep + file_name
|
||||
message = (
|
||||
"Serving a static directory with a bytes "
|
||||
"string is deprecated and will be removed in v22.9."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=message):
|
||||
message = "Static file or directory must be a path-like object or string"
|
||||
with pytest.raises(TypeError, match=message):
|
||||
app.static("/testing.file", file_path)
|
||||
request, response = app.test_client.get("/testing.file")
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
@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"
|
||||
|
||||
|
||||
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("/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")
|
||||
assert response.status == 200
|
||||
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("/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")
|
||||
assert response.status == 404
|
||||
|
||||
|
|
|
@ -72,24 +72,6 @@ def test_not_have_multiplexer_single(app: Sanic):
|
|||
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):
|
||||
worker_state["Test"] = {"foo": "bar"}
|
||||
m.ack()
|
||||
|
|
Loading…
Reference in New Issue
Block a user