Auto extend with Sanic Extensions (#2308)
This commit is contained in:
110
sanic/app.py
110
sanic/app.py
@@ -28,6 +28,7 @@ from ssl import SSLContext
|
||||
from traceback import format_exc
|
||||
from types import SimpleNamespace
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AnyStr,
|
||||
Awaitable,
|
||||
@@ -41,6 +42,7 @@ from typing import (
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from urllib.parse import urlencode, urlunparse
|
||||
@@ -53,6 +55,7 @@ from sanic_routing.exceptions import ( # type: ignore
|
||||
from sanic_routing.route import Route # type: ignore
|
||||
|
||||
from sanic import reloader_helpers
|
||||
from sanic.application.ext import setup_ext
|
||||
from sanic.application.logo import get_logo
|
||||
from sanic.application.motd import MOTD
|
||||
from sanic.application.state import ApplicationState, Mode
|
||||
@@ -103,11 +106,21 @@ from sanic.tls import process_to_context
|
||||
from sanic.touchup import TouchUp, TouchUpMeta
|
||||
|
||||
|
||||
if TYPE_CHECKING: # no cov
|
||||
try:
|
||||
from sanic_ext import Extend # type: ignore
|
||||
from sanic_ext.extensions.base import Extension # type: ignore
|
||||
except ImportError:
|
||||
Extend = TypeVar("Extend") # type: ignore
|
||||
|
||||
|
||||
if OS_IS_WINDOWS:
|
||||
enable_windows_color_support()
|
||||
|
||||
filterwarnings("once", category=DeprecationWarning)
|
||||
|
||||
SANIC_PACKAGES = ("sanic-routing", "sanic-testing", "sanic-ext")
|
||||
|
||||
|
||||
class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"""
|
||||
@@ -125,6 +138,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"_asgi_client",
|
||||
"_blueprint_order",
|
||||
"_delayed_tasks",
|
||||
"_ext",
|
||||
"_future_exceptions",
|
||||
"_future_listeners",
|
||||
"_future_middleware",
|
||||
@@ -1421,26 +1435,15 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"#proxy-configuration"
|
||||
)
|
||||
|
||||
ssl = process_to_context(ssl)
|
||||
|
||||
self.debug = debug
|
||||
self.state.host = host
|
||||
self.state.port = port
|
||||
self.state.workers = workers
|
||||
|
||||
# Serve
|
||||
serve_location = ""
|
||||
proto = "http"
|
||||
if ssl is not None:
|
||||
proto = "https"
|
||||
if unix:
|
||||
serve_location = f"{unix} {proto}://..."
|
||||
elif sock:
|
||||
serve_location = f"{sock.getsockname()} {proto}://..."
|
||||
elif host and port:
|
||||
# colon(:) is legal for a host only in an ipv6 address
|
||||
display_host = f"[{host}]" if ":" in host else host
|
||||
serve_location = f"{proto}://{display_host}:{port}"
|
||||
|
||||
ssl = process_to_context(ssl)
|
||||
self.state.ssl = ssl
|
||||
self.state.unix = unix
|
||||
self.state.sock = sock
|
||||
|
||||
server_settings = {
|
||||
"protocol": protocol,
|
||||
@@ -1456,7 +1459,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"backlog": backlog,
|
||||
}
|
||||
|
||||
self.motd(serve_location)
|
||||
self.motd(self.serve_location)
|
||||
|
||||
if sys.stdout.isatty() and not self.state.is_debug:
|
||||
error_logger.warning(
|
||||
@@ -1482,6 +1485,27 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
|
||||
return server_settings
|
||||
|
||||
@property
|
||||
def serve_location(self) -> str:
|
||||
serve_location = ""
|
||||
proto = "http"
|
||||
if self.state.ssl is not None:
|
||||
proto = "https"
|
||||
if self.state.unix:
|
||||
serve_location = f"{self.state.unix} {proto}://..."
|
||||
elif self.state.sock:
|
||||
serve_location = f"{self.state.sock.getsockname()} {proto}://..."
|
||||
elif self.state.host and self.state.port:
|
||||
# colon(:) is legal for a host only in an ipv6 address
|
||||
display_host = (
|
||||
f"[{self.state.host}]"
|
||||
if ":" in self.state.host
|
||||
else self.state.host
|
||||
)
|
||||
serve_location = f"{proto}://{display_host}:{self.state.port}"
|
||||
|
||||
return serve_location
|
||||
|
||||
def _build_endpoint_name(self, *parts):
|
||||
parts = [self.name, *parts]
|
||||
return ".".join(parts)
|
||||
@@ -1790,11 +1814,8 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
display["auto-reload"] = reload_display
|
||||
|
||||
packages = []
|
||||
for package_name, module_name in {
|
||||
"sanic-routing": "sanic_routing",
|
||||
"sanic-testing": "sanic_testing",
|
||||
"sanic-ext": "sanic_ext",
|
||||
}.items():
|
||||
for package_name in SANIC_PACKAGES:
|
||||
module_name = package_name.replace("-", "_")
|
||||
try:
|
||||
module = import_module(module_name)
|
||||
packages.append(f"{package_name}=={module.__version__}")
|
||||
@@ -1814,6 +1835,41 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
)
|
||||
MOTD.output(logo, serve_location, display, extra)
|
||||
|
||||
@property
|
||||
def ext(self) -> Extend:
|
||||
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: Optional[List[Type[Extension]]] = None,
|
||||
built_in_extensions: bool = True,
|
||||
config: Optional[Union[Config, Dict[str, Any]]] = None,
|
||||
**kwargs,
|
||||
) -> Extend:
|
||||
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
|
||||
# -------------------------------------------------------------------- #
|
||||
@@ -1875,6 +1931,14 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
|
||||
async def _startup(self):
|
||||
self._future_registry.clear()
|
||||
|
||||
# Startup Sanic Extensions
|
||||
if not hasattr(self, "_ext"):
|
||||
setup_ext(self)
|
||||
if hasattr(self, "_ext"):
|
||||
self.ext._display()
|
||||
|
||||
# Setup routers
|
||||
self.signalize()
|
||||
self.finalize()
|
||||
|
||||
@@ -1890,8 +1954,10 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
)
|
||||
self.__class__._uvloop_setting = self.config.USE_UVLOOP
|
||||
|
||||
# Startup time optimizations
|
||||
ErrorHandler.finalize(self.error_handler, config=self.config)
|
||||
TouchUp.run(self)
|
||||
|
||||
self.state.is_started = True
|
||||
|
||||
async def _server_event(
|
||||
|
||||
39
sanic/application/ext.py
Normal file
39
sanic/application/ext.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from importlib import import_module
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING: # no cov
|
||||
from sanic import Sanic
|
||||
|
||||
try:
|
||||
from sanic_ext import Extend # type: ignore
|
||||
except ImportError:
|
||||
...
|
||||
|
||||
|
||||
def setup_ext(app: Sanic, *, fail: bool = False, **kwargs):
|
||||
if not app.config.AUTO_EXTEND:
|
||||
return
|
||||
|
||||
sanic_ext = None
|
||||
with suppress(ModuleNotFoundError):
|
||||
sanic_ext = import_module("sanic_ext")
|
||||
|
||||
if not sanic_ext:
|
||||
if fail:
|
||||
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
|
||||
|
||||
if not getattr(app, "_ext", None):
|
||||
Ext: Extend = getattr(sanic_ext, "Extend")
|
||||
app._ext = Ext(app, **kwargs)
|
||||
|
||||
return app.ext
|
||||
@@ -41,9 +41,6 @@ class MOTD(ABC):
|
||||
|
||||
|
||||
class MOTDBasic(MOTD):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def display(self):
|
||||
if self.logo:
|
||||
logger.debug(self.logo)
|
||||
|
||||
@@ -5,7 +5,9 @@ import logging
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Set, Union
|
||||
from socket import socket
|
||||
from ssl import SSLContext
|
||||
from typing import TYPE_CHECKING, Any, Optional, Set, Union
|
||||
|
||||
from sanic.log import logger
|
||||
|
||||
@@ -37,8 +39,11 @@ class ApplicationState:
|
||||
coffee: bool = field(default=False)
|
||||
fast: bool = field(default=False)
|
||||
host: str = field(default="")
|
||||
mode: Mode = field(default=Mode.PRODUCTION)
|
||||
port: int = field(default=0)
|
||||
ssl: Optional[SSLContext] = field(default=None)
|
||||
sock: Optional[socket] = field(default=None)
|
||||
unix: Optional[str] = field(default=None)
|
||||
mode: Mode = field(default=Mode.PRODUCTION)
|
||||
reload_dirs: Set[Path] = field(default_factory=set)
|
||||
server: Server = field(default=Server.SANIC)
|
||||
is_running: bool = field(default=False)
|
||||
|
||||
@@ -5,7 +5,7 @@ from functools import partial
|
||||
from typing import TYPE_CHECKING, List, Optional, Union
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if TYPE_CHECKING: # no cov
|
||||
from sanic.blueprints import Blueprint
|
||||
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ from sanic.models.handler_types import (
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic # noqa
|
||||
if TYPE_CHECKING: # no cov
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
def lazy(func, as_decorator=True):
|
||||
|
||||
@@ -18,6 +18,7 @@ SANIC_PREFIX = "SANIC_"
|
||||
DEFAULT_CONFIG = {
|
||||
"_FALLBACK_ERROR_FORMAT": _default,
|
||||
"ACCESS_LOG": True,
|
||||
"AUTO_EXTEND": True,
|
||||
"AUTO_RELOAD": False,
|
||||
"EVENT_AUTOREGISTER": False,
|
||||
"FORWARDED_FOR_HEADER": "X-Forwarded-For",
|
||||
@@ -59,6 +60,7 @@ class DescriptorMeta(type):
|
||||
|
||||
class Config(dict, metaclass=DescriptorMeta):
|
||||
ACCESS_LOG: bool
|
||||
AUTO_EXTEND: bool
|
||||
AUTO_RELOAD: bool
|
||||
EVENT_AUTOREGISTER: bool
|
||||
FORWARDED_FOR_HEADER: str
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if TYPE_CHECKING: # no cov
|
||||
from sanic.request import Request
|
||||
from sanic.response import BaseHTTPResponse
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from typing import (
|
||||
from sanic_routing.route import Route # type: ignore
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if TYPE_CHECKING: # no cov
|
||||
from sanic.server import ConnInfo
|
||||
from sanic.app import Sanic
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from functools import partial
|
||||
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
||||
from signal import signal as signal_func
|
||||
|
||||
from sanic.application.ext import setup_ext
|
||||
from sanic.compat import OS_IS_WINDOWS, ctrlc_workaround_for_windows
|
||||
from sanic.log import error_logger, logger
|
||||
from sanic.models.server_types import Signal
|
||||
@@ -116,6 +117,7 @@ def serve(
|
||||
**asyncio_server_kwargs,
|
||||
)
|
||||
|
||||
setup_ext(app)
|
||||
if run_async:
|
||||
return AsyncioServer(
|
||||
app=app,
|
||||
|
||||
@@ -13,7 +13,7 @@ from typing import (
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if TYPE_CHECKING: # no cov
|
||||
from sanic import Sanic
|
||||
from sanic.blueprints import Blueprint
|
||||
|
||||
@@ -81,6 +81,8 @@ class HTTPMethodView:
|
||||
|
||||
def dispatch_request(self, request, *args, **kwargs):
|
||||
handler = getattr(self, request.method.lower(), None)
|
||||
if not handler and request.method == "HEAD":
|
||||
handler = self.get
|
||||
return handler(request, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -15,10 +15,10 @@ from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
||||
|
||||
try:
|
||||
import ssl # type: ignore
|
||||
except ImportError:
|
||||
except ImportError: # no cov
|
||||
ssl = None # type: ignore
|
||||
|
||||
if UVLOOP_INSTALLED:
|
||||
if UVLOOP_INSTALLED: # no cov
|
||||
try_use_uvloop()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user