LTS v21.12 Deprecations (#2306)

Co-authored-by: Néstor Pérez <25409753+prryplatypus@users.noreply.github.com>
This commit is contained in:
Adam Hopkins
2021-12-24 00:30:27 +02:00
committed by GitHub
parent 98ce4bdeb2
commit 8c07e388cd
72 changed files with 638 additions and 847 deletions

View File

@@ -1 +1 @@
__version__ = "21.12.0dev"
__version__ = "21.12.0"

View File

@@ -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
View File

6
sanic/base/meta.py Normal file
View 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

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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] = []

View File

@@ -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] = []

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)