LTS v21.12 Deprecations (#2306)
Co-authored-by: Néstor Pérez <25409753+prryplatypus@users.noreply.github.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
__version__ = "21.12.0dev"
|
||||
__version__ = "21.12.0"
|
||||
|
||||
77
sanic/app.py
77
sanic/app.py
@@ -44,7 +44,7 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
from urllib.parse import urlencode, urlunparse
|
||||
from warnings import filterwarnings, warn
|
||||
from warnings import filterwarnings
|
||||
|
||||
from sanic_routing.exceptions import ( # type: ignore
|
||||
FinalizationError,
|
||||
@@ -57,7 +57,7 @@ from sanic.application.logo import get_logo
|
||||
from sanic.application.motd import MOTD
|
||||
from sanic.application.state import ApplicationState, Mode
|
||||
from sanic.asgi import ASGIApp
|
||||
from sanic.base import BaseSanic
|
||||
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
|
||||
@@ -71,7 +71,13 @@ from sanic.exceptions import (
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic.helpers import _default
|
||||
from sanic.http import Stage
|
||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, Colors, error_logger, logger
|
||||
from sanic.log import (
|
||||
LOGGING_CONFIG_DEFAULTS,
|
||||
Colors,
|
||||
deprecation,
|
||||
error_logger,
|
||||
logger,
|
||||
)
|
||||
from sanic.mixins.listeners import ListenerEvent
|
||||
from sanic.models.futures import (
|
||||
FutureException,
|
||||
@@ -85,7 +91,7 @@ from sanic.models.futures import (
|
||||
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
|
||||
from sanic.response import BaseHTTPResponse, HTTPResponse, ResponseStream
|
||||
from sanic.router import Router
|
||||
from sanic.server import AsyncioServer, HttpProtocol
|
||||
from sanic.server import Signal as ServerSignal
|
||||
@@ -114,8 +120,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"_run_response_middleware",
|
||||
"_run_request_middleware",
|
||||
)
|
||||
__fake_slots__ = (
|
||||
"_app_registry",
|
||||
__slots__ = (
|
||||
"_asgi_app",
|
||||
"_asgi_client",
|
||||
"_blueprint_order",
|
||||
@@ -131,19 +136,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"_task_registry",
|
||||
"_test_client",
|
||||
"_test_manager",
|
||||
"_uvloop_setting", # TODO: Remove in v22.6
|
||||
"asgi",
|
||||
"auto_reload",
|
||||
"auto_reload",
|
||||
"blueprints",
|
||||
"config",
|
||||
"configure_logging",
|
||||
"ctx",
|
||||
"debug",
|
||||
"error_handler",
|
||||
"go_fast",
|
||||
"is_running",
|
||||
"is_stopping",
|
||||
"listeners",
|
||||
"name",
|
||||
"named_request_middleware",
|
||||
@@ -155,13 +153,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"signal_router",
|
||||
"sock",
|
||||
"strict_slashes",
|
||||
"test_mode",
|
||||
"websocket_enabled",
|
||||
"websocket_tasks",
|
||||
)
|
||||
|
||||
_app_registry: Dict[str, "Sanic"] = {}
|
||||
_uvloop_setting = None
|
||||
_uvloop_setting = None # TODO: Remove in v22.6
|
||||
test_mode = False
|
||||
|
||||
def __init__(
|
||||
@@ -172,7 +169,6 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
router: Optional[Router] = None,
|
||||
signal_router: Optional[SignalRouter] = None,
|
||||
error_handler: Optional[ErrorHandler] = None,
|
||||
load_env: Union[bool, str] = True,
|
||||
env_prefix: Optional[str] = SANIC_PREFIX,
|
||||
request_class: Optional[Type[Request]] = None,
|
||||
strict_slashes: bool = False,
|
||||
@@ -188,17 +184,16 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
dict_config = log_config or LOGGING_CONFIG_DEFAULTS
|
||||
logging.config.dictConfig(dict_config) # type: ignore
|
||||
|
||||
if config and (load_env is not True or env_prefix != SANIC_PREFIX):
|
||||
if config and env_prefix != SANIC_PREFIX:
|
||||
raise SanicException(
|
||||
"When instantiating Sanic with config, you cannot also pass "
|
||||
"load_env or env_prefix"
|
||||
"env_prefix"
|
||||
)
|
||||
|
||||
self.config: Config = config or Config(
|
||||
load_env=load_env,
|
||||
env_prefix=env_prefix,
|
||||
)
|
||||
# First setup config
|
||||
self.config: Config = config or Config(env_prefix=env_prefix)
|
||||
|
||||
# Then we can do the rest
|
||||
self._asgi_client: Any = None
|
||||
self._blueprint_order: List[Blueprint] = []
|
||||
self._delayed_tasks: List[str] = []
|
||||
@@ -231,6 +226,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
self.go_fast = self.run
|
||||
|
||||
if register is not None:
|
||||
deprecation(
|
||||
"The register argument is deprecated and will stop working "
|
||||
"in v22.6. After v22.6 all apps will be added to the Sanic "
|
||||
"app registry.",
|
||||
22.6,
|
||||
)
|
||||
self.config.REGISTER = register
|
||||
if self.config.REGISTER:
|
||||
self.__class__.register_app(self)
|
||||
@@ -740,7 +741,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
exception, request.name if request else None
|
||||
)
|
||||
if handler:
|
||||
warn(
|
||||
deprecation(
|
||||
"An error occurred while handling the request after at "
|
||||
"least some part of the response was sent to the client. "
|
||||
"Therefore, the response from your custom exception "
|
||||
@@ -755,7 +756,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"For further information, please see the docs: "
|
||||
"https://sanicframework.org/en/guide/advanced/"
|
||||
"signals.html",
|
||||
DeprecationWarning,
|
||||
22.6,
|
||||
)
|
||||
try:
|
||||
response = self.error_handler.response(request, exception)
|
||||
@@ -808,6 +809,9 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
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",
|
||||
@@ -818,6 +822,17 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
},
|
||||
)
|
||||
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)"
|
||||
@@ -921,7 +936,8 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
elif not hasattr(handler, "is_websocket"):
|
||||
response = request.stream.response # type: ignore
|
||||
|
||||
# Make sure that response is finished / run StreamingHTTP callback
|
||||
# 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",
|
||||
@@ -932,6 +948,17 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
},
|
||||
)
|
||||
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(
|
||||
|
||||
0
sanic/base/__init__.py
Normal file
0
sanic/base/__init__.py
Normal file
6
sanic/base/meta.py
Normal file
6
sanic/base/meta.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class SanicMeta(type):
|
||||
@classmethod
|
||||
def __prepare__(metaclass, name, bases, **kwds):
|
||||
cls = super().__prepare__(metaclass, name, bases, **kwds)
|
||||
cls["__slots__"] = ()
|
||||
return cls
|
||||
@@ -1,8 +1,8 @@
|
||||
import re
|
||||
|
||||
from typing import Any, Tuple
|
||||
from warnings import warn
|
||||
from typing import Any
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.mixins.exceptions import ExceptionMixin
|
||||
from sanic.mixins.listeners import ListenerMixin
|
||||
@@ -20,8 +20,9 @@ class BaseSanic(
|
||||
ListenerMixin,
|
||||
ExceptionMixin,
|
||||
SignalMixin,
|
||||
metaclass=SanicMeta,
|
||||
):
|
||||
__fake_slots__: Tuple[str, ...]
|
||||
__slots__ = ("name",)
|
||||
|
||||
def __init__(self, name: str = None, *args: Any, **kwargs: Any) -> None:
|
||||
class_name = self.__class__.__name__
|
||||
@@ -33,11 +34,10 @@ class BaseSanic(
|
||||
)
|
||||
|
||||
if not VALID_NAME.match(name):
|
||||
warn(
|
||||
f"{class_name} instance named '{name}' uses a format that is"
|
||||
f"deprecated. Starting in version 21.12, {class_name} objects "
|
||||
"must be named only using alphanumeric characters, _, or -.",
|
||||
DeprecationWarning,
|
||||
raise SanicException(
|
||||
f"{class_name} instance named '{name}' uses an invalid "
|
||||
"format. Names must begin with a character and may only "
|
||||
"contain alphanumeric characters, _, or -."
|
||||
)
|
||||
|
||||
self.name = name
|
||||
@@ -52,15 +52,12 @@ class BaseSanic(
|
||||
return f'{self.__class__.__name__}(name="{self.name}")'
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
# This is a temporary compat layer so we can raise a warning until
|
||||
# setting attributes on the app instance can be removed and deprecated
|
||||
# with a proper implementation of __slots__
|
||||
if name not in self.__fake_slots__:
|
||||
warn(
|
||||
try:
|
||||
super().__setattr__(name, value)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(
|
||||
f"Setting variables on {self.__class__.__name__} instances is "
|
||||
"deprecated and will be removed in version 21.12. You should "
|
||||
f"change your {self.__class__.__name__} instance to use "
|
||||
"not allowed. You should change your "
|
||||
f"{self.__class__.__name__} instance to use "
|
||||
f"instance.ctx.{name} instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
super().__setattr__(name, value)
|
||||
) from e
|
||||
@@ -24,7 +24,7 @@ from typing import (
|
||||
from sanic_routing.exceptions import NotFound # type: ignore
|
||||
from sanic_routing.route import Route # type: ignore
|
||||
|
||||
from sanic.base import BaseSanic
|
||||
from sanic.base.root import BaseSanic
|
||||
from sanic.blueprint_group import BlueprintGroup
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.helpers import Default, _default
|
||||
@@ -85,7 +85,7 @@ class Blueprint(BaseSanic):
|
||||
trailing */*
|
||||
"""
|
||||
|
||||
__fake_slots__ = (
|
||||
__slots__ = (
|
||||
"_apps",
|
||||
"_future_routes",
|
||||
"_future_statics",
|
||||
@@ -98,7 +98,6 @@ class Blueprint(BaseSanic):
|
||||
"host",
|
||||
"listeners",
|
||||
"middlewares",
|
||||
"name",
|
||||
"routes",
|
||||
"statics",
|
||||
"strict_slashes",
|
||||
|
||||
@@ -4,12 +4,11 @@ from inspect import getmembers, isclass, isdatadescriptor
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Union
|
||||
from warnings import warn
|
||||
|
||||
from sanic.errorpages import DEFAULT_FORMAT, check_error_format
|
||||
from sanic.helpers import Default, _default
|
||||
from sanic.http import Http
|
||||
from sanic.log import error_logger
|
||||
from sanic.log import deprecation, error_logger
|
||||
from sanic.utils import load_module_from_file_location, str_to_bool
|
||||
|
||||
|
||||
@@ -88,7 +87,6 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||
def __init__(
|
||||
self,
|
||||
defaults: Dict[str, Union[str, bool, int, float, None]] = None,
|
||||
load_env: Optional[Union[bool, str]] = True,
|
||||
env_prefix: Optional[str] = SANIC_PREFIX,
|
||||
keep_alive: Optional[bool] = None,
|
||||
*,
|
||||
@@ -110,15 +108,6 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||
if env_prefix != SANIC_PREFIX:
|
||||
if env_prefix:
|
||||
self.load_environment_vars(env_prefix)
|
||||
elif load_env is not True:
|
||||
if load_env:
|
||||
self.load_environment_vars(prefix=load_env)
|
||||
warn(
|
||||
"Use of load_env is deprecated and will be removed in "
|
||||
"21.12. Modify the configuration prefix by passing "
|
||||
"env_prefix instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
self.load_environment_vars(SANIC_PREFIX)
|
||||
|
||||
@@ -161,10 +150,10 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||
self._configure_header_size()
|
||||
elif attr == "LOGO":
|
||||
self._LOGO = value
|
||||
warn(
|
||||
deprecation(
|
||||
"Setting the config.LOGO is deprecated and will no longer "
|
||||
"be supported starting in v22.6.",
|
||||
DeprecationWarning,
|
||||
22.6,
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -244,25 +244,3 @@ class InvalidSignal(SanicException):
|
||||
class WebsocketClosed(SanicException):
|
||||
quiet = True
|
||||
message = "Client has closed the websocket connection"
|
||||
|
||||
|
||||
def abort(status_code: int, message: Optional[Union[str, bytes]] = None):
|
||||
"""
|
||||
Raise an exception based on SanicException. Returns the HTTP response
|
||||
message appropriate for the given status code, unless provided.
|
||||
|
||||
STATUS_CODES from sanic.helpers for the given status code.
|
||||
|
||||
:param status_code: The HTTP status code to return.
|
||||
:param message: The HTTP response body. Defaults to the messages in
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"sanic.exceptions.abort has been marked as deprecated, and will be "
|
||||
"removed in release 21.12.\n To migrate your code, simply replace "
|
||||
"abort(status_code, msg) with raise SanicException(msg, status_code), "
|
||||
"or even better, raise an appropriate SanicException subclass."
|
||||
)
|
||||
|
||||
raise SanicException(message=message, status_code=status_code)
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
from inspect import signature
|
||||
from typing import Dict, List, Optional, Tuple, Type, Union
|
||||
from warnings import warn
|
||||
|
||||
from sanic.config import Config
|
||||
from sanic.errorpages import (
|
||||
@@ -18,7 +17,7 @@ from sanic.exceptions import (
|
||||
SanicException,
|
||||
)
|
||||
from sanic.helpers import Default, _default
|
||||
from sanic.log import error_logger
|
||||
from sanic.log import deprecation, error_logger
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
from sanic.response import text
|
||||
|
||||
@@ -71,12 +70,12 @@ class ErrorHandler:
|
||||
|
||||
@staticmethod
|
||||
def _warn_fallback_deprecation():
|
||||
warn(
|
||||
deprecation(
|
||||
"Setting the ErrorHandler fallback value directly is "
|
||||
"deprecated and no longer supported. This feature will "
|
||||
"be removed in v22.6. Instead, use "
|
||||
"app.config.FALLBACK_ERROR_FORMAT.",
|
||||
DeprecationWarning,
|
||||
22.6,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -100,19 +99,19 @@ class ErrorHandler:
|
||||
config: Optional[Config] = None,
|
||||
):
|
||||
if fallback:
|
||||
warn(
|
||||
deprecation(
|
||||
"Setting the ErrorHandler fallback value via finalize() "
|
||||
"is deprecated and no longer supported. This feature will "
|
||||
"be removed in v22.6. Instead, use "
|
||||
"app.config.FALLBACK_ERROR_FORMAT.",
|
||||
DeprecationWarning,
|
||||
22.6,
|
||||
)
|
||||
|
||||
if config is None:
|
||||
warn(
|
||||
deprecation(
|
||||
"Starting in v22.3, config will be a required argument "
|
||||
"for ErrorHandler.finalize().",
|
||||
DeprecationWarning,
|
||||
22.3,
|
||||
)
|
||||
|
||||
if fallback and fallback != DEFAULT_FORMAT:
|
||||
@@ -131,7 +130,7 @@ class ErrorHandler:
|
||||
|
||||
sig = signature(error_handler.lookup)
|
||||
if len(sig.parameters) == 1:
|
||||
warn(
|
||||
deprecation(
|
||||
"You are using a deprecated error handler. The lookup "
|
||||
"method should accept two positional parameters: "
|
||||
"(exception, route_name: Optional[str]). "
|
||||
@@ -139,7 +138,7 @@ class ErrorHandler:
|
||||
"specific exceptions will not work properly. Beginning "
|
||||
"in v22.3, the legacy style lookup method will not "
|
||||
"work at all.",
|
||||
DeprecationWarning,
|
||||
22.3,
|
||||
)
|
||||
legacy_lookup = error_handler._legacy_lookup
|
||||
error_handler._lookup = legacy_lookup # type: ignore
|
||||
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict
|
||||
from warnings import warn
|
||||
|
||||
|
||||
LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(
|
||||
@@ -78,3 +79,11 @@ access_logger = logging.getLogger("sanic.access")
|
||||
"""
|
||||
Logger used by Sanic for access logging
|
||||
"""
|
||||
|
||||
|
||||
def deprecation(message: str, version: float):
|
||||
version_info = f"[DEPRECATION v{version}] "
|
||||
if sys.stdout.isatty():
|
||||
version_info = f"{Colors.RED}{version_info}"
|
||||
message = f"{Colors.YELLOW}{message}{Colors.END}"
|
||||
warn(version_info + message, DeprecationWarning)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Set
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.models.futures import FutureException
|
||||
|
||||
|
||||
class ExceptionMixin:
|
||||
class ExceptionMixin(metaclass=SanicMeta):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self._future_exceptions: Set[FutureException] = set()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from enum import Enum, auto
|
||||
from functools import partial
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.models.futures import FutureListener
|
||||
from sanic.models.handler_types import ListenerType, Sanic
|
||||
|
||||
@@ -18,7 +19,7 @@ class ListenerEvent(str, Enum):
|
||||
MAIN_PROCESS_STOP = auto()
|
||||
|
||||
|
||||
class ListenerMixin:
|
||||
class ListenerMixin(metaclass=SanicMeta):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self._future_listeners: List[FutureListener] = []
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from functools import partial
|
||||
from typing import List
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.models.futures import FutureMiddleware
|
||||
|
||||
|
||||
class MiddlewareMixin:
|
||||
class MiddlewareMixin(metaclass=SanicMeta):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self._future_middleware: List[FutureMiddleware] = []
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from ast import NodeVisitor, Return, parse
|
||||
from contextlib import suppress
|
||||
from functools import partial, wraps
|
||||
from inspect import getsource, signature
|
||||
from mimetypes import guess_type
|
||||
@@ -12,6 +13,7 @@ from urllib.parse import unquote
|
||||
|
||||
from sanic_routing.route import Route # type: ignore
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.compat import stat_async
|
||||
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS
|
||||
from sanic.errorpages import RESPONSE_MAPPING
|
||||
@@ -22,12 +24,11 @@ from sanic.exceptions import (
|
||||
InvalidUsage,
|
||||
)
|
||||
from sanic.handlers import ContentRangeHandler
|
||||
from sanic.log import error_logger
|
||||
from sanic.log import deprecation, error_logger
|
||||
from sanic.models.futures import FutureRoute, FutureStatic
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
from sanic.response import HTTPResponse, file, file_stream
|
||||
from sanic.types import HashableDict
|
||||
from sanic.views import CompositionView
|
||||
|
||||
|
||||
RouteWrapper = Callable[
|
||||
@@ -43,7 +44,7 @@ RESTRICTED_ROUTE_CONTEXT = (
|
||||
)
|
||||
|
||||
|
||||
class RouteMixin:
|
||||
class RouteMixin(metaclass=SanicMeta):
|
||||
name: str
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
@@ -253,14 +254,6 @@ class RouteMixin:
|
||||
if hasattr(_handler, "is_stream"):
|
||||
stream = True
|
||||
|
||||
# handle composition view differently
|
||||
if isinstance(handler, CompositionView):
|
||||
methods = handler.handlers.keys()
|
||||
for _handler in handler.handlers.values():
|
||||
if hasattr(_handler, "is_stream"):
|
||||
stream = True
|
||||
break
|
||||
|
||||
if strict_slashes is None:
|
||||
strict_slashes = self.strict_slashes
|
||||
|
||||
@@ -982,19 +975,16 @@ class RouteMixin:
|
||||
|
||||
return route
|
||||
|
||||
def _determine_error_format(self, handler) -> Optional[str]:
|
||||
if not isinstance(handler, CompositionView):
|
||||
try:
|
||||
src = dedent(getsource(handler))
|
||||
tree = parse(src)
|
||||
http_response_types = self._get_response_types(tree)
|
||||
def _determine_error_format(self, handler) -> str:
|
||||
with suppress(OSError, TypeError):
|
||||
src = dedent(getsource(handler))
|
||||
tree = parse(src)
|
||||
http_response_types = self._get_response_types(tree)
|
||||
|
||||
if len(http_response_types) == 1:
|
||||
return next(iter(http_response_types))
|
||||
except (OSError, TypeError):
|
||||
...
|
||||
if len(http_response_types) == 1:
|
||||
return next(iter(http_response_types))
|
||||
|
||||
return None
|
||||
return ""
|
||||
|
||||
def _get_response_types(self, node):
|
||||
types = set()
|
||||
@@ -1003,7 +993,18 @@ class RouteMixin:
|
||||
def visit_Return(self, node: Return) -> Any:
|
||||
nonlocal types
|
||||
|
||||
try:
|
||||
with suppress(AttributeError):
|
||||
if node.value.func.id == "stream": # type: ignore
|
||||
deprecation(
|
||||
"The sanic.response.stream method has been "
|
||||
"deprecated and will be removed in v22.6. Please "
|
||||
"upgrade your application to use the new style "
|
||||
"streaming pattern. See "
|
||||
"https://sanicframework.org/en/guide/advanced/"
|
||||
"streaming.html#response-streaming for more "
|
||||
"information.",
|
||||
22.6,
|
||||
)
|
||||
checks = [node.value.func.id] # type: ignore
|
||||
if node.value.keywords: # type: ignore
|
||||
checks += [
|
||||
@@ -1015,8 +1016,6 @@ class RouteMixin:
|
||||
for check in checks:
|
||||
if check in RESPONSE_MAPPING:
|
||||
types.add(RESPONSE_MAPPING[check])
|
||||
except AttributeError:
|
||||
...
|
||||
|
||||
HttpResponseVisitor().visit(node)
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Dict, Optional, Set, Union
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.models.futures import FutureSignal
|
||||
from sanic.models.handler_types import SignalHandler
|
||||
from sanic.signals import Signal
|
||||
from sanic.types import HashableDict
|
||||
|
||||
|
||||
class SignalMixin:
|
||||
class SignalMixin(metaclass=SanicMeta):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self._future_signals: Set[FutureSignal] = set()
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from mimetypes import guess_type
|
||||
from os import path
|
||||
@@ -12,10 +14,10 @@ from typing import (
|
||||
Iterator,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from urllib.parse import quote_plus
|
||||
from warnings import warn
|
||||
|
||||
from sanic.compat import Header, open_async
|
||||
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
|
||||
@@ -28,6 +30,10 @@ from sanic.models.protocol_types import HTMLProtocol, Range
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic.asgi import ASGIApp
|
||||
from sanic.request import Request
|
||||
else:
|
||||
Request = TypeVar("Request")
|
||||
|
||||
|
||||
try:
|
||||
from ujson import dumps as json_dumps
|
||||
@@ -136,95 +142,6 @@ class BaseHTTPResponse:
|
||||
await self.stream.send(data, end_stream=end_stream)
|
||||
|
||||
|
||||
StreamingFunction = Callable[[BaseHTTPResponse], Coroutine[Any, Any, None]]
|
||||
|
||||
|
||||
class StreamingHTTPResponse(BaseHTTPResponse):
|
||||
"""
|
||||
Old style streaming response where you pass a streaming function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def sample_streaming_fn(response):
|
||||
await response.write("foo")
|
||||
await asyncio.sleep(1)
|
||||
await response.write("bar")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
@app.post("/")
|
||||
async def test(request):
|
||||
return stream(sample_streaming_fn)
|
||||
|
||||
.. warning::
|
||||
|
||||
**Deprecated** and set for removal in v21.12. You can now achieve the
|
||||
same functionality without a callback.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.post("/")
|
||||
async def test(request):
|
||||
response = await request.respond()
|
||||
await response.send("foo", False)
|
||||
await asyncio.sleep(1)
|
||||
await response.send("bar", False)
|
||||
await asyncio.sleep(1)
|
||||
await response.send("", True)
|
||||
return response
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"streaming_fn",
|
||||
"status",
|
||||
"content_type",
|
||||
"headers",
|
||||
"_cookies",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
streaming_fn: StreamingFunction,
|
||||
status: int = 200,
|
||||
headers: Optional[Union[Header, Dict[str, str]]] = None,
|
||||
content_type: str = "text/plain; charset=utf-8",
|
||||
ignore_deprecation_notice: bool = False,
|
||||
):
|
||||
if not ignore_deprecation_notice:
|
||||
warn(
|
||||
"Use of the StreamingHTTPResponse is deprecated in v21.6, and "
|
||||
"will be removed in v21.12. Please upgrade your streaming "
|
||||
"response implementation. You can learn more here: "
|
||||
"https://sanicframework.org/en/guide/advanced/streaming.html"
|
||||
"#response-streaming. If you use the builtin stream() or "
|
||||
"file_stream() methods, this upgrade will be be done for you."
|
||||
)
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.content_type = content_type
|
||||
self.streaming_fn = streaming_fn
|
||||
self.status = status
|
||||
self.headers = Header(headers or {})
|
||||
self._cookies = None
|
||||
|
||||
async def write(self, data):
|
||||
"""Writes a chunk of data to the streaming response.
|
||||
|
||||
:param data: str or bytes-ish data to be written.
|
||||
"""
|
||||
await super().send(self._encode_body(data))
|
||||
|
||||
async def send(self, *args, **kwargs):
|
||||
if self.streaming_fn is not None:
|
||||
await self.streaming_fn(self)
|
||||
self.streaming_fn = None
|
||||
await super().send(*args, **kwargs)
|
||||
|
||||
async def eof(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class HTTPResponse(BaseHTTPResponse):
|
||||
"""
|
||||
HTTP response to be sent back to the client.
|
||||
@@ -419,6 +336,109 @@ async def file(
|
||||
)
|
||||
|
||||
|
||||
def redirect(
|
||||
to: str,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
status: int = 302,
|
||||
content_type: str = "text/html; charset=utf-8",
|
||||
) -> HTTPResponse:
|
||||
"""
|
||||
Abort execution and cause a 302 redirect (by default) by setting a
|
||||
Location header.
|
||||
|
||||
:param to: path or fully qualified URL to redirect to
|
||||
:param headers: optional dict of headers to include in the new request
|
||||
:param status: status code (int) of the new request, defaults to 302
|
||||
:param content_type: the content type (string) of the response
|
||||
"""
|
||||
headers = headers or {}
|
||||
|
||||
# URL Quote the URL before redirecting
|
||||
safe_to = quote_plus(to, safe=":/%#?&=@[]!$&'()*+,;")
|
||||
|
||||
# According to RFC 7231, a relative URI is now permitted.
|
||||
headers["Location"] = safe_to
|
||||
|
||||
return HTTPResponse(
|
||||
status=status, headers=headers, content_type=content_type
|
||||
)
|
||||
|
||||
|
||||
class ResponseStream:
|
||||
"""
|
||||
ResponseStream is a compat layer to bridge the gap after the deprecation
|
||||
of StreamingHTTPResponse. In v22.6 it will be removed when:
|
||||
- stream is removed
|
||||
- file_stream is moved to new style streaming
|
||||
- file and file_stream are combined into a single API
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_cookies",
|
||||
"content_type",
|
||||
"headers",
|
||||
"request",
|
||||
"response",
|
||||
"status",
|
||||
"streaming_fn",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
streaming_fn: Callable[
|
||||
[Union[BaseHTTPResponse, ResponseStream]],
|
||||
Coroutine[Any, Any, None],
|
||||
],
|
||||
status: int = 200,
|
||||
headers: Optional[Union[Header, Dict[str, str]]] = None,
|
||||
content_type: Optional[str] = None,
|
||||
):
|
||||
self.streaming_fn = streaming_fn
|
||||
self.status = status
|
||||
self.headers = headers or Header()
|
||||
self.content_type = content_type
|
||||
self.request: Optional[Request] = None
|
||||
self._cookies: Optional[CookieJar] = None
|
||||
|
||||
async def write(self, message: str):
|
||||
await self.response.send(message)
|
||||
|
||||
async def stream(self) -> HTTPResponse:
|
||||
if not self.request:
|
||||
raise ServerError("Attempted response to unknown request")
|
||||
self.response = await self.request.respond(
|
||||
headers=self.headers,
|
||||
status=self.status,
|
||||
content_type=self.content_type,
|
||||
)
|
||||
await self.streaming_fn(self)
|
||||
return self.response
|
||||
|
||||
async def eof(self) -> None:
|
||||
await self.response.eof()
|
||||
|
||||
@property
|
||||
def cookies(self) -> CookieJar:
|
||||
if self._cookies is None:
|
||||
self._cookies = CookieJar(self.headers)
|
||||
return self._cookies
|
||||
|
||||
@property
|
||||
def processed_headers(self):
|
||||
return self.response.processed_headers
|
||||
|
||||
@property
|
||||
def body(self):
|
||||
return self.response.body
|
||||
|
||||
def __call__(self, request: Request) -> ResponseStream:
|
||||
self.request = request
|
||||
return self
|
||||
|
||||
def __await__(self):
|
||||
return self.stream().__await__()
|
||||
|
||||
|
||||
async def file_stream(
|
||||
location: Union[str, PurePath],
|
||||
status: int = 200,
|
||||
@@ -427,7 +447,7 @@ async def file_stream(
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
filename: Optional[str] = None,
|
||||
_range: Optional[Range] = None,
|
||||
) -> StreamingHTTPResponse:
|
||||
) -> ResponseStream:
|
||||
"""Return a streaming response object with file data.
|
||||
|
||||
:param location: Location of file on system.
|
||||
@@ -435,7 +455,6 @@ async def file_stream(
|
||||
:param mime_type: Specific mime_type.
|
||||
:param headers: Custom Headers.
|
||||
:param filename: Override filename.
|
||||
:param chunked: Deprecated
|
||||
:param _range:
|
||||
"""
|
||||
headers = headers or {}
|
||||
@@ -471,23 +490,24 @@ async def file_stream(
|
||||
break
|
||||
await response.write(content)
|
||||
|
||||
return StreamingHTTPResponse(
|
||||
return ResponseStream(
|
||||
streaming_fn=_streaming_fn,
|
||||
status=status,
|
||||
headers=headers,
|
||||
content_type=mime_type,
|
||||
ignore_deprecation_notice=True,
|
||||
)
|
||||
|
||||
|
||||
def stream(
|
||||
streaming_fn: StreamingFunction,
|
||||
streaming_fn: Callable[
|
||||
[Union[BaseHTTPResponse, ResponseStream]], Coroutine[Any, Any, None]
|
||||
],
|
||||
status: int = 200,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
content_type: str = "text/plain; charset=utf-8",
|
||||
):
|
||||
"""Accepts an coroutine `streaming_fn` which can be used to
|
||||
write chunks to a streaming response. Returns a `StreamingHTTPResponse`.
|
||||
) -> ResponseStream:
|
||||
"""Accepts a coroutine `streaming_fn` which can be used to
|
||||
write chunks to a streaming response. Returns a `ResponseStream`.
|
||||
|
||||
Example usage::
|
||||
|
||||
@@ -501,42 +521,13 @@ def stream(
|
||||
|
||||
:param streaming_fn: A coroutine accepts a response and
|
||||
writes content to that response.
|
||||
:param mime_type: Specific mime_type.
|
||||
:param status: HTTP status.
|
||||
:param content_type: Specific content_type.
|
||||
:param headers: Custom Headers.
|
||||
:param chunked: Deprecated
|
||||
"""
|
||||
return StreamingHTTPResponse(
|
||||
return ResponseStream(
|
||||
streaming_fn,
|
||||
headers=headers,
|
||||
content_type=content_type,
|
||||
status=status,
|
||||
ignore_deprecation_notice=True,
|
||||
)
|
||||
|
||||
|
||||
def redirect(
|
||||
to: str,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
status: int = 302,
|
||||
content_type: str = "text/html; charset=utf-8",
|
||||
) -> HTTPResponse:
|
||||
"""
|
||||
Abort execution and cause a 302 redirect (by default) by setting a
|
||||
Location header.
|
||||
|
||||
:param to: path or fully qualified URL to redirect to
|
||||
:param headers: optional dict of headers to include in the new request
|
||||
:param status: status code (int) of the new request, defaults to 302
|
||||
:param content_type: the content type (string) of the response
|
||||
"""
|
||||
headers = headers or {}
|
||||
|
||||
# URL Quote the URL before redirecting
|
||||
safe_to = quote_plus(to, safe=":/%#?&=@[]!$&'()*+,;")
|
||||
|
||||
# According to RFC 7231, a relative URI is now permitted.
|
||||
headers["Location"] = safe_to
|
||||
|
||||
return HTTPResponse(
|
||||
status=status, headers=headers, content_type=content_type
|
||||
)
|
||||
|
||||
@@ -3,9 +3,9 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from warnings import warn
|
||||
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.log import deprecation
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -37,10 +37,10 @@ class AsyncioServer:
|
||||
|
||||
@property
|
||||
def init(self):
|
||||
warn(
|
||||
deprecation(
|
||||
"AsyncioServer.init has been deprecated and will be removed "
|
||||
"in v22.6. Use Sanic.state.is_started instead.",
|
||||
DeprecationWarning,
|
||||
22.6,
|
||||
)
|
||||
return self.app.state.is_started
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from typing import TYPE_CHECKING, Optional, Sequence, cast
|
||||
from warnings import warn
|
||||
|
||||
from websockets.connection import CLOSED, CLOSING, OPEN
|
||||
from websockets.server import ServerConnection
|
||||
from websockets.typing import Subprotocol
|
||||
|
||||
from sanic.exceptions import ServerError
|
||||
from sanic.log import error_logger
|
||||
from sanic.log import deprecation, error_logger
|
||||
from sanic.server import HttpProtocol
|
||||
|
||||
from ..websockets.impl import WebsocketImplProtocol
|
||||
@@ -17,6 +16,14 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class WebSocketProtocol(HttpProtocol):
|
||||
__slots__ = (
|
||||
"websocket",
|
||||
"websocket_timeout",
|
||||
"websocket_max_size",
|
||||
"websocket_ping_interval",
|
||||
"websocket_ping_timeout",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
@@ -35,24 +42,24 @@ class WebSocketProtocol(HttpProtocol):
|
||||
self.websocket_max_size = websocket_max_size
|
||||
if websocket_max_queue is not None and websocket_max_queue > 0:
|
||||
# TODO: Reminder remove this warning in v22.3
|
||||
warn(
|
||||
deprecation(
|
||||
"Websocket no longer uses queueing, so websocket_max_queue"
|
||||
" is no longer required.",
|
||||
DeprecationWarning,
|
||||
22.3,
|
||||
)
|
||||
if websocket_read_limit is not None and websocket_read_limit > 0:
|
||||
# TODO: Reminder remove this warning in v22.3
|
||||
warn(
|
||||
deprecation(
|
||||
"Websocket no longer uses read buffers, so "
|
||||
"websocket_read_limit is not required.",
|
||||
DeprecationWarning,
|
||||
22.3,
|
||||
)
|
||||
if websocket_write_limit is not None and websocket_write_limit > 0:
|
||||
# TODO: Reminder remove this warning in v22.3
|
||||
warn(
|
||||
deprecation(
|
||||
"Websocket no longer uses write buffers, so "
|
||||
"websocket_write_limit is not required.",
|
||||
DeprecationWarning,
|
||||
22.3,
|
||||
)
|
||||
self.websocket_ping_interval = websocket_ping_interval
|
||||
self.websocket_ping_timeout = websocket_ping_timeout
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.exceptions import SanicException
|
||||
|
||||
from .service import TouchUp
|
||||
|
||||
|
||||
class TouchUpMeta(type):
|
||||
class TouchUpMeta(SanicMeta):
|
||||
def __new__(cls, name, bases, attrs, **kwargs):
|
||||
gen_class = super().__new__(cls, name, bases, attrs, **kwargs)
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ from typing import (
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
from warnings import warn
|
||||
|
||||
from sanic.constants import HTTP_METHODS
|
||||
from sanic.exceptions import InvalidUsage
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
|
||||
|
||||
@@ -136,48 +133,3 @@ class HTTPMethodView:
|
||||
def stream(func):
|
||||
func.is_stream = True
|
||||
return func
|
||||
|
||||
|
||||
class CompositionView:
|
||||
"""Simple method-function mapped view for the sanic.
|
||||
You can add handler functions to methods (get, post, put, patch, delete)
|
||||
for every HTTP method you want to support.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
view = CompositionView()
|
||||
view.add(['GET'], lambda request: text('I am get method'))
|
||||
view.add(['POST', 'PUT'], lambda request: text('I am post/put method'))
|
||||
|
||||
If someone tries to use a non-implemented method, there will be a
|
||||
405 response.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = {}
|
||||
self.name = self.__class__.__name__
|
||||
warn(
|
||||
"CompositionView has been deprecated and will be removed in "
|
||||
"v21.12. Please update your view to HTTPMethodView.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
def __name__(self):
|
||||
return self.name
|
||||
|
||||
def add(self, methods, handler, stream=False):
|
||||
if stream:
|
||||
handler.is_stream = stream
|
||||
for method in methods:
|
||||
if method not in HTTP_METHODS:
|
||||
raise InvalidUsage(f"{method} is not a valid HTTP method.")
|
||||
|
||||
if method in self.handlers:
|
||||
raise InvalidUsage(f"Method {method} is already registered.")
|
||||
self.handlers[method] = handler
|
||||
|
||||
def __call__(self, request, *args, **kwargs):
|
||||
handler = self.handlers[request.method.upper()]
|
||||
return handler(request, *args, **kwargs)
|
||||
|
||||
Reference in New Issue
Block a user