Release 22.6 (#2487)
This commit is contained in:
parent
aba333bfb6
commit
13d5a44278
|
@ -313,8 +313,10 @@ Version 21.3.0
|
|||
`#2074 <https://github.com/sanic-org/sanic/pull/2074>`_
|
||||
Performance adjustments in ``handle_request_``
|
||||
|
||||
Version 20.12.3
|
||||
---------------
|
||||
Version 20.12.3 🔷
|
||||
------------------
|
||||
|
||||
`Current LTS version`
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
|
@ -348,8 +350,8 @@ Version 19.12.5
|
|||
`#2027 <https://github.com/sanic-org/sanic/pull/2027>`_
|
||||
Remove old chardet requirement, add in hard multidict requirement
|
||||
|
||||
Version 20.12.0
|
||||
---------------
|
||||
Version 20.12.0 🔹
|
||||
-----------------
|
||||
|
||||
**Features**
|
||||
|
||||
|
@ -357,8 +359,8 @@ Version 20.12.0
|
|||
`#1993 <https://github.com/sanic-org/sanic/pull/1993>`_
|
||||
Add disable app registry
|
||||
|
||||
Version 20.12.0
|
||||
---------------
|
||||
Version 20.12.0 🔹
|
||||
-----------------
|
||||
|
||||
**Features**
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ API
|
|||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 3
|
||||
|
||||
👥 User Guide <https://sanicframework.org/guide/>
|
||||
sanic/api_reference
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
📜 Changelog
|
||||
============
|
||||
|
||||
.. mdinclude:: ./releases/22/22.6.md
|
||||
.. mdinclude:: ./releases/22/22.3.md
|
||||
.. mdinclude:: ./releases/21/21.12.md
|
||||
.. mdinclude:: ./releases/21/21.9.md
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
## Version 21.12.1
|
||||
## Version 21.12.1 🔷
|
||||
|
||||
_Current LTS version_
|
||||
|
||||
- [#2349](https://github.com/sanic-org/sanic/pull/2349) Only display MOTD on startup
|
||||
- [#2354](https://github.com/sanic-org/sanic/pull/2354) Ignore name argument in Python 3.7
|
||||
- [#2355](https://github.com/sanic-org/sanic/pull/2355) Add config.update support for all config values
|
||||
|
||||
## Version 21.12.0
|
||||
## Version 21.12.0 🔹
|
||||
|
||||
### Features
|
||||
- [#2260](https://github.com/sanic-org/sanic/pull/2260) Allow early Blueprint registrations to still apply later added objects
|
||||
|
|
42
docs/sanic/releases/22/22.6.md
Normal file
42
docs/sanic/releases/22/22.6.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
## Version 22.6.0 🔶
|
||||
|
||||
_Current version_
|
||||
|
||||
### Features
|
||||
- [#2378](https://github.com/sanic-org/sanic/pull/2378) Introduce HTTP/3 and autogeneration of TLS certificates in `DEBUG` mode
|
||||
- 👶 *EARLY RELEASE FEATURE*: Serving Sanic over HTTP/3 is an early release feature. It does not yet fully cover the HTTP/3 spec, but instead aims for feature parity with Sanic's existing HTTP/1.1 server. Websockets, WebTransport, push responses are examples of some features not yet implemented.
|
||||
- 📦 *EXTRA REQUIREMENT*: Not all HTTP clients are capable of interfacing with HTTP/3 servers. You may need to install a [HTTP/3 capable client](https://curl.se/docs/http3.html).
|
||||
- 📦 *EXTRA REQUIREMENT*: In order to use TLS autogeneration, you must install either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme).
|
||||
- [#2416](https://github.com/sanic-org/sanic/pull/2416) Add message to `task.cancel`
|
||||
- [#2420](https://github.com/sanic-org/sanic/pull/2420) Add exception aliases for more consistent naming with standard HTTP response types (`BadRequest`, `MethodNotAllowed`, `RangeNotSatisfiable`)
|
||||
- [#2432](https://github.com/sanic-org/sanic/pull/2432) Expose ASGI `scope` as a property on the `Request` object
|
||||
- [#2438](https://github.com/sanic-org/sanic/pull/2438) Easier access to websocket class for annotation: `from sanic import Websocket`
|
||||
- [#2439](https://github.com/sanic-org/sanic/pull/2439) New API for reading form values with options: `Request.get_form`
|
||||
- [#2447](https://github.com/sanic-org/sanic/pull/2447), [#2486](https://github.com/sanic-org/sanic/pull/2486) Improved API to support setting cache control headers
|
||||
- [#2453](https://github.com/sanic-org/sanic/pull/2453) Move verbosity filtering to logger
|
||||
- [#2475](https://github.com/sanic-org/sanic/pull/2475) Expose getter for current request using `Request.get_current()`
|
||||
|
||||
### Bugfixes
|
||||
- [#2448](https://github.com/sanic-org/sanic/pull/2448) Fix to allow running with `pythonw.exe` or places where there is no `sys.stdout`
|
||||
- [#2451](https://github.com/sanic-org/sanic/pull/2451) Trigger `http.lifecycle.request` signal in ASGI mode
|
||||
- [#2455](https://github.com/sanic-org/sanic/pull/2455) Resolve typing of stacked route definitions
|
||||
- [#2463](https://github.com/sanic-org/sanic/pull/2463) Properly catch websocket CancelledError in websocket handler in Python 3.7
|
||||
|
||||
### Deprecations and Removals
|
||||
- [#2487](https://github.com/sanic-org/sanic/pull/2487) v22.6 deprecations and changes
|
||||
1. Optional application registry
|
||||
1. Execution of custom handlers after some part of response was sent
|
||||
1. Configuring fallback handlers on the `ErrorHandler`
|
||||
1. Custom `LOGO` setting
|
||||
1. `sanic.response.stream`
|
||||
1. `AsyncioServer.init`
|
||||
|
||||
### Developer infrastructure
|
||||
- [#2449](https://github.com/sanic-org/sanic/pull/2449) Clean up `black` and `isort` config
|
||||
- [#2479](https://github.com/sanic-org/sanic/pull/2479) Fix some flappy tests
|
||||
|
||||
### Improved Documentation
|
||||
- [#2461](https://github.com/sanic-org/sanic/pull/2461) Update example to match current application naming standards
|
||||
- [#2466](https://github.com/sanic-org/sanic/pull/2466) Better type annotation for `Extend`
|
||||
- [#2485](https://github.com/sanic-org/sanic/pull/2485) Improved help messages in CLI
|
||||
|
|
@ -1 +1 @@
|
|||
__version__ = "22.3.2"
|
||||
__version__ = "22.6.0"
|
||||
|
|
40
sanic/app.py
40
sanic/app.py
|
@ -169,7 +169,6 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
|||
strict_slashes: bool = False,
|
||||
log_config: Optional[Dict[str, Any]] = None,
|
||||
configure_logging: bool = True,
|
||||
register: Optional[bool] = None,
|
||||
dumps: Optional[Callable[..., AnyStr]] = None,
|
||||
) -> None:
|
||||
super().__init__(name=name)
|
||||
|
@ -218,20 +217,9 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
|||
|
||||
# Register alternative method names
|
||||
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)
|
||||
|
||||
self.router.ctx.app = self
|
||||
self.signal_router.ctx.app = self
|
||||
self.__class__.register_app(self)
|
||||
|
||||
if dumps:
|
||||
BaseHTTPResponse._dumps = dumps # type: ignore
|
||||
|
@ -736,37 +724,24 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
|||
"has at least partially been sent."
|
||||
)
|
||||
|
||||
# ----------------- deprecated -----------------
|
||||
handler = self.error_handler._lookup(
|
||||
exception, request.name if request else None
|
||||
)
|
||||
if handler:
|
||||
deprecation(
|
||||
logger.warning(
|
||||
"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 "
|
||||
f"handler {handler.__name__} will not be sent to the "
|
||||
"client. Beginning in v22.6, Sanic will stop executing "
|
||||
"custom exception handlers in this scenario. Exception "
|
||||
"handlers should only be used to generate the exception "
|
||||
"responses. If you would like to perform any other "
|
||||
"action on a raised exception, please consider using a "
|
||||
"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",
|
||||
22.6,
|
||||
)
|
||||
try:
|
||||
response = self.error_handler.response(request, exception)
|
||||
if isawaitable(response):
|
||||
response = await response
|
||||
except BaseException as e:
|
||||
logger.error("An error occurred in the exception handler.")
|
||||
error_logger.exception(e)
|
||||
# ----------------------------------------------
|
||||
|
||||
return
|
||||
|
||||
# -------------------------------------------- #
|
||||
|
@ -1559,7 +1534,6 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
|||
if self.state.primary:
|
||||
# TODO:
|
||||
# - Raise warning if secondary apps have error handler config
|
||||
ErrorHandler.finalize(self.error_handler, config=self.config)
|
||||
if self.config.TOUCHUP:
|
||||
TouchUp.run(self)
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ DEFAULT_CONFIG = {
|
|||
"NOISY_EXCEPTIONS": False,
|
||||
"PROXIES_COUNT": None,
|
||||
"REAL_IP_HEADER": None,
|
||||
"REGISTER": True,
|
||||
"REQUEST_BUFFER_SIZE": 65536, # 64 KiB
|
||||
"REQUEST_MAX_HEADER_SIZE": 8192, # 8 KiB, but cannot exceed 16384
|
||||
"REQUEST_ID_HEADER": "X-Request-ID",
|
||||
|
@ -84,7 +83,6 @@ class Config(dict, metaclass=DescriptorMeta):
|
|||
NOISY_EXCEPTIONS: bool
|
||||
PROXIES_COUNT: Optional[int]
|
||||
REAL_IP_HEADER: Optional[str]
|
||||
REGISTER: bool
|
||||
REQUEST_BUFFER_SIZE: int
|
||||
REQUEST_MAX_HEADER_SIZE: int
|
||||
REQUEST_ID_HEADER: str
|
||||
|
@ -111,7 +109,6 @@ class Config(dict, metaclass=DescriptorMeta):
|
|||
super().__init__({**DEFAULT_CONFIG, **defaults})
|
||||
|
||||
self._converters = [str, str_to_bool, float, int]
|
||||
self._LOGO = ""
|
||||
|
||||
if converters:
|
||||
for converter in converters:
|
||||
|
@ -168,24 +165,14 @@ class Config(dict, metaclass=DescriptorMeta):
|
|||
"REQUEST_MAX_SIZE",
|
||||
):
|
||||
self._configure_header_size()
|
||||
if attr == "LOGO":
|
||||
self._LOGO = value
|
||||
deprecation(
|
||||
"Setting the config.LOGO is deprecated and will no longer "
|
||||
"be supported starting in v22.6.",
|
||||
22.6,
|
||||
)
|
||||
elif attr == "LOCAL_CERT_CREATOR" and not isinstance(
|
||||
|
||||
if attr == "LOCAL_CERT_CREATOR" and not isinstance(
|
||||
self.LOCAL_CERT_CREATOR, LocalCertCreator
|
||||
):
|
||||
self.LOCAL_CERT_CREATOR = LocalCertCreator[
|
||||
self.LOCAL_CERT_CREATOR.upper()
|
||||
]
|
||||
|
||||
@property
|
||||
def LOGO(self):
|
||||
return self._LOGO
|
||||
|
||||
@property
|
||||
def FALLBACK_ERROR_FORMAT(self) -> str:
|
||||
if self._FALLBACK_ERROR_FORMAT is _default:
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional, Tuple, Type, Union
|
||||
from typing import Dict, List, Optional, Tuple, Type
|
||||
|
||||
from sanic.config import Config
|
||||
from sanic.errorpages import (
|
||||
DEFAULT_FORMAT,
|
||||
BaseRenderer,
|
||||
TextRenderer,
|
||||
exception_response,
|
||||
)
|
||||
from sanic.errorpages import BaseRenderer, TextRenderer, exception_response
|
||||
from sanic.exceptions import (
|
||||
HeaderNotFound,
|
||||
InvalidRangeType,
|
||||
RangeNotSatisfiable,
|
||||
SanicException,
|
||||
)
|
||||
from sanic.helpers import Default, _default
|
||||
from sanic.log import deprecation, error_logger
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
from sanic.response import text
|
||||
|
@ -36,89 +28,20 @@ class ErrorHandler:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
fallback: Union[str, Default] = _default,
|
||||
base: Type[BaseRenderer] = TextRenderer,
|
||||
):
|
||||
self.cached_handlers: Dict[
|
||||
Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler]
|
||||
] = {}
|
||||
self.debug = False
|
||||
self._fallback = fallback
|
||||
self.base = base
|
||||
|
||||
if fallback is not _default:
|
||||
self._warn_fallback_deprecation()
|
||||
|
||||
@property
|
||||
def fallback(self): # no cov
|
||||
# This is for backwards compat and can be removed in v22.6
|
||||
if self._fallback is _default:
|
||||
return DEFAULT_FORMAT
|
||||
return self._fallback
|
||||
|
||||
@fallback.setter
|
||||
def fallback(self, value: str): # no cov
|
||||
self._warn_fallback_deprecation()
|
||||
if not isinstance(value, str):
|
||||
raise SanicException(
|
||||
f"Cannot set error handler fallback to: value={value}"
|
||||
)
|
||||
self._fallback = value
|
||||
|
||||
@staticmethod
|
||||
def _warn_fallback_deprecation():
|
||||
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.",
|
||||
22.6,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_fallback_value(cls, error_handler: ErrorHandler, config: Config):
|
||||
if error_handler._fallback is not _default:
|
||||
if config._FALLBACK_ERROR_FORMAT == error_handler._fallback:
|
||||
return error_handler.fallback
|
||||
|
||||
error_logger.warning(
|
||||
"Conflicting error fallback values were found in the "
|
||||
"error handler and in the app.config while handling an "
|
||||
"exception. Using the value from app.config."
|
||||
)
|
||||
return config.FALLBACK_ERROR_FORMAT
|
||||
|
||||
@classmethod
|
||||
def finalize(
|
||||
cls,
|
||||
error_handler: ErrorHandler,
|
||||
config: Config,
|
||||
fallback: Optional[str] = None,
|
||||
):
|
||||
if fallback:
|
||||
def finalize(cls, *args, **kwargs):
|
||||
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.",
|
||||
22.6,
|
||||
)
|
||||
|
||||
if not fallback:
|
||||
fallback = config.FALLBACK_ERROR_FORMAT
|
||||
|
||||
if fallback != DEFAULT_FORMAT:
|
||||
if error_handler._fallback is not _default:
|
||||
error_logger.warning(
|
||||
f"Setting the fallback value to {fallback}. This changes "
|
||||
"the current non-default value "
|
||||
f"'{error_handler._fallback}'."
|
||||
)
|
||||
error_handler._fallback = fallback
|
||||
|
||||
if not isinstance(error_handler, cls):
|
||||
error_logger.warning(
|
||||
f"Error handler is non-conforming: {type(error_handler)}"
|
||||
"ErrorHandler.finalize is deprecated and no longer needed. "
|
||||
"Please remove update your code to remove it. ",
|
||||
22.12,
|
||||
)
|
||||
|
||||
def _full_lookup(self, exception, route_name: Optional[str] = None):
|
||||
|
@ -237,7 +160,7 @@ class ErrorHandler:
|
|||
:return:
|
||||
"""
|
||||
self.log(request, exception)
|
||||
fallback = ErrorHandler._get_fallback_value(self, request.app.config)
|
||||
fallback = request.app.config.FALLBACK_ERROR_FORMAT
|
||||
return exception_response(
|
||||
request,
|
||||
exception,
|
||||
|
|
|
@ -16,18 +16,6 @@ from typing import (
|
|||
cast,
|
||||
)
|
||||
|
||||
from aioquic.h0.connection import H0_ALPN, H0Connection
|
||||
from aioquic.h3.connection import H3_ALPN, H3Connection
|
||||
from aioquic.h3.events import (
|
||||
DatagramReceived,
|
||||
DataReceived,
|
||||
H3Event,
|
||||
HeadersReceived,
|
||||
WebTransportStreamDataReceived,
|
||||
)
|
||||
from aioquic.quic.configuration import QuicConfiguration
|
||||
from aioquic.tls import SessionTicket
|
||||
|
||||
from sanic.compat import Header
|
||||
from sanic.constants import LocalCertCreator
|
||||
from sanic.exceptions import PayloadTooLarge, SanicException, ServerError
|
||||
|
@ -40,13 +28,29 @@ from sanic.models.protocol_types import TransportProtocol
|
|||
from sanic.models.server_types import ConnInfo
|
||||
|
||||
|
||||
try:
|
||||
from aioquic.h0.connection import H0_ALPN, H0Connection
|
||||
from aioquic.h3.connection import H3_ALPN, H3Connection
|
||||
from aioquic.h3.events import (
|
||||
DatagramReceived,
|
||||
DataReceived,
|
||||
H3Event,
|
||||
HeadersReceived,
|
||||
WebTransportStreamDataReceived,
|
||||
)
|
||||
from aioquic.quic.configuration import QuicConfiguration
|
||||
from aioquic.tls import SessionTicket
|
||||
|
||||
HTTP3_AVAILABLE = True
|
||||
except ModuleNotFoundError: # no cov
|
||||
HTTP3_AVAILABLE = False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic
|
||||
from sanic.request import Request
|
||||
from sanic.response import BaseHTTPResponse
|
||||
from sanic.server.protocols.http_protocol import Http3Protocol
|
||||
|
||||
|
||||
HttpConnection = Union[H0Connection, H3Connection]
|
||||
|
||||
|
||||
|
@ -269,6 +273,7 @@ class Http3:
|
|||
Internal helper for managing the HTTP/3 request/response cycle
|
||||
"""
|
||||
|
||||
if HTTP3_AVAILABLE:
|
||||
HANDLER_PROPERTY_MAPPING = {
|
||||
DataReceived: "stream_id",
|
||||
HeadersReceived: "stream_id",
|
||||
|
|
|
@ -34,7 +34,7 @@ from sanic.exceptions import (
|
|||
RangeNotSatisfiable,
|
||||
)
|
||||
from sanic.handlers import ContentRangeHandler
|
||||
from sanic.log import deprecation, error_logger
|
||||
from sanic.log import error_logger
|
||||
from sanic.models.futures import FutureRoute, FutureStatic
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
from sanic.response import HTTPResponse, file, file_stream
|
||||
|
@ -1025,17 +1025,6 @@ class RouteMixin(metaclass=SanicMeta):
|
|||
nonlocal types
|
||||
|
||||
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 += [
|
||||
|
@ -1066,7 +1055,7 @@ class RouteMixin(metaclass=SanicMeta):
|
|||
raise AttributeError(
|
||||
"Cannot use restricted route context: "
|
||||
f"{restricted_arguments}. This limitation is only in place "
|
||||
"until v22.3 when the restricted names will no longer be in"
|
||||
"until v22.9 when the restricted names will no longer be in"
|
||||
"conflict. See https://github.com/sanic-org/sanic/issues/2303 "
|
||||
"for more information."
|
||||
)
|
||||
|
|
|
@ -565,11 +565,7 @@ class RunnerMixin(metaclass=SanicMeta):
|
|||
if self.config.MOTD_DISPLAY:
|
||||
extra.update(self.config.MOTD_DISPLAY)
|
||||
|
||||
logo = (
|
||||
get_logo(coffee=self.state.coffee)
|
||||
if self.config.LOGO == "" or self.config.LOGO is True
|
||||
else self.config.LOGO
|
||||
)
|
||||
logo = get_logo(coffee=self.state.coffee)
|
||||
|
||||
MOTD.output(logo, serve_location, display, extra)
|
||||
|
||||
|
|
|
@ -427,8 +427,7 @@ def redirect(
|
|||
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
|
||||
of StreamingHTTPResponse. It will be removed when:
|
||||
- file_stream is moved to new style streaming
|
||||
- file and file_stream are combined into a single API
|
||||
"""
|
||||
|
@ -556,38 +555,3 @@ async def file_stream(
|
|||
headers=headers,
|
||||
content_type=mime_type,
|
||||
)
|
||||
|
||||
|
||||
def stream(
|
||||
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",
|
||||
) -> ResponseStream:
|
||||
"""Accepts a coroutine `streaming_fn` which can be used to
|
||||
write chunks to a streaming response. Returns a `ResponseStream`.
|
||||
|
||||
Example usage::
|
||||
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
async def streaming_fn(response):
|
||||
await response.write('foo')
|
||||
await response.write('bar')
|
||||
|
||||
return stream(streaming_fn, content_type='text/plain')
|
||||
|
||||
:param streaming_fn: A coroutine accepts a response and
|
||||
writes content to that response.
|
||||
:param status: HTTP status.
|
||||
:param content_type: Specific content_type.
|
||||
:param headers: Custom Headers.
|
||||
"""
|
||||
return ResponseStream(
|
||||
streaming_fn,
|
||||
headers=headers,
|
||||
content_type=content_type,
|
||||
status=status,
|
||||
)
|
||||
|
|
|
@ -5,7 +5,6 @@ import asyncio
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.log import deprecation
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -35,15 +34,6 @@ class AsyncioServer:
|
|||
self.serve_coro = serve_coro
|
||||
self.server = None
|
||||
|
||||
@property
|
||||
def init(self):
|
||||
deprecation(
|
||||
"AsyncioServer.init has been deprecated and will be removed "
|
||||
"in v22.6. Use Sanic.state.is_started instead.",
|
||||
22.6,
|
||||
)
|
||||
return self.app.state.is_started
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Trigger "before_server_start" events
|
||||
|
|
|
@ -2,8 +2,6 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from aioquic.h3.connection import H3_ALPN, H3Connection
|
||||
|
||||
from sanic.http.constants import HTTP
|
||||
from sanic.http.http3 import Http3
|
||||
from sanic.touchup.meta import TouchUpMeta
|
||||
|
@ -17,13 +15,6 @@ import sys
|
|||
from asyncio import CancelledError
|
||||
from time import monotonic as current_time
|
||||
|
||||
from aioquic.asyncio import QuicConnectionProtocol
|
||||
from aioquic.quic.events import (
|
||||
DatagramFrameReceived,
|
||||
ProtocolNegotiated,
|
||||
QuicEvent,
|
||||
)
|
||||
|
||||
from sanic.exceptions import RequestTimeout, ServiceUnavailable
|
||||
from sanic.http import Http, Stage
|
||||
from sanic.log import Colors, error_logger, logger
|
||||
|
@ -32,6 +23,21 @@ from sanic.request import Request
|
|||
from sanic.server.protocols.base_protocol import SanicProtocol
|
||||
|
||||
|
||||
ConnectionProtocol = type("ConnectionProtocol", (), {})
|
||||
try:
|
||||
from aioquic.asyncio import QuicConnectionProtocol
|
||||
from aioquic.h3.connection import H3_ALPN, H3Connection
|
||||
from aioquic.quic.events import (
|
||||
DatagramFrameReceived,
|
||||
ProtocolNegotiated,
|
||||
QuicEvent,
|
||||
)
|
||||
|
||||
ConnectionProtocol = QuicConnectionProtocol
|
||||
except ModuleNotFoundError: # no cov
|
||||
...
|
||||
|
||||
|
||||
class HttpProtocolMixin:
|
||||
__slots__ = ()
|
||||
__version__: HTTP
|
||||
|
@ -278,7 +284,7 @@ class HttpProtocol(HttpProtocolMixin, SanicProtocol, metaclass=TouchUpMeta):
|
|||
error_logger.exception("protocol.data_received")
|
||||
|
||||
|
||||
class Http3Protocol(HttpProtocolMixin, QuicConnectionProtocol):
|
||||
class Http3Protocol(HttpProtocolMixin, ConnectionProtocol): # type: ignore
|
||||
HTTP_CLASS = Http3
|
||||
__version__ = HTTP.VERSION_3
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from ssl import SSLContext
|
|||
from typing import TYPE_CHECKING, Dict, Optional, Type, Union
|
||||
|
||||
from sanic.config import Config
|
||||
from sanic.exceptions import ServerError
|
||||
from sanic.http.constants import HTTP
|
||||
from sanic.http.tls import get_ssl_context
|
||||
from sanic.server.events import trigger_events
|
||||
|
@ -23,8 +24,6 @@ from functools import partial
|
|||
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
||||
from signal import signal as signal_func
|
||||
|
||||
from aioquic.asyncio import serve as quic_serve
|
||||
|
||||
from sanic.application.ext import setup_ext
|
||||
from sanic.compat import OS_IS_WINDOWS, ctrlc_workaround_for_windows
|
||||
from sanic.http.http3 import SessionTicketStore, get_config
|
||||
|
@ -39,6 +38,14 @@ from sanic.server.socket import (
|
|||
)
|
||||
|
||||
|
||||
try:
|
||||
from aioquic.asyncio import serve as quic_serve
|
||||
|
||||
HTTP3_AVAILABLE = True
|
||||
except ModuleNotFoundError: # no cov
|
||||
HTTP3_AVAILABLE = False
|
||||
|
||||
|
||||
def serve(
|
||||
host,
|
||||
port,
|
||||
|
@ -273,6 +280,10 @@ def _serve_http_3(
|
|||
register_sys_signals: bool = True,
|
||||
run_multiple: bool = False,
|
||||
):
|
||||
if not HTTP3_AVAILABLE:
|
||||
raise ServerError(
|
||||
"Cannot run HTTP/3 server without aioquic installed. "
|
||||
)
|
||||
protocol = partial(Http3Protocol, app=app)
|
||||
ticket_store = SessionTicketStore()
|
||||
ssl_context = get_ssl_context(app, ssl)
|
||||
|
|
|
@ -4,7 +4,6 @@ import re
|
|||
|
||||
from collections import Counter
|
||||
from inspect import isawaitable
|
||||
from os import environ
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
@ -113,19 +112,6 @@ def test_create_server_main_convenience(app, caplog):
|
|||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_create_server_init(app, caplog):
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio_srv_coro = app.create_server(return_asyncio_server=True)
|
||||
server = loop.run_until_complete(asyncio_srv_coro)
|
||||
|
||||
message = (
|
||||
"AsyncioServer.init has been deprecated and will be removed in v22.6. "
|
||||
"Use Sanic.state.is_started instead."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=message):
|
||||
server.init
|
||||
|
||||
|
||||
def test_app_loop_not_running(app):
|
||||
with pytest.raises(SanicException) as excinfo:
|
||||
app.loop
|
||||
|
@ -385,40 +371,6 @@ def test_get_app_default_ambiguous():
|
|||
Sanic.get_app()
|
||||
|
||||
|
||||
def test_app_no_registry():
|
||||
Sanic("no-register", register=False)
|
||||
with pytest.raises(
|
||||
SanicException, match='Sanic app name "no-register" not found.'
|
||||
):
|
||||
Sanic.get_app("no-register")
|
||||
|
||||
|
||||
def test_app_no_registry_deprecation_message():
|
||||
with pytest.warns(DeprecationWarning) as records:
|
||||
Sanic("no-register", register=False)
|
||||
Sanic("yes-register", register=True)
|
||||
|
||||
message = (
|
||||
"[DEPRECATION v22.6] 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."
|
||||
)
|
||||
|
||||
assert len(records) == 2
|
||||
for record in records:
|
||||
assert record.message.args[0] == message
|
||||
|
||||
|
||||
def test_app_no_registry_env():
|
||||
environ["SANIC_REGISTER"] = "False"
|
||||
Sanic("no-register")
|
||||
with pytest.raises(
|
||||
SanicException, match='Sanic app name "no-register" not found.'
|
||||
):
|
||||
Sanic.get_app("no-register")
|
||||
del environ["SANIC_REGISTER"]
|
||||
|
||||
|
||||
def test_app_set_attribute_warning(app):
|
||||
message = (
|
||||
"Setting variables on Sanic instances is not allowed. You should "
|
||||
|
|
|
@ -371,15 +371,6 @@ def test_update_from_lowercase_key(app: Sanic):
|
|||
assert "test_setting_value" not in app.config
|
||||
|
||||
|
||||
def test_deprecation_notice_when_setting_logo(app: Sanic):
|
||||
message = (
|
||||
"Setting the config.LOGO is deprecated and will no longer be "
|
||||
"supported starting in v22.6."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=message):
|
||||
app.config.LOGO = "My Custom Logo"
|
||||
|
||||
|
||||
def test_config_set_methods(app: Sanic, monkeypatch: MonkeyPatch):
|
||||
post_set = Mock()
|
||||
monkeypatch.setattr(Config, "_post_set", post_set)
|
||||
|
|
|
@ -13,13 +13,7 @@ from sanic import Sanic, handlers
|
|||
from sanic.exceptions import BadRequest, Forbidden, NotFound, ServerError
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic.request import Request
|
||||
from sanic.response import stream, text
|
||||
|
||||
|
||||
async def sample_streaming_fn(response):
|
||||
await response.write("foo,")
|
||||
await asyncio.sleep(0.001)
|
||||
await response.write("bar")
|
||||
from sanic.response import text
|
||||
|
||||
|
||||
class ErrorWithRequestCtx(ServerError):
|
||||
|
@ -81,10 +75,10 @@ def exception_handler_app():
|
|||
|
||||
@exception_handler_app.exception(Forbidden)
|
||||
async def async_handler_exception(request, exception):
|
||||
return stream(
|
||||
sample_streaming_fn,
|
||||
content_type="text/csv",
|
||||
)
|
||||
response = await request.respond(content_type="text/csv")
|
||||
await response.send("foo,")
|
||||
await asyncio.sleep(0.001)
|
||||
await response.send("bar")
|
||||
|
||||
@exception_handler_app.middleware
|
||||
async def some_request_middleware(request):
|
||||
|
@ -183,7 +177,7 @@ def test_exception_handler_lookup(exception_handler_app: Sanic):
|
|||
class ModuleNotFoundError(ImportError):
|
||||
pass
|
||||
|
||||
handler = ErrorHandler("auto")
|
||||
handler = ErrorHandler()
|
||||
handler.add(ImportError, import_error_handler)
|
||||
handler.add(CustomError, custom_error_handler)
|
||||
handler.add(ServerError, server_error_handler)
|
||||
|
@ -261,7 +255,6 @@ def test_exception_handler_response_was_sent(
|
|||
_, response = app.test_client.get("/1")
|
||||
assert "some text" in response.text
|
||||
|
||||
# Change to assert warning not in the records in the future version.
|
||||
message_in_records(
|
||||
caplog.records,
|
||||
(
|
||||
|
|
|
@ -19,35 +19,6 @@ def test_logo_base(app, run_startup):
|
|||
assert logs[0][2] == BASE_LOGO
|
||||
|
||||
|
||||
def test_logo_false(app, run_startup):
|
||||
app.config.LOGO = False
|
||||
|
||||
logs = run_startup(app)
|
||||
|
||||
banner, port = logs[1][2].rsplit(":", 1)
|
||||
assert logs[0][1] == logging.INFO
|
||||
assert banner == "Goin' Fast @ http://127.0.0.1"
|
||||
assert int(port) > 0
|
||||
|
||||
|
||||
def test_logo_true(app, run_startup):
|
||||
app.config.LOGO = True
|
||||
|
||||
logs = run_startup(app)
|
||||
|
||||
assert logs[0][1] == logging.DEBUG
|
||||
assert logs[0][2] == BASE_LOGO
|
||||
|
||||
|
||||
def test_logo_custom(app, run_startup):
|
||||
app.config.LOGO = "My Custom Logo"
|
||||
|
||||
logs = run_startup(app)
|
||||
|
||||
assert logs[0][1] == logging.DEBUG
|
||||
assert logs[0][2] == "My Custom Logo"
|
||||
|
||||
|
||||
def test_motd_with_expected_info(app, run_startup):
|
||||
logs = run_startup(app)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import contextlib
|
|||
|
||||
import pytest
|
||||
|
||||
from sanic.response import stream, text
|
||||
from sanic.response import text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
@ -43,18 +43,16 @@ async def test_stream_request_cancel_when_conn_lost(app):
|
|||
async def post(request, id):
|
||||
assert isinstance(request.stream, asyncio.Queue)
|
||||
|
||||
async def streaming(response):
|
||||
while True:
|
||||
body = await request.stream.get()
|
||||
if body is None:
|
||||
break
|
||||
await response.write(body.decode("utf-8"))
|
||||
response = await request.respond()
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
# at this point client is already disconnected
|
||||
app.ctx.still_serving_cancelled_request = True
|
||||
|
||||
return stream(streaming)
|
||||
while True:
|
||||
body = await request.stream.get()
|
||||
if body is None:
|
||||
break
|
||||
await response.send(body.decode("utf-8"))
|
||||
|
||||
# schedule client call
|
||||
loop = asyncio.get_event_loop()
|
||||
|
|
|
@ -27,7 +27,6 @@ from sanic.response import (
|
|||
file_stream,
|
||||
json,
|
||||
raw,
|
||||
stream,
|
||||
text,
|
||||
)
|
||||
|
||||
|
@ -49,10 +48,13 @@ def test_response_body_not_a_string(app):
|
|||
assert b"Internal Server Error" in response.body
|
||||
|
||||
|
||||
async def sample_streaming_fn(response):
|
||||
await response.write("foo,")
|
||||
async def sample_streaming_fn(request, response=None):
|
||||
if not response:
|
||||
response = await request.respond(content_type="text/csv")
|
||||
await response.send("foo,")
|
||||
await asyncio.sleep(0.001)
|
||||
await response.write("bar")
|
||||
await response.send("bar")
|
||||
await response.eof()
|
||||
|
||||
|
||||
def test_method_not_allowed():
|
||||
|
@ -217,10 +219,7 @@ def test_no_content(json_app):
|
|||
def streaming_app(app):
|
||||
@app.route("/")
|
||||
async def test(request: Request):
|
||||
return stream(
|
||||
sample_streaming_fn,
|
||||
content_type="text/csv",
|
||||
)
|
||||
await sample_streaming_fn(request)
|
||||
|
||||
return app
|
||||
|
||||
|
@ -229,11 +228,11 @@ def streaming_app(app):
|
|||
def non_chunked_streaming_app(app):
|
||||
@app.route("/")
|
||||
async def test(request: Request):
|
||||
return stream(
|
||||
sample_streaming_fn,
|
||||
response = await request.respond(
|
||||
headers={"Content-Length": "7"},
|
||||
content_type="text/csv",
|
||||
)
|
||||
await sample_streaming_fn(request, response)
|
||||
|
||||
return app
|
||||
|
||||
|
@ -283,18 +282,6 @@ def test_non_chunked_streaming_returns_correct_content(
|
|||
assert response.text == "foo,bar"
|
||||
|
||||
|
||||
def test_stream_response_with_cookies_legacy(app):
|
||||
@app.route("/")
|
||||
async def test(request: Request):
|
||||
response = stream(sample_streaming_fn, content_type="text/csv")
|
||||
response.cookies["test"] = "modified"
|
||||
response.cookies["test"] = "pass"
|
||||
return response
|
||||
|
||||
request, response = app.test_client.get("/")
|
||||
assert response.cookies["test"] == "pass"
|
||||
|
||||
|
||||
def test_stream_response_with_cookies(app):
|
||||
@app.route("/")
|
||||
async def test(request: Request):
|
||||
|
@ -317,7 +304,7 @@ def test_stream_response_with_cookies(app):
|
|||
def test_stream_response_without_cookies(app):
|
||||
@app.route("/")
|
||||
async def test(request: Request):
|
||||
return stream(sample_streaming_fn, content_type="text/csv")
|
||||
await sample_streaming_fn(request)
|
||||
|
||||
request, response = app.test_client.get("/")
|
||||
assert response.cookies == {}
|
||||
|
|
Loading…
Reference in New Issue
Block a user