from __future__ import annotations import asyncio import logging import logging.config import re import sys from asyncio import ( AbstractEventLoop, CancelledError, Task, ensure_future, get_running_loop, wait_for, ) from asyncio.futures import Future from collections import defaultdict, deque from collections.abc import Awaitable, Coroutine, Iterable, Iterator from contextlib import contextmanager, suppress from enum import Enum from functools import partial, wraps from inspect import isawaitable from os import environ from pathlib import Path from socket import socket from traceback import format_exc from types import SimpleNamespace from typing import ( TYPE_CHECKING, Any, AnyStr, Callable, ClassVar, Generic, Literal, TypeVar, cast, overload, ) from urllib.parse import urlencode, urlunparse from sanic_routing.exceptions import FinalizationError, NotFound from sanic_routing.route import Route from sanic.application.ext import setup_ext from sanic.application.state import ApplicationState, ServerStage from sanic.asgi import ASGIApp, Lifespan from sanic.base.root import BaseSanic from sanic.blueprint_group import BlueprintGroup from sanic.blueprints import Blueprint from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support from sanic.config import SANIC_PREFIX, Config from sanic.exceptions import ( BadRequest, SanicException, ServerError, URLBuildError, ) from sanic.handlers import ErrorHandler from sanic.helpers import Default, _default from sanic.http import Stage from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger from sanic.logging.deprecation import deprecation from sanic.logging.setup import setup_logging from sanic.middleware import Middleware, MiddlewareLocation from sanic.mixins.commands import CommandMixin from sanic.mixins.listeners import ListenerEvent from sanic.mixins.startup import StartupMixin from sanic.mixins.static import StaticHandleMixin from sanic.models.ctx_types import REPLContext from sanic.models.futures import ( FutureException, FutureListener, FutureMiddleware, FutureRegistry, FutureRoute, FutureSignal, ) from sanic.models.handler_types import ListenerType, MiddlewareType from sanic.models.handler_types import Sanic as SanicVar from sanic.request import Request from sanic.response import BaseHTTPResponse, HTTPResponse, ResponseStream from sanic.router import Router from sanic.server.websockets.impl import ConnectionClosed from sanic.signals import Event, Signal, SignalRouter from sanic.touchup import TouchUp, TouchUpMeta from sanic.types.shared_ctx import SharedContext from sanic.worker.inspector import Inspector from sanic.worker.loader import CertLoader from sanic.worker.manager import WorkerManager if TYPE_CHECKING: try: from sanic_ext import Extend # type: ignore from sanic_ext.extensions.base import Extension # type: ignore except ImportError: Extend = TypeVar("Extend", type) # type: ignore if OS_IS_WINDOWS: # no cov enable_windows_color_support() ctx_type = TypeVar("ctx_type") config_type = TypeVar("config_type", bound=Config) class Sanic( Generic[config_type, ctx_type], StaticHandleMixin, BaseSanic, StartupMixin, CommandMixin, metaclass=TouchUpMeta, ): """The main application instance You will create an instance of this class and use it to register routes, listeners, middleware, blueprints, error handlers, etc. By convention, it is often called `app`. It must be named using the `name` parameter and is roughly constrained to the same restrictions as a Python module name, however, it can contain hyphens (`-`). ```python # will cause an error because it contains spaces Sanic("This is not legal") ``` ```python # this is legal Sanic("Hyphens-are-legal_or_also_underscores") ``` Args: name (str): The name of the application. Must be a valid Python module name (including hyphens). config (Optional[config_type]): The configuration to use for the application. Defaults to `None`. ctx (Optional[ctx_type]): The context to use for the application. Defaults to `None`. router (Optional[Router]): The router to use for the application. Defaults to `None`. signal_router (Optional[SignalRouter]): The signal router to use for the application. Defaults to `None`. error_handler (Optional[ErrorHandler]): The error handler to use for the application. Defaults to `None`. env_prefix (Optional[str]): The prefix to use for environment variables. Defaults to `SANIC_`. request_class (Optional[Type[Request]]): The request class to use for the application. Defaults to `Request`. strict_slashes (bool): Whether to enforce strict slashes. Defaults to `False`. log_config (Optional[Dict[str, Any]]): The logging configuration to use for the application. Defaults to `None`. configure_logging (bool): Whether to configure logging. Defaults to `True`. dumps (Optional[Callable[..., AnyStr]]): The function to use for serializing JSON. Defaults to `None`. loads (Optional[Callable[..., Any]]): The function to use for deserializing JSON. Defaults to `None`. inspector (bool): Whether to enable the inspector. Defaults to `False`. inspector_class (Optional[Type[Inspector]]): The inspector class to use for the application. Defaults to `None`. certloader_class (Optional[Type[CertLoader]]): The certloader class to use for the application. Defaults to `None`. """ __touchup__ = ( "handle_request", "handle_exception", "_run_response_middleware", "_run_request_middleware", ) __slots__ = ( "_asgi_app", "_asgi_lifespan", "_asgi_client", "_blueprint_order", "_delayed_tasks", "_ext", "_future_commands", "_future_exceptions", "_future_listeners", "_future_middleware", "_future_registry", "_future_routes", "_future_signals", "_future_statics", "_inspector", "_manager", "_state", "_task_registry", "_test_client", "_test_manager", "blueprints", "certloader_class", "config", "configure_logging", "ctx", "error_handler", "inspector_class", "go_fast", "listeners", "multiplexer", "named_request_middleware", "named_response_middleware", "repl_ctx", "request_class", "request_middleware", "response_middleware", "router", "shared_ctx", "signal_router", "sock", "strict_slashes", "websocket_enabled", "websocket_tasks", ) _app_registry: ClassVar[dict[str, Sanic]] = {} test_mode: ClassVar[bool] = False @overload def __init__( self: Sanic[Config, SimpleNamespace], name: str, config: None = None, ctx: None = None, router: Router | None = None, signal_router: SignalRouter | None = None, error_handler: ErrorHandler | None = None, env_prefix: str | None = SANIC_PREFIX, request_class: type[Request] | None = None, strict_slashes: bool = False, log_config: dict[str, Any] | None = None, configure_logging: bool = True, dumps: Callable[..., AnyStr] | None = None, loads: Callable[..., Any] | None = None, inspector: bool = False, inspector_class: type[Inspector] | None = None, certloader_class: type[CertLoader] | None = None, ) -> None: ... @overload def __init__( self: Sanic[config_type, SimpleNamespace], name: str, config: config_type | None = None, ctx: None = None, router: Router | None = None, signal_router: SignalRouter | None = None, error_handler: ErrorHandler | None = None, env_prefix: str | None = SANIC_PREFIX, request_class: type[Request] | None = None, strict_slashes: bool = False, log_config: dict[str, Any] | None = None, configure_logging: bool = True, dumps: Callable[..., AnyStr] | None = None, loads: Callable[..., Any] | None = None, inspector: bool = False, inspector_class: type[Inspector] | None = None, certloader_class: type[CertLoader] | None = None, ) -> None: ... @overload def __init__( self: Sanic[Config, ctx_type], name: str, config: None = None, ctx: ctx_type | None = None, router: Router | None = None, signal_router: SignalRouter | None = None, error_handler: ErrorHandler | None = None, env_prefix: str | None = SANIC_PREFIX, request_class: type[Request] | None = None, strict_slashes: bool = False, log_config: dict[str, Any] | None = None, configure_logging: bool = True, dumps: Callable[..., AnyStr] | None = None, loads: Callable[..., Any] | None = None, inspector: bool = False, inspector_class: type[Inspector] | None = None, certloader_class: type[CertLoader] | None = None, ) -> None: ... @overload def __init__( self: Sanic[config_type, ctx_type], name: str, config: config_type | None = None, ctx: ctx_type | None = None, router: Router | None = None, signal_router: SignalRouter | None = None, error_handler: ErrorHandler | None = None, env_prefix: str | None = SANIC_PREFIX, request_class: type[Request] | None = None, strict_slashes: bool = False, log_config: dict[str, Any] | None = None, configure_logging: bool = True, dumps: Callable[..., AnyStr] | None = None, loads: Callable[..., Any] | None = None, inspector: bool = False, inspector_class: type[Inspector] | None = None, certloader_class: type[CertLoader] | None = None, ) -> None: ... def __init__( self, name: str, config: config_type | None = None, ctx: ctx_type | None = None, router: Router | None = None, signal_router: SignalRouter | None = None, error_handler: ErrorHandler | None = None, env_prefix: str | None = SANIC_PREFIX, request_class: type[Request] | None = None, strict_slashes: bool = False, log_config: dict[str, Any] | None = None, configure_logging: bool = True, dumps: Callable[..., AnyStr] | None = None, loads: Callable[..., Any] | None = None, inspector: bool = False, inspector_class: type[Inspector] | None = None, certloader_class: type[CertLoader] | None = None, ) -> None: super().__init__(name=name) # logging if configure_logging: dict_config = log_config or LOGGING_CONFIG_DEFAULTS logging.config.dictConfig(dict_config) # type: ignore if config and env_prefix != SANIC_PREFIX: raise SanicException( "When instantiating Sanic with config, you cannot also pass " "env_prefix" ) # First setup config self.config: config_type = cast( config_type, config or Config(env_prefix=env_prefix) ) if inspector: self.config.INSPECTOR = inspector # Then we can do the rest self._asgi_app: ASGIApp | None = None self._asgi_lifespan: Lifespan | None = None self._asgi_client: Any = None self._blueprint_order: list[Blueprint] = [] self._delayed_tasks: list[str] = [] self._future_registry: FutureRegistry = FutureRegistry() self._inspector: Inspector | None = None self._manager: WorkerManager | None = None self._state: ApplicationState = ApplicationState(app=self) self._task_registry: dict[str, Task | None] = {} self._test_client: Any = None self._test_manager: Any = None self.asgi = False self.auto_reload = False self.blueprints: dict[str, Blueprint] = {} self.certloader_class: type[CertLoader] = ( certloader_class or CertLoader ) self.configure_logging: bool = configure_logging self.ctx: ctx_type = cast(ctx_type, ctx or SimpleNamespace()) self.error_handler: ErrorHandler = error_handler or ErrorHandler() self.inspector_class: type[Inspector] = inspector_class or Inspector self.listeners: dict[str, list[ListenerType[Any]]] = defaultdict(list) self.named_request_middleware: dict[str, deque[Middleware]] = {} self.named_response_middleware: dict[str, deque[Middleware]] = {} self.repl_ctx: REPLContext = REPLContext() self.request_class = request_class or Request self.request_middleware: deque[Middleware] = deque() self.response_middleware: deque[Middleware] = deque() self.router: Router = router or Router() self.shared_ctx: SharedContext = SharedContext() self.signal_router: SignalRouter = signal_router or SignalRouter() self.sock: socket | None = None self.strict_slashes: bool = strict_slashes self.websocket_enabled: bool = False self.websocket_tasks: set[Future[Any]] = set() # Register alternative method names self.go_fast = self.run self.router.ctx.app = self self.signal_router.ctx.app = self self.__class__.register_app(self) if dumps: BaseHTTPResponse._dumps = dumps # type: ignore if loads: Request._loads = loads # type: ignore @property def loop(self) -> AbstractEventLoop: """Synonymous with asyncio.get_event_loop(). .. note:: Only supported when using the `app.run` method. Returns: AbstractEventLoop: The event loop for the application. Raises: SanicException: If the application is not running. """ if self.state.stage is ServerStage.STOPPED and self.asgi is False: raise SanicException( "Loop can only be retrieved after the app has started " "running. Not supported with `create_server` function" ) try: return get_running_loop() except RuntimeError: # no cov return asyncio.get_event_loop_policy().get_event_loop() # -------------------------------------------------------------------- # # Registration # -------------------------------------------------------------------- # def register_listener( self, listener: ListenerType[SanicVar], event: str, *, priority: int = 0, ) -> ListenerType[SanicVar]: """Register the listener for a given event. Args: listener (Callable): The listener to register. event (str): The event to listen for. Returns: Callable: The listener that was registered. """ try: _event = ListenerEvent[event.upper()] except (ValueError, AttributeError): valid = ", ".join( map(lambda x: x.lower(), ListenerEvent.__members__.keys()) ) raise BadRequest(f"Invalid event: {event}. Use one of: {valid}") if "." in _event: self.signal(_event.value, priority=priority)( partial(self._listener, listener=listener) ) else: if priority: error_logger.warning( f"Priority is not supported for {_event.value}" ) self.listeners[_event.value].append(listener) return listener def register_middleware( self, middleware: MiddlewareType | Middleware, attach_to: str = "request", *, priority: Default | int = _default, ) -> MiddlewareType | Middleware: """Register a middleware to be called before a request is handled. Args: middleware (Callable): A callable that takes in a request. attach_to (str): Whether to attach to request or response. Defaults to `'request'`. priority (int): The priority level of the middleware. Lower numbers are executed first. Defaults to `0`. Returns: Union[Callable, Callable[[Callable], Callable]]: The decorated middleware function or a partial function depending on how the method was called. """ retval = middleware location = MiddlewareLocation[attach_to.upper()] if not isinstance(middleware, Middleware): middleware = Middleware( middleware, location=location, priority=priority if isinstance(priority, int) else 0, ) elif middleware.priority != priority and isinstance(priority, int): middleware = Middleware( middleware.func, location=middleware.location, priority=priority, ) if location is MiddlewareLocation.REQUEST: if middleware not in self.request_middleware: self.request_middleware.append(middleware) if location is MiddlewareLocation.RESPONSE: if middleware not in self.response_middleware: self.response_middleware.appendleft(middleware) return retval def register_named_middleware( self, middleware: MiddlewareType, route_names: Iterable[str], attach_to: str = "request", *, priority: Default | int = _default, ): """Used to register named middleqare (middleware typically on blueprints) Args: middleware (Callable): A callable that takes in a request. route_names (Iterable[str]): The route names to attach the middleware to. attach_to (str): Whether to attach to request or response. Defaults to `'request'`. priority (int): The priority level of the middleware. Lower numbers are executed first. Defaults to `0`. Returns: Union[Callable, Callable[[Callable], Callable]]: The decorated middleware function or a partial function depending on how the method was called. """ # noqa: E501 retval = middleware location = MiddlewareLocation[attach_to.upper()] if not isinstance(middleware, Middleware): middleware = Middleware( middleware, location=location, priority=priority if isinstance(priority, int) else 0, ) elif middleware.priority != priority and isinstance(priority, int): middleware = Middleware( middleware.func, location=middleware.location, priority=priority, ) if location is MiddlewareLocation.REQUEST: for _rn in route_names: if _rn not in self.named_request_middleware: self.named_request_middleware[_rn] = deque() if middleware not in self.named_request_middleware[_rn]: self.named_request_middleware[_rn].append(middleware) if location is MiddlewareLocation.RESPONSE: for _rn in route_names: if _rn not in self.named_response_middleware: self.named_response_middleware[_rn] = deque() if middleware not in self.named_response_middleware[_rn]: self.named_response_middleware[_rn].appendleft(middleware) return retval def _apply_exception_handler( self, handler: FutureException, route_names: list[str] | None = None, ): """Decorate a function to be registered as a handler for exceptions :param exceptions: exceptions :return: decorated function """ for exception in handler.exceptions: if isinstance(exception, (tuple, list)): for e in exception: self.error_handler.add(e, handler.handler, route_names) else: self.error_handler.add(exception, handler.handler, route_names) return handler.handler def _apply_listener(self, listener: FutureListener): return self.register_listener( listener.listener, listener.event, priority=listener.priority ) def _apply_route( self, route: FutureRoute, overwrite: bool = False ) -> list[Route]: params = route._asdict() params["overwrite"] = overwrite websocket = params.pop("websocket", False) subprotocols = params.pop("subprotocols", None) if websocket: self.enable_websocket() websocket_handler = partial( self._websocket_handler, route.handler, subprotocols=subprotocols, ) websocket_handler.__name__ = route.handler.__name__ # type: ignore websocket_handler.is_websocket = True # type: ignore params["handler"] = websocket_handler ctx = params.pop("route_context") with self.amend(): routes = self.router.add(**params) if isinstance(routes, Route): routes = [routes] for r in routes: r.extra.websocket = websocket r.extra.static = params.get("static", False) r.ctx.__dict__.update(ctx) return routes def _apply_middleware( self, middleware: FutureMiddleware, route_names: list[str] | None = None, ): with self.amend(): if route_names: return self.register_named_middleware( middleware.middleware, route_names, middleware.attach_to ) else: return self.register_middleware( middleware.middleware, middleware.attach_to ) def _apply_signal(self, signal: FutureSignal) -> Signal: with self.amend(): return self.signal_router.add( handler=signal.handler, event=signal.event, condition=signal.condition, exclusive=signal.exclusive, priority=signal.priority, ) @overload def dispatch( self, event: str, *, condition: dict[str, str] | None = None, context: dict[str, Any] | None = None, fail_not_found: bool = True, inline: Literal[True], reverse: bool = False, ) -> Coroutine[Any, Any, Awaitable[Any]]: ... @overload def dispatch( self, event: str, *, condition: dict[str, str] | None = None, context: dict[str, Any] | None = None, fail_not_found: bool = True, inline: Literal[False] = False, reverse: bool = False, ) -> Coroutine[Any, Any, Awaitable[Task]]: ... def dispatch( self, event: str, *, condition: dict[str, str] | None = None, context: dict[str, Any] | None = None, fail_not_found: bool = True, inline: bool = False, reverse: bool = False, ) -> Coroutine[Any, Any, Awaitable[Task | Any]]: """Dispatches an event to the signal router. Args: event (str): Name of the event to dispatch. condition (Optional[Dict[str, str]]): Condition for the event dispatch. context (Optional[Dict[str, Any]]): Context for the event dispatch. fail_not_found (bool): Whether to fail if the event is not found. Default is `True`. inline (bool): If `True`, returns the result directly. If `False`, returns a `Task`. Default is `False`. reverse (bool): Whether to reverse the dispatch order. Default is `False`. Returns: Coroutine[Any, Any, Awaitable[Union[Task, Any]]]: An awaitable that returns the result directly if `inline=True`, or a `Task` if `inline=False`. Examples: ```python @app.signal("user.registration.created") async def send_registration_email(**context): await send_email(context["email"], template="registration") @app.post("/register") async def handle_registration(request): await do_registration(request) await request.app.dispatch( "user.registration.created", context={"email": request.json.email} }) ``` """ return self.signal_router.dispatch( event, context=context, condition=condition, inline=inline, reverse=reverse, fail_not_found=fail_not_found, ) async def event( self, event: str | Enum, timeout: int | float | None = None, *, condition: dict[str, Any] | None = None, exclusive: bool = True, ) -> None: """Wait for a specific event to be triggered. This method waits for a named event to be triggered and can be used in conjunction with the signal system to wait for specific signals. If the event is not found and auto-registration of events is enabled, the event will be registered and then waited on. If the event is not found and auto-registration is not enabled, a `NotFound` exception is raised. Auto-registration can be handled by setting the `EVENT_AUTOREGISTER` config value to `True`. ```python app.config.EVENT_AUTOREGISTER = True ``` Args: event (str): The name of the event to wait for. timeout (Optional[Union[int, float]]): An optional timeout value in seconds. If provided, the wait will be terminated if the timeout is reached. Defaults to `None`, meaning no timeout. condition: If provided, method will only return when the signal is dispatched with the given condition. exclusive: When true (default), the signal can only be dispatched when the condition has been met. When ``False``, the signal can be dispatched either with or without it. Raises: NotFound: If the event is not found and auto-registration of events is not enabled. Returns: The context dict of the dispatched signal. Examples: ```python async def wait_for_event(app): while True: print("> waiting") await app.event("foo.bar.baz") print("> event found") @app.after_server_start async def after_server_start(app, loop): app.add_task(wait_for_event(app)) ``` """ waiter = self.signal_router.get_waiter(event, condition, exclusive) if not waiter and self.config.EVENT_AUTOREGISTER: self.signal_router.reset() self.add_signal(None, event) waiter = self.signal_router.get_waiter(event, condition, exclusive) self.signal_router.finalize() if not waiter: raise NotFound(f"Could not find signal {event}") return await wait_for(waiter.wait(), timeout=timeout) def report_exception( self, handler: Callable[[Sanic, Exception], Coroutine[Any, Any, None]] ) -> Callable[[Exception], Coroutine[Any, Any, None]]: """Register a handler to report exceptions. A convenience method to register a handler for the signal that is emitted when an exception occurs. It is typically used to report exceptions to an external service. It is equivalent to: ```python @app.signal(Event.SERVER_EXCEPTION_REPORT) async def report(exception): await do_something_with_error(exception) ``` Args: handler (Callable[[Sanic, Exception], Coroutine[Any, Any, None]]): The handler to register. Returns: Callable[[Sanic, Exception], Coroutine[Any, Any, None]]: The handler that was registered. """ @wraps(handler) async def report(exception: Exception) -> None: await handler(self, exception) self.add_signal( handler=report, event=Event.SERVER_EXCEPTION_REPORT.value ) return report def enable_websocket(self, enable: bool = True) -> None: """Enable or disable the support for websocket. Websocket is enabled automatically if websocket routes are added to the application. This typically will not need to be called manually. Args: enable (bool, optional): If set to `True`, enables websocket support. If set to `False`, disables websocket support. Defaults to `True`. Returns: None """ if not self.websocket_enabled: # if the server is stopped, we want to cancel any ongoing # websocket tasks, to allow the server to exit promptly self.listener("before_server_stop")(self._cancel_websocket_tasks) self.websocket_enabled = enable def blueprint( self, blueprint: Blueprint | Iterable[Blueprint] | BlueprintGroup, *, url_prefix: str | None = None, version: int | float | str | None = None, strict_slashes: bool | None = None, version_prefix: str | None = None, name_prefix: str | None = None, ) -> None: """Register a blueprint on the application. See [Blueprints](/en/guide/best-practices/blueprints) for more information. Args: blueprint (Union[Blueprint, Iterable[Blueprint], BlueprintGroup]): Blueprint object or (list, tuple) thereof. url_prefix (Optional[str]): Prefix for all URLs bound to the blueprint. Defaults to `None`. version (Optional[Union[int, float, str]]): Version prefix for URLs. Defaults to `None`. strict_slashes (Optional[bool]): Enforce the trailing slashes. Defaults to `None`. version_prefix (Optional[str]): Prefix for version. Defaults to `None`. name_prefix (Optional[str]): Prefix for the blueprint name. Defaults to `None`. Example: ```python app = Sanic("TestApp") bp = Blueprint('TestBP') @bp.route('/route') def handler(request): return text('Hello, Blueprint!') app.blueprint(bp, url_prefix='/blueprint') ``` """ # noqa: E501 options: dict[str, Any] = {} if url_prefix is not None: options["url_prefix"] = url_prefix if version is not None: options["version"] = version if strict_slashes is not None: options["strict_slashes"] = strict_slashes if version_prefix is not None: options["version_prefix"] = version_prefix if name_prefix is not None: options["name_prefix"] = name_prefix if isinstance(blueprint, (Iterable, BlueprintGroup)): for item in blueprint: params: dict[str, Any] = {**options} if isinstance(blueprint, BlueprintGroup): merge_from = [ options.get("url_prefix", ""), blueprint.url_prefix or "", ] if not isinstance(item, BlueprintGroup): merge_from.append(item.url_prefix or "") merged_prefix = "/".join( str(u).strip("/") for u in merge_from if u ).rstrip("/") params["url_prefix"] = f"/{merged_prefix}" for _attr in ["version", "strict_slashes"]: if getattr(item, _attr) is None: params[_attr] = getattr( blueprint, _attr ) or options.get(_attr) if item.version_prefix == "/v": if blueprint.version_prefix == "/v": params["version_prefix"] = options.get( "version_prefix" ) else: params["version_prefix"] = blueprint.version_prefix name_prefix = getattr(blueprint, "name_prefix", None) if name_prefix and "name_prefix" not in params: params["name_prefix"] = name_prefix self.blueprint(item, **params) return if blueprint.name in self.blueprints: assert self.blueprints[blueprint.name] is blueprint, ( 'A blueprint with the name "%s" is already registered. ' "Blueprint names must be unique." % (blueprint.name,) ) else: self.blueprints[blueprint.name] = blueprint self._blueprint_order.append(blueprint) if ( self.strict_slashes is not None and blueprint.strict_slashes is None ): blueprint.strict_slashes = self.strict_slashes blueprint.register(self, options) def url_for(self, view_name: str, **kwargs): """Build a URL based on a view name and the values provided. This method constructs URLs for a given view name, taking into account various special keyword arguments that can be used to modify the resulting URL. It can handle internal routing as well as external URLs with different schemes. There are several special keyword arguments that can be used to modify the URL that is built. They each begin with an underscore. They are: - `_anchor` - `_external` - `_host` - `_server` - `_scheme` Args: view_name (str): String referencing the view name. _anchor (str): Adds an "#anchor" to the end. _scheme (str): Should be either "http" or "https", default is "http". _external (bool): Whether to return the path or a full URL with scheme and host. _host (str): Used when one or more hosts are defined for a route to tell Sanic which to use. _server (str): If not using "_host", this will be used for defining the hostname of the URL. **kwargs: Keys and values that are used to build request parameters and query string arguments. Raises: URLBuildError: If there are issues with constructing the URL. Returns: str: The built URL. Examples: Building a URL for a specific view with parameters: ```python url_for('view_name', param1='value1', param2='value2') # /view-name?param1=value1¶m2=value2 ``` Creating an external URL with a specific scheme and anchor: ```python url_for('view_name', _scheme='https', _external=True, _anchor='section1') # https://example.com/view-name#section1 ``` Creating a URL with a specific host: ```python url_for('view_name', _host='subdomain.example.com') # http://subdomain.example.com/view-name """ # noqa: E501 # find the route by the supplied view name kw: dict[str, str] = {} # special static files url_for if "." not in view_name: view_name = f"{self.name}.{view_name}" if view_name.endswith(".static"): name = kwargs.pop("name", None) if name: view_name = view_name.replace("static", name) kw.update(name=view_name) route = self.router.find_route_by_view_name(view_name, **kw) if not route: raise URLBuildError( f"Endpoint with name `{view_name}` was not found" ) uri = route.path if getattr(route.extra, "static", None): filename = kwargs.pop("filename", "") # it's static folder if "__file_uri__" in uri: folder_ = uri.split("<__file_uri__:", 1)[0] if folder_.endswith("/"): folder_ = folder_[:-1] if filename.startswith("/"): filename = filename[1:] kwargs["__file_uri__"] = filename if ( uri != "/" and uri.endswith("/") and not route.strict and not route.raw_path[:-1] ): uri = uri[:-1] if not uri.startswith("/"): uri = f"/{uri}" out = uri # _method is only a placeholder now, don't know how to support it kwargs.pop("_method", None) anchor = kwargs.pop("_anchor", "") # _external need SERVER_NAME in config or pass _server arg host = kwargs.pop("_host", None) external = kwargs.pop("_external", False) or bool(host) scheme = kwargs.pop("_scheme", "") if route.extra.hosts and external: if not host and len(route.extra.hosts) > 1: raise ValueError( f"Host is ambiguous: {', '.join(route.extra.hosts)}" ) elif host and host not in route.extra.hosts: raise ValueError( f"Requested host ({host}) is not available for this " f"route: {route.extra.hosts}" ) elif not host: host = list(route.extra.hosts)[0] if scheme and not external: raise ValueError("When specifying _scheme, _external must be True") netloc = kwargs.pop("_server", None) if netloc is None and external: netloc = host or self.config.get("SERVER_NAME", "") if external: if not scheme: if ":" in netloc[:8]: scheme = netloc[:8].split(":", 1)[0] else: scheme = "http" # Replace http/https with ws/wss for WebSocket handlers if route.extra.websocket: scheme = scheme.replace("http", "ws") if "://" in netloc[:8]: netloc = netloc.split("://", 1)[-1] # find all the parameters we will need to build in the URL # matched_params = re.findall(self.router.parameter_pattern, uri) route.finalize() for param_info in route.params.values(): # name, _type, pattern = self.router.parse_parameter_string(match) # we only want to match against each individual parameter try: supplied_param = str(kwargs.pop(param_info.name)) except KeyError: raise URLBuildError( f"Required parameter `{param_info.name}` was not " "passed to url_for" ) # determine if the parameter supplied by the caller # passes the test in the URL if param_info.pattern: pattern = ( param_info.pattern[1] if isinstance(param_info.pattern, tuple) else param_info.pattern ) passes_pattern = pattern.match(supplied_param) if not passes_pattern: if param_info.cast is not str: msg = ( f'Value "{supplied_param}" ' f"for parameter `{param_info.name}` does " "not match pattern for type " f"`{param_info.cast.__name__}`: " f"{pattern.pattern}" ) else: msg = ( f'Value "{supplied_param}" for parameter ' f"`{param_info.name}` does not satisfy " f"pattern {pattern.pattern}" ) raise URLBuildError(msg) # replace the parameter in the URL with the supplied value replacement_regex = f"(<{param_info.name}.*?>)" out = re.sub(replacement_regex, supplied_param, out) # parse the remainder of the keyword arguments into a querystring query_string = urlencode(kwargs, doseq=True) if kwargs else "" # scheme://netloc/path;parameters?query#fragment out = urlunparse((scheme, netloc, out, "", query_string, anchor)) return out # -------------------------------------------------------------------- # # Request Handling # -------------------------------------------------------------------- # async def handle_exception( self, request: Request, exception: BaseException, run_middleware: bool = True, ) -> None: # no cov """A handler that catches specific exceptions and outputs a response. .. note:: This method is typically used internally, and you should not need to call it directly. Args: request (Request): The current request object. exception (BaseException): The exception that was raised. run_middleware (bool): Whether to run middleware. Defaults to `True`. Raises: ServerError: response 500. """ response = None if not getattr(exception, "__dispatched__", False): ... # DO NOT REMOVE THIS LINE. IT IS NEEDED FOR TOUCHUP. await self.dispatch( "server.exception.report", context={"exception": exception}, ) await self.dispatch( "http.lifecycle.exception", inline=True, context={"request": request, "exception": exception}, ) if ( request.stream is not None and request.stream.stage is not Stage.HANDLER ): error_logger.exception(exception, exc_info=True) logger.error( "The error response will not be sent to the client for " f'the following exception:"{exception}". A previous response ' "has at least partially been sent." ) handler = self.error_handler._lookup( exception, request.name if request else None ) if handler: logger.warning( "An error occurred while handling the request after at " "least some part of the response was sent to the client. " "The response from your custom exception handler " f"{handler.__name__} will not be sent to the client." "Exception handlers should only be used to generate the " "exception responses. If you would like to perform any " "other action on a raised exception, consider using a " "signal handler like " '`@app.signal("http.lifecycle.exception")`\n' "For further information, please see the docs: " "https://sanicframework.org/en/guide/advanced/" "signals.html", ) return # -------------------------------------------- # # Request Middleware # -------------------------------------------- # if run_middleware: try: middleware = ( request.route and request.route.extra.request_middleware ) or self.request_middleware response = await self._run_request_middleware( request, middleware ) except Exception as e: return await self.handle_exception(request, e, False) # No middleware results if not response: try: response = self.error_handler.response(request, exception) if isawaitable(response): response = await response except Exception as e: if isinstance(e, SanicException): response = self.error_handler.default(request, e) elif self.debug: response = HTTPResponse( ( f"Error while handling error: {e}\n" f"Stack: {format_exc()}" ), status=500, ) else: response = HTTPResponse( "An error occurred while handling an error", status=500 ) if response is not None: try: request.reset_response() response = await request.respond(response) except BaseException: # Skip response middleware if request.stream: request.stream.respond(response) await response.send(end_stream=True) raise else: if request.stream: response = request.stream.response # Marked for cleanup and DRY with handle_request/handle_exception # when ResponseStream is no longer supporder if isinstance(response, BaseHTTPResponse): await self.dispatch( "http.lifecycle.response", inline=True, context={ "request": request, "response": response, }, ) await response.send(end_stream=True) elif isinstance(response, ResponseStream): resp = await response(request) await self.dispatch( "http.lifecycle.response", inline=True, context={ "request": request, "response": resp, }, ) await response.eof() else: raise ServerError( f"Invalid response type {response!r} (need HTTPResponse)" ) async def handle_request(self, request: Request) -> None: # no cov """Handles a request by dispatching it to the appropriate handler. .. note:: This method is typically used internally, and you should not need to call it directly. Args: request (Request): The current request object. Raises: ServerError: response 500. """ __tracebackhide__ = True await self.dispatch( "http.lifecycle.handle", inline=True, context={"request": request}, ) # Define `response` var here to remove warnings about # allocation before assignment below. response: ( BaseHTTPResponse | Coroutine[Any, Any, BaseHTTPResponse | None] | ResponseStream | None ) = None run_middleware = True try: await self.dispatch( "http.routing.before", inline=True, context={"request": request}, ) # Fetch handler from router route, handler, kwargs = self.router.get( request.path, request.method, request.headers.getone("host", None), ) request._match_info = {**kwargs} request.route = route await self.dispatch( "http.routing.after", inline=True, context={ "request": request, "route": route, "kwargs": kwargs, "handler": handler, }, ) if ( request.stream and request.stream.request_body and not route.extra.ignore_body ): if hasattr(handler, "is_stream"): # Streaming handler: lift the size limit request.stream.request_max_size = float("inf") else: # Non-streaming handler: preload body await request.receive_body() # -------------------------------------------- # # Request Middleware # -------------------------------------------- # run_middleware = False if request.route.extra.request_middleware: response = await self._run_request_middleware( request, request.route.extra.request_middleware ) # No middleware results if not response: # -------------------------------------------- # # Execute Handler # -------------------------------------------- # if handler is None: raise ServerError( "'None' was returned while requesting a " "handler from the router" ) # Run response handler await self.dispatch( "http.handler.before", inline=True, context={"request": request}, ) response = handler(request, **request.match_info) if isawaitable(response): response = await response await self.dispatch( "http.handler.after", inline=True, context={"request": request}, ) if request.responded: if response is not None: error_logger.error( "The response object returned by the route handler " "will not be sent to client. The request has already " "been responded to." ) if request.stream is not None: response = request.stream.response elif response is not None: response = await request.respond(response) # type: ignore elif not hasattr(handler, "is_websocket"): response = request.stream.response # type: ignore # Marked for cleanup and DRY with handle_request/handle_exception # when ResponseStream is no longer supporder if isinstance(response, BaseHTTPResponse): await self.dispatch( "http.lifecycle.response", inline=True, context={ "request": request, "response": response, }, ) ... await response.send(end_stream=True) elif isinstance(response, ResponseStream): resp = await response(request) await self.dispatch( "http.lifecycle.response", inline=True, context={ "request": request, "response": resp, }, ) await response.eof() else: if not hasattr(handler, "is_websocket"): raise ServerError( f"Invalid response type {response!r} " "(need HTTPResponse)" ) except CancelledError: # type: ignore raise except Exception as e: # Response Generation Failed await self.handle_exception( request, e, run_middleware=run_middleware ) async def _websocket_handler( self, handler, request, *args, subprotocols=None, **kwargs ): if self.asgi: ws = request.transport.get_websocket_connection() await ws.accept(subprotocols) else: protocol = request.transport.get_protocol() ws = await protocol.websocket_handshake(request, subprotocols) await self.dispatch( "websocket.handler.before", inline=True, context={"request": request, "websocket": ws}, fail_not_found=False, ) # schedule the application handler # its future is kept in self.websocket_tasks in case it # needs to be cancelled due to the server being stopped fut = ensure_future(handler(request, ws, *args, **kwargs)) self.websocket_tasks.add(fut) cancelled = False try: await fut await self.dispatch( "websocket.handler.after", inline=True, context={"request": request, "websocket": ws}, reverse=True, fail_not_found=False, ) except (CancelledError, ConnectionClosed): # type: ignore cancelled = True except Exception as e: self.error_handler.log(request, e) await self.dispatch( "websocket.handler.exception", inline=True, context={"request": request, "websocket": ws, "exception": e}, reverse=True, fail_not_found=False, ) finally: self.websocket_tasks.remove(fut) if cancelled: ws.end_connection(1000) else: await ws.close() # -------------------------------------------------------------------- # # Testing # -------------------------------------------------------------------- # @property def test_client(self) -> SanicTestClient: # type: ignore # noqa """A testing client that uses httpx and a live running server to reach into the application to execute handlers. This property is available if the `sanic-testing` package is installed. See [Test Clients](/en/plugins/sanic-testing/clients#wsgi-client-sanictestclient) for details. Returns: SanicTestClient: A testing client from the `sanic-testing` package. """ # noqa: E501 if self._test_client: return self._test_client elif self._test_manager: return self._test_manager.test_client from sanic_testing.testing import SanicTestClient # type: ignore self._test_client = SanicTestClient(self) return self._test_client @property def asgi_client(self) -> SanicASGITestClient: # type: ignore # noqa """A testing client that uses ASGI to reach into the application to execute handlers. This property is available if the `sanic-testing` package is installed. See [Test Clients](/en/plugins/sanic-testing/clients#asgi-async-client-sanicasgitestclient) for details. Returns: SanicASGITestClient: A testing client from the `sanic-testing` package. """ # noqa: E501 if self._asgi_client: return self._asgi_client elif self._test_manager: return self._test_manager.asgi_client from sanic_testing.testing import SanicASGITestClient # type: ignore self._asgi_client = SanicASGITestClient(self) return self._asgi_client # -------------------------------------------------------------------- # # Execution # -------------------------------------------------------------------- # async def _run_request_middleware( self, request, middleware_collection ): # no cov request._request_middleware_started = True for middleware in middleware_collection: await self.dispatch( "http.middleware.before", inline=True, context={ "request": request, "response": None, }, condition={"attach_to": "request"}, ) response = middleware(request) if isawaitable(response): response = await response await self.dispatch( "http.middleware.after", inline=True, context={ "request": request, "response": None, }, condition={"attach_to": "request"}, ) if response: return response return None async def _run_response_middleware( self, request, response, middleware_collection ): # no cov for middleware in middleware_collection: await self.dispatch( "http.middleware.before", inline=True, context={ "request": request, "response": response, }, condition={"attach_to": "response"}, ) _response = middleware(request, response) if isawaitable(_response): _response = await _response await self.dispatch( "http.middleware.after", inline=True, context={ "request": request, "response": _response if _response else response, }, condition={"attach_to": "response"}, ) if _response: response = _response if isinstance(response, BaseHTTPResponse): response = request.stream.respond(response) break return response def _build_endpoint_name(self, *parts): parts = [self.name, *parts] return ".".join(parts) @classmethod def _cancel_websocket_tasks(cls, app): for task in app.websocket_tasks: task.cancel() @staticmethod async def _listener( app: Sanic, loop: AbstractEventLoop, listener: ListenerType ): try: maybe_coro = listener(app) # type: ignore except TypeError: name = getattr( listener, "__qualname__", getattr(getattr(listener, "func", None), "__qualname__", None), ) deprecation( f"Passing the loop argument to listeners is deprecated. " f"Your listener{f' {name!r}' if name else ''} should only " "accept the app argument.", 26.6, ) maybe_coro = listener(app, loop) # type: ignore if maybe_coro and isawaitable(maybe_coro): await maybe_coro # -------------------------------------------------------------------- # # Task management # -------------------------------------------------------------------- # @classmethod def _prep_task( cls, task, app, loop, ): async def do(task): try: if callable(task): try: task = task(app) except TypeError: task = task() if isawaitable(task): return await task except CancelledError: error_logger.warning( f"Task {task} was cancelled before it completed." ) raise except Exception as e: await app.dispatch( "server.exception.report", context={"exception": e}, ) raise return do(task) @classmethod def _loop_add_task( cls, task, app, loop, *, name: str | None = None, register: bool = True, ) -> Task: tsk: Task = task if not isinstance(task, Future): prepped = cls._prep_task(task, app, loop) tsk = loop.create_task(prepped, name=name) if name and register: app._task_registry[name] = tsk return tsk @staticmethod async def dispatch_delayed_tasks(app: Sanic) -> None: """Signal handler for dispatching delayed tasks. This is used to dispatch tasks that were added before the loop was started, and will be called after the loop has started. It is not typically used directly. Args: app (Sanic): The Sanic application instance. Returns: None """ loop = asyncio.get_running_loop() for name in app._delayed_tasks: await app.dispatch(name, context={"app": app, "loop": loop}) app._delayed_tasks.clear() @staticmethod async def run_delayed_task( app: Sanic, loop: AbstractEventLoop, task: Future[Any] | Task[Any] | Awaitable[Any], ) -> None: """Executes a delayed task within the context of a given app and loop. This method prepares a given task by invoking the app's private `_prep_task` method and then awaits the execution of the prepared task. Args: app (Any): The application instance on which the task will be executed. loop (AbstractEventLoop): The event loop where the task will be scheduled. task (Task[Any]): The task function that will be prepared and executed. Returns: None """ prepped = app._prep_task(task, app, loop) await prepped def add_task( self, task: Future[Any] | Coroutine[Any, Any, Any] | Awaitable[Any], *, name: str | None = None, register: bool = True, ) -> Task[Any] | None: """Schedule a task to run later, after the loop has started. While this is somewhat similar to `asyncio.create_task`, it can be used before the loop has started (in which case it will run after the loop has started in the `before_server_start` listener). Naming tasks is a good practice as it allows you to cancel them later, and allows Sanic to manage them when the server is stopped, if needed. [See user guide re: background tasks](/en/guide/basics/tasks#background-tasks) Args: task (Union[Future[Any], Coroutine[Any, Any, Any], Awaitable[Any]]): The future, coroutine, or awaitable to schedule. name (Optional[str], optional): The name of the task, if needed for later reference. Defaults to `None`. register (bool, optional): Whether to register the task. Defaults to `True`. Returns: Optional[Task[Any]]: The task that was scheduled, if applicable. """ # noqa: E501 try: loop = self.loop # Will raise SanicError if loop is not started return self._loop_add_task( task, self, loop, name=name, register=register ) except SanicException: task_name = f"sanic.delayed_task.{hash(task)}" if not self._delayed_tasks: self.after_server_start(partial(self.dispatch_delayed_tasks)) if name: raise RuntimeError( "Cannot name task outside of a running application" ) self.signal(task_name)(partial(self.run_delayed_task, task=task)) self._delayed_tasks.append(task_name) return None @overload def get_task( self, name: str, *, raise_exception: Literal[True] ) -> Task: ... @overload def get_task( self, name: str, *, raise_exception: Literal[False] ) -> Task | None: ... @overload def get_task(self, name: str, *, raise_exception: bool) -> Task | None: ... def get_task( self, name: str, *, raise_exception: bool = True ) -> Task | None: """Get a named task. This method is used to get a task by its name. Optionally, you can control whether an exception should be raised if the task is not found. Args: name (str): The name of the task to be retrieved. raise_exception (bool): If `True`, an exception will be raised if the task is not found. Defaults to `True`. Returns: Optional[Task]: The task, if found. """ try: return self._task_registry[name] except KeyError: if raise_exception: raise SanicException( f'Registered task named "{name}" not found.' ) return None async def cancel_task( self, name: str, msg: str | None = None, *, raise_exception: bool = True, ) -> None: """Cancel a named task. This method is used to cancel a task by its name. Optionally, you can provide a message that describes why the task was canceled, and control whether an exception should be raised if the task is not found. Args: name (str): The name of the task to be canceled. msg (Optional[str]): Optional message describing why the task was canceled. Defaults to None. raise_exception (bool): If True, an exception will be raised if the task is not found. Defaults to True. Example: ```python async def my_task(): try: await asyncio.sleep(10) except asyncio.CancelledError as e: current_task = asyncio.current_task() print(f"Task {current_task.get_name()} was cancelled. {e}") # Task sleepy_task was cancelled. No more sleeping! @app.before_server_start async def before_start(app): app.add_task(my_task, name="sleepy_task") await asyncio.sleep(1) await app.cancel_task("sleepy_task", msg="No more sleeping!") ``` """ # noqa: E501 task = self.get_task(name, raise_exception=raise_exception) if task and not task.cancelled(): if msg and sys.version_info < (3, 14): task.cancel(msg) else: task.cancel() try: await task except CancelledError: ... def purge_tasks(self) -> None: """Purges completed and cancelled tasks from the task registry. This method iterates through the task registry, identifying any tasks that are either done or cancelled, and then removes those tasks, leaving only the pending tasks in the registry. """ for key, task in self._task_registry.items(): if task is None: continue if task.done() or task.cancelled(): self._task_registry[key] = None self._task_registry = { k: v for k, v in self._task_registry.items() if v is not None } def shutdown_tasks( self, timeout: float | None = None, increment: float = 0.1 ) -> None: """Cancel all tasks except the server task. This method is used to cancel all tasks except the server task. It iterates through the task registry, cancelling all tasks except the server task, and then waits for the tasks to complete. Optionally, you can provide a timeout and an increment to control how long the method will wait for the tasks to complete. Args: timeout (Optional[float]): The amount of time to wait for the tasks to complete. Defaults to `None`. increment (float): The amount of time to wait between checks for whether the tasks have completed. Defaults to `0.1`. """ for task in self.tasks: if task.get_name() != "RunServer": task.cancel() if timeout is None: timeout = self.config.GRACEFUL_SHUTDOWN_TIMEOUT while len(self._task_registry) and timeout: with suppress(RuntimeError): running_loop = get_running_loop() running_loop.run_until_complete(asyncio.sleep(increment)) self.purge_tasks() timeout -= increment @property def tasks(self) -> Iterable[Task[Any]]: """The tasks that are currently registered with the application. Returns: Iterable[Task[Any]]: The tasks that are currently registered with the application. """ return ( task for task in iter(self._task_registry.values()) if task is not None ) # -------------------------------------------------------------------- # # ASGI # -------------------------------------------------------------------- # async def __call__(self, scope, receive, send): """ To be ASGI compliant, our instance must be a callable that accepts three arguments: scope, receive, send. See the ASGI reference for more details: https://asgi.readthedocs.io/en/latest """ if scope["type"] == "lifespan": setup_logging( self.state.is_debug, self.config.NO_COLOR, self.config.LOG_EXTRA, ) self.asgi = True self.motd("") self._asgi_lifespan = Lifespan(self, scope, receive, send) await self._asgi_lifespan() else: self._asgi_app = await ASGIApp.create(self, scope, receive, send) await self._asgi_app() _asgi_single_callable = True # We conform to ASGI 3.0 single-callable # -------------------------------------------------------------------- # # Configuration # -------------------------------------------------------------------- # def update_config(self, config: bytes | str | dict | Any) -> None: """Update the application configuration. This method is used to update the application configuration. It can accept a configuration object, a dictionary, or a path to a file that contains a configuration object or dictionary. See [Configuration](/en/guide/deployment/configuration) for details. Args: config (Union[bytes, str, dict, Any]): The configuration object, dictionary, or path to a configuration file. """ self.config.update_config(config) @property def asgi(self) -> bool: """Whether the app is running in ASGI mode.""" return self.state.asgi @asgi.setter def asgi(self, value: bool): self.state.asgi = value @property def debug(self) -> bool: """Whether the app is running in debug mode.""" return self.state.is_debug @property def auto_reload(self) -> bool: """Whether the app is running in auto-reload mode.""" return self.config.AUTO_RELOAD @auto_reload.setter def auto_reload(self, value: bool): self.config.AUTO_RELOAD = value self.state.auto_reload = value @property def state(self) -> ApplicationState: # type: ignore """The application state. Returns: ApplicationState: The current state of the application. """ return self._state @property def reload_dirs(self) -> set[Path]: """The directories that are monitored for auto-reload. Returns: Set[str]: The set of directories that are monitored for auto-reload. """ return self.state.reload_dirs # -------------------------------------------------------------------- # # Sanic Extensions # -------------------------------------------------------------------- # @property def ext(self) -> Extend: """Convenience property for accessing Sanic Extensions. This property is available if the `sanic-ext` package is installed. See [Sanic Extensions](/en/plugins/sanic-ext/getting-started) for details. Returns: Extend: The Sanic Extensions instance. Examples: A typical use case might be for registering a dependency injection. ```python app.ext.dependency(SomeObject()) ``` """ if not hasattr(self, "_ext"): setup_ext(self, fail=True) if not hasattr(self, "_ext"): raise RuntimeError( "Sanic Extensions is not installed. You can add it to your " "environment using:\n$ pip install sanic[ext]\nor\n$ pip " "install sanic-ext" ) return self._ext # type: ignore def extend( self, *, extensions: list[type[Extension]] | None = None, built_in_extensions: bool = True, config: Config | dict[str, Any] | None = None, **kwargs, ) -> Extend: """Extend Sanic with additional functionality using Sanic Extensions. This method enables you to add one or more Sanic Extensions to the current Sanic instance. It allows for more control over the Extend object, such as enabling or disabling built-in extensions or providing custom configuration. See [Sanic Extensions](/en/plugins/sanic-ext/getting-started) for details. Args: extensions (Optional[List[Type[Extension]]], optional): A list of extensions to add. Defaults to `None`, meaning only built-in extensions are added. built_in_extensions (bool, optional): Whether to enable built-in extensions. Defaults to `True`. config (Optional[Union[Config, Dict[str, Any]]], optional): Optional custom configuration for the extensions. Defaults to `None`. **kwargs: Additional keyword arguments that might be needed by specific extensions. Returns: Extend: The Sanic Extensions instance. Raises: RuntimeError: If an attempt is made to extend Sanic after Sanic Extensions has already been set up. Examples: A typical use case might be to add a custom extension along with built-in ones. ```python app.extend( extensions=[MyCustomExtension], built_in_extensions=True ) ``` """ if hasattr(self, "_ext"): raise RuntimeError( "Cannot extend Sanic after Sanic Extensions has been setup." ) setup_ext( self, extensions=extensions, built_in_extensions=built_in_extensions, config=config, fail=True, **kwargs, ) return self.ext # -------------------------------------------------------------------- # # Class methods # -------------------------------------------------------------------- # @classmethod def register_app(cls, app: Sanic) -> None: """Register a Sanic instance with the class registry. This method adds a Sanic application instance to the class registry, which is used for tracking all instances of the application. It is usually used internally, but can be used to register an application that may have otherwise been created outside of the class registry. Args: app (Sanic): The Sanic instance to be registered. Raises: SanicException: If the app is not an instance of Sanic or if the name of the app is already in use (unless in test mode). Examples: ```python Sanic.register_app(my_app) ``` """ if not isinstance(app, cls): raise SanicException("Registered app must be an instance of Sanic") name = app.name if name in cls._app_registry and not cls.test_mode: raise SanicException(f'Sanic app name "{name}" already in use.') cls._app_registry[name] = app @classmethod def unregister_app(cls, app: Sanic) -> None: """Unregister a Sanic instance from the class registry. This method removes a previously registered Sanic application instance from the class registry. This can be useful for cleanup purposes, especially in testing or when an app instance is no longer needed. But, it is typically used internally and should not be needed in most cases. Args: app (Sanic): The Sanic instance to be unregistered. Raises: SanicException: If the app is not an instance of Sanic. Examples: ```python Sanic.unregister_app(my_app) ``` """ if not isinstance(app, cls): raise SanicException("Registered app must be an instance of Sanic") name = app.name if name in cls._app_registry: del cls._app_registry[name] @classmethod def get_app( cls, name: str | None = None, *, force_create: bool = False ) -> Sanic: """Retrieve an instantiated Sanic instance by name. This method is best used when needing to get access to an already defined application instance in another part of an app. .. warning:: Be careful when using this method in the global scope as it is possible that the import path running will cause it to error if the imported global scope runs before the application instance is created. It is typically best used in a function or method that is called after the application instance has been created. ```python def setup_routes(): app = Sanic.get_app() app.add_route(handler_1, '/route1') app.add_route(handler_2, '/route2') ``` Args: name (Optional[str], optional): Name of the application instance to retrieve. When not specified, it will return the only application instance if there is only one. If not specified and there are multiple application instances, it will raise an exception. Defaults to `None`. force_create (bool, optional): If `True` and the named app does not exist, a new instance will be created. Defaults to `False`. Returns: Sanic: The requested Sanic app instance. Raises: SanicException: If there are multiple or no Sanic apps found, or if the specified name is not found. Example: ```python app1 = Sanic("app1") app2 = Sanic.get_app("app1") # app2 is the same instance as app1 ``` """ if name is None: if len(cls._app_registry) > 1: raise SanicException( 'Multiple Sanic apps found, use Sanic.get_app("app_name")' ) elif len(cls._app_registry) == 0: raise SanicException("No Sanic apps have been registered.") else: return list(cls._app_registry.values())[0] try: return cls._app_registry[name] except KeyError: if name == "__main__": return cls.get_app("__mp_main__", force_create=force_create) if force_create: return cls(name) raise SanicException( f"Sanic app name '{name}' not found.\n" "App instantiation must occur outside " "if __name__ == '__main__' " "block or by using an AppLoader.\nSee " "https://sanic.dev/en/guide/deployment/app-loader.html" " for more details." ) @classmethod def _check_uvloop_conflict(cls) -> None: values = {app.config.USE_UVLOOP for app in cls._app_registry.values()} if len(values) > 1: error_logger.warning( "It looks like you're running several apps with different " "uvloop settings. This is not supported and may lead to " "unintended behaviour." ) # -------------------------------------------------------------------- # # Lifecycle # -------------------------------------------------------------------- # @contextmanager def amend(self) -> Iterator[None]: """Context manager to allow changes to the app after it has started. Typically, once an application has started and is running, you cannot make certain changes, like adding routes, middleware, or signals. This context manager allows you to make those changes, and then finalizes the app again when the context manager exits. Yields: None Example: ```python with app.amend(): app.add_route(handler, '/new_route') ``` """ if not self.state.is_started: yield else: do_router = self.router.finalized do_signal_router = self.signal_router.finalized if do_router: self.router.reset() if do_signal_router: self.signal_router.reset() yield if do_signal_router: self.signalize(cast(bool, self.config.TOUCHUP)) if do_router: self.finalize() def finalize(self) -> None: """Finalize the routing configuration for the Sanic application. This method completes the routing setup by calling the router's finalize method, and it also finalizes any middleware that has been added to the application. If the application is not in test mode, any finalization errors will be raised. Finalization consists of identifying defined routes and optimizing Sanic's performance to meet the application's specific needs. If you are manually adding routes, after Sanic has started, you will typically want to use the `amend` context manager rather than calling this method directly. .. note:: This method is usually called internally during the server setup process and does not typically need to be invoked manually. Raises: FinalizationError: If there is an error during the finalization process, and the application is not in test mode. Example: ```python app.finalize() ``` """ try: self.router.finalize() except FinalizationError as e: if not Sanic.test_mode: raise e self.finalize_middleware() def signalize(self, allow_fail_builtin: bool = True) -> None: """Finalize the signal handling configuration for the Sanic application. This method completes the signal handling setup by calling the signal router's finalize method. If the application is not in test mode, any finalization errors will be raised. Finalization consists of identifying defined signaliz and optimizing Sanic's performance to meet the application's specific needs. If you are manually adding signals, after Sanic has started, you will typically want to use the `amend` context manager rather than calling this method directly. .. note:: This method is usually called internally during the server setup process and does not typically need to be invoked manually. Args: allow_fail_builtin (bool, optional): If set to `True`, will allow built-in signals to fail during the finalization process. Defaults to `True`. Raises: FinalizationError: If there is an error during the signal finalization process, and the application is not in test mode. Example: ```python app.signalize(allow_fail_builtin=False) ``` """ # noqa: E501 self.signal_router.allow_fail_builtin = allow_fail_builtin try: self.signal_router.finalize() except FinalizationError as e: if not Sanic.test_mode: raise e async def _startup(self): self._future_registry.clear() if not hasattr(self, "_ext"): setup_ext(self) if hasattr(self, "_ext"): self.ext._display() if self.state.is_debug and self.config.TOUCHUP is not True: self.config.TOUCHUP = False elif isinstance(self.config.TOUCHUP, Default): self.config.TOUCHUP = True # Setup routers self.signalize(self.config.TOUCHUP) self.finalize() route_names = [route.extra.ident for route in self.router.routes] duplicates = { name for name in route_names if route_names.count(name) > 1 } if duplicates: names = ", ".join(duplicates) 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 " "https://sanic.dev/en/guide/release-notes/v23.3.html#duplicated-route-names-are-no-longer-allowed" # noqa ) raise ServerError(message) Sanic._check_uvloop_conflict() # Startup time optimizations if self.state.primary: # TODO: # - Raise warning if secondary apps have error handler config if self.config.TOUCHUP: TouchUp.run(self) self.state.is_started = True def ack(self) -> None: """Shorthand to send an ack message to the Server Manager. In general, this should usually not need to be called manually. It is used to tell the Manager that a process is operational and ready to begin operation. """ if hasattr(self, "multiplexer"): self.multiplexer.ack() def set_serving(self, serving: bool) -> None: """Set the serving state of the application. This method is used to set the serving state of the application. It is used internally by Sanic and should not typically be called manually. Args: serving (bool): Whether the application is serving. """ self.state.is_running = serving if hasattr(self, "multiplexer"): self.multiplexer.set_serving(serving) async def _server_event( self, concern: str, action: str, loop: AbstractEventLoop | None = None, ) -> None: event = f"server.{concern}.{action}" if action not in ("before", "after") or concern not in ( "init", "shutdown", ): raise SanicException(f"Invalid server event: {event}") logger.debug( f"Triggering server events: {event}", extra={"verbosity": 1} ) reverse = concern == "shutdown" if loop is None: loop = self.loop await self.dispatch( event, fail_not_found=False, reverse=reverse, inline=True, context={ "app": self, "loop": loop, }, ) # -------------------------------------------------------------------- # # Process Management # -------------------------------------------------------------------- # def refresh( self, passthru: dict[str, Any] | None = None, ) -> Sanic: """Refresh the application instance. **This is used internally by Sanic**. .. warning:: This method is intended for internal use only and should not be called directly. Args: passthru (Optional[Dict[str, Any]], optional): Optional dictionary of attributes to pass through to the new instance. Defaults to `None`. Returns: Sanic: The refreshed application instance. """ # noqa: E501 registered = self.__class__.get_app(self.name) if self is not registered: if not registered.state.server_info: registered.state.server_info = self.state.server_info self = registered if passthru: for attr, info in passthru.items(): if isinstance(info, dict): for key, value in info.items(): setattr(getattr(self, attr), key, value) else: setattr(self, attr, info) if hasattr(self, "multiplexer"): self.shared_ctx.lock() return self @property def inspector(self) -> Inspector: """An instance of Inspector for accessing the application's state. This can only be accessed from a worker process, and only if the inspector has been enabled. See [Inspector](/en/guide/deployment/inspector) for details. Returns: Inspector: An instance of Inspector. """ if environ.get("SANIC_WORKER_PROCESS") or not self._inspector: raise SanicException( "Can only access the inspector from the main process " "after main_process_start has run. For example, you most " "likely want to use it inside the @app.main_process_ready " "event listener." ) return self._inspector @property def manager(self) -> WorkerManager: """ Property to access the WorkerManager instance. This property provides access to the WorkerManager object controlling the worker processes. It can only be accessed from the main process. .. note:: Make sure to only access this property from the main process, as attempting to do so from a worker process will result in an exception. See [WorkerManager](/en/guide/deployment/manager) for details. Returns: WorkerManager: The manager responsible for managing worker processes. Raises: SanicException: If an attempt is made to access the manager from a worker process or if the manager is not initialized. Example: ```python app.manager.manage(...) ``` """ if environ.get("SANIC_WORKER_PROCESS") or not self._manager: raise SanicException( "Can only access the manager from the main process " "after main_process_start has run. For example, you most " "likely want to use it inside the @app.main_process_ready " "event listener." ) return self._manager