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>`_
|
`#2074 <https://github.com/sanic-org/sanic/pull/2074>`_
|
||||||
Performance adjustments in ``handle_request_``
|
Performance adjustments in ``handle_request_``
|
||||||
|
|
||||||
Version 20.12.3
|
Version 20.12.3 🔷
|
||||||
---------------
|
------------------
|
||||||
|
|
||||||
|
`Current LTS version`
|
||||||
|
|
||||||
**Bugfixes**
|
**Bugfixes**
|
||||||
|
|
||||||
|
@ -348,8 +350,8 @@ Version 19.12.5
|
||||||
`#2027 <https://github.com/sanic-org/sanic/pull/2027>`_
|
`#2027 <https://github.com/sanic-org/sanic/pull/2027>`_
|
||||||
Remove old chardet requirement, add in hard multidict requirement
|
Remove old chardet requirement, add in hard multidict requirement
|
||||||
|
|
||||||
Version 20.12.0
|
Version 20.12.0 🔹
|
||||||
---------------
|
-----------------
|
||||||
|
|
||||||
**Features**
|
**Features**
|
||||||
|
|
||||||
|
@ -357,8 +359,8 @@ Version 20.12.0
|
||||||
`#1993 <https://github.com/sanic-org/sanic/pull/1993>`_
|
`#1993 <https://github.com/sanic-org/sanic/pull/1993>`_
|
||||||
Add disable app registry
|
Add disable app registry
|
||||||
|
|
||||||
Version 20.12.0
|
Version 20.12.0 🔹
|
||||||
---------------
|
-----------------
|
||||||
|
|
||||||
**Features**
|
**Features**
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ API
|
||||||
===
|
===
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 3
|
||||||
|
|
||||||
👥 User Guide <https://sanicframework.org/guide/>
|
👥 User Guide <https://sanicframework.org/guide/>
|
||||||
sanic/api_reference
|
sanic/api_reference
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
📜 Changelog
|
📜 Changelog
|
||||||
============
|
============
|
||||||
|
|
||||||
|
.. mdinclude:: ./releases/22/22.6.md
|
||||||
.. mdinclude:: ./releases/22/22.3.md
|
.. mdinclude:: ./releases/22/22.3.md
|
||||||
.. mdinclude:: ./releases/21/21.12.md
|
.. mdinclude:: ./releases/21/21.12.md
|
||||||
.. mdinclude:: ./releases/21/21.9.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
|
- [#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
|
- [#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
|
- [#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
|
### Features
|
||||||
- [#2260](https://github.com/sanic-org/sanic/pull/2260) Allow early Blueprint registrations to still apply later added objects
|
- [#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,
|
strict_slashes: bool = False,
|
||||||
log_config: Optional[Dict[str, Any]] = None,
|
log_config: Optional[Dict[str, Any]] = None,
|
||||||
configure_logging: bool = True,
|
configure_logging: bool = True,
|
||||||
register: Optional[bool] = None,
|
|
||||||
dumps: Optional[Callable[..., AnyStr]] = None,
|
dumps: Optional[Callable[..., AnyStr]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(name=name)
|
super().__init__(name=name)
|
||||||
|
@ -218,20 +217,9 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
||||||
|
|
||||||
# Register alternative method names
|
# Register alternative method names
|
||||||
self.go_fast = self.run
|
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.router.ctx.app = self
|
||||||
self.signal_router.ctx.app = self
|
self.signal_router.ctx.app = self
|
||||||
|
self.__class__.register_app(self)
|
||||||
|
|
||||||
if dumps:
|
if dumps:
|
||||||
BaseHTTPResponse._dumps = dumps # type: ignore
|
BaseHTTPResponse._dumps = dumps # type: ignore
|
||||||
|
@ -736,37 +724,24 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
||||||
"has at least partially been sent."
|
"has at least partially been sent."
|
||||||
)
|
)
|
||||||
|
|
||||||
# ----------------- deprecated -----------------
|
|
||||||
handler = self.error_handler._lookup(
|
handler = self.error_handler._lookup(
|
||||||
exception, request.name if request else None
|
exception, request.name if request else None
|
||||||
)
|
)
|
||||||
if handler:
|
if handler:
|
||||||
deprecation(
|
logger.warning(
|
||||||
"An error occurred while handling the request after at "
|
"An error occurred while handling the request after at "
|
||||||
"least some part of the response was sent to the client. "
|
"least some part of the response was sent to the client. "
|
||||||
"Therefore, the response from your custom exception "
|
"The response from your custom exception handler "
|
||||||
f"handler {handler.__name__} will not be sent to the "
|
f"{handler.__name__} will not be sent to the client."
|
||||||
"client. Beginning in v22.6, Sanic will stop executing "
|
"Exception handlers should only be used to generate the "
|
||||||
"custom exception handlers in this scenario. Exception "
|
"exception responses. If you would like to perform any "
|
||||||
"handlers should only be used to generate the exception "
|
"other action on a raised exception, consider using a "
|
||||||
"responses. If you would like to perform any other "
|
|
||||||
"action on a raised exception, please consider using a "
|
|
||||||
"signal handler like "
|
"signal handler like "
|
||||||
'`@app.signal("http.lifecycle.exception")`\n'
|
'`@app.signal("http.lifecycle.exception")`\n'
|
||||||
"For further information, please see the docs: "
|
"For further information, please see the docs: "
|
||||||
"https://sanicframework.org/en/guide/advanced/"
|
"https://sanicframework.org/en/guide/advanced/"
|
||||||
"signals.html",
|
"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
|
return
|
||||||
|
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
|
@ -1559,7 +1534,6 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
||||||
if self.state.primary:
|
if self.state.primary:
|
||||||
# TODO:
|
# TODO:
|
||||||
# - Raise warning if secondary apps have error handler config
|
# - Raise warning if secondary apps have error handler config
|
||||||
ErrorHandler.finalize(self.error_handler, config=self.config)
|
|
||||||
if self.config.TOUCHUP:
|
if self.config.TOUCHUP:
|
||||||
TouchUp.run(self)
|
TouchUp.run(self)
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ DEFAULT_CONFIG = {
|
||||||
"NOISY_EXCEPTIONS": False,
|
"NOISY_EXCEPTIONS": False,
|
||||||
"PROXIES_COUNT": None,
|
"PROXIES_COUNT": None,
|
||||||
"REAL_IP_HEADER": None,
|
"REAL_IP_HEADER": None,
|
||||||
"REGISTER": True,
|
|
||||||
"REQUEST_BUFFER_SIZE": 65536, # 64 KiB
|
"REQUEST_BUFFER_SIZE": 65536, # 64 KiB
|
||||||
"REQUEST_MAX_HEADER_SIZE": 8192, # 8 KiB, but cannot exceed 16384
|
"REQUEST_MAX_HEADER_SIZE": 8192, # 8 KiB, but cannot exceed 16384
|
||||||
"REQUEST_ID_HEADER": "X-Request-ID",
|
"REQUEST_ID_HEADER": "X-Request-ID",
|
||||||
|
@ -84,7 +83,6 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||||
NOISY_EXCEPTIONS: bool
|
NOISY_EXCEPTIONS: bool
|
||||||
PROXIES_COUNT: Optional[int]
|
PROXIES_COUNT: Optional[int]
|
||||||
REAL_IP_HEADER: Optional[str]
|
REAL_IP_HEADER: Optional[str]
|
||||||
REGISTER: bool
|
|
||||||
REQUEST_BUFFER_SIZE: int
|
REQUEST_BUFFER_SIZE: int
|
||||||
REQUEST_MAX_HEADER_SIZE: int
|
REQUEST_MAX_HEADER_SIZE: int
|
||||||
REQUEST_ID_HEADER: str
|
REQUEST_ID_HEADER: str
|
||||||
|
@ -111,7 +109,6 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||||
super().__init__({**DEFAULT_CONFIG, **defaults})
|
super().__init__({**DEFAULT_CONFIG, **defaults})
|
||||||
|
|
||||||
self._converters = [str, str_to_bool, float, int]
|
self._converters = [str, str_to_bool, float, int]
|
||||||
self._LOGO = ""
|
|
||||||
|
|
||||||
if converters:
|
if converters:
|
||||||
for converter in converters:
|
for converter in converters:
|
||||||
|
@ -168,24 +165,14 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||||
"REQUEST_MAX_SIZE",
|
"REQUEST_MAX_SIZE",
|
||||||
):
|
):
|
||||||
self._configure_header_size()
|
self._configure_header_size()
|
||||||
if attr == "LOGO":
|
|
||||||
self._LOGO = value
|
if attr == "LOCAL_CERT_CREATOR" and not isinstance(
|
||||||
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(
|
|
||||||
self.LOCAL_CERT_CREATOR, LocalCertCreator
|
self.LOCAL_CERT_CREATOR, LocalCertCreator
|
||||||
):
|
):
|
||||||
self.LOCAL_CERT_CREATOR = LocalCertCreator[
|
self.LOCAL_CERT_CREATOR = LocalCertCreator[
|
||||||
self.LOCAL_CERT_CREATOR.upper()
|
self.LOCAL_CERT_CREATOR.upper()
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
|
||||||
def LOGO(self):
|
|
||||||
return self._LOGO
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def FALLBACK_ERROR_FORMAT(self) -> str:
|
def FALLBACK_ERROR_FORMAT(self) -> str:
|
||||||
if self._FALLBACK_ERROR_FORMAT is _default:
|
if self._FALLBACK_ERROR_FORMAT is _default:
|
||||||
|
|
|
@ -1,21 +1,13 @@
|
||||||
from __future__ import annotations
|
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 BaseRenderer, TextRenderer, exception_response
|
||||||
from sanic.errorpages import (
|
|
||||||
DEFAULT_FORMAT,
|
|
||||||
BaseRenderer,
|
|
||||||
TextRenderer,
|
|
||||||
exception_response,
|
|
||||||
)
|
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
HeaderNotFound,
|
HeaderNotFound,
|
||||||
InvalidRangeType,
|
InvalidRangeType,
|
||||||
RangeNotSatisfiable,
|
RangeNotSatisfiable,
|
||||||
SanicException,
|
|
||||||
)
|
)
|
||||||
from sanic.helpers import Default, _default
|
|
||||||
from sanic.log import deprecation, error_logger
|
from sanic.log import deprecation, error_logger
|
||||||
from sanic.models.handler_types import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
@ -36,91 +28,22 @@ class ErrorHandler:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
fallback: Union[str, Default] = _default,
|
|
||||||
base: Type[BaseRenderer] = TextRenderer,
|
base: Type[BaseRenderer] = TextRenderer,
|
||||||
):
|
):
|
||||||
self.cached_handlers: Dict[
|
self.cached_handlers: Dict[
|
||||||
Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler]
|
Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler]
|
||||||
] = {}
|
] = {}
|
||||||
self.debug = False
|
self.debug = False
|
||||||
self._fallback = fallback
|
|
||||||
self.base = base
|
self.base = base
|
||||||
|
|
||||||
if fallback is not _default:
|
@classmethod
|
||||||
self._warn_fallback_deprecation()
|
def finalize(cls, *args, **kwargs):
|
||||||
|
|
||||||
@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(
|
deprecation(
|
||||||
"Setting the ErrorHandler fallback value directly is "
|
"ErrorHandler.finalize is deprecated and no longer needed. "
|
||||||
"deprecated and no longer supported. This feature will "
|
"Please remove update your code to remove it. ",
|
||||||
"be removed in v22.6. Instead, use "
|
22.12,
|
||||||
"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:
|
|
||||||
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)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _full_lookup(self, exception, route_name: Optional[str] = None):
|
def _full_lookup(self, exception, route_name: Optional[str] = None):
|
||||||
return self.lookup(exception, route_name)
|
return self.lookup(exception, route_name)
|
||||||
|
|
||||||
|
@ -237,7 +160,7 @@ class ErrorHandler:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.log(request, exception)
|
self.log(request, exception)
|
||||||
fallback = ErrorHandler._get_fallback_value(self, request.app.config)
|
fallback = request.app.config.FALLBACK_ERROR_FORMAT
|
||||||
return exception_response(
|
return exception_response(
|
||||||
request,
|
request,
|
||||||
exception,
|
exception,
|
||||||
|
|
|
@ -16,18 +16,6 @@ from typing import (
|
||||||
cast,
|
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.compat import Header
|
||||||
from sanic.constants import LocalCertCreator
|
from sanic.constants import LocalCertCreator
|
||||||
from sanic.exceptions import PayloadTooLarge, SanicException, ServerError
|
from sanic.exceptions import PayloadTooLarge, SanicException, ServerError
|
||||||
|
@ -40,14 +28,30 @@ from sanic.models.protocol_types import TransportProtocol
|
||||||
from sanic.models.server_types import ConnInfo
|
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:
|
if TYPE_CHECKING:
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import BaseHTTPResponse
|
from sanic.response import BaseHTTPResponse
|
||||||
from sanic.server.protocols.http_protocol import Http3Protocol
|
from sanic.server.protocols.http_protocol import Http3Protocol
|
||||||
|
|
||||||
|
HttpConnection = Union[H0Connection, H3Connection]
|
||||||
HttpConnection = Union[H0Connection, H3Connection]
|
|
||||||
|
|
||||||
|
|
||||||
class HTTP3Transport(TransportProtocol):
|
class HTTP3Transport(TransportProtocol):
|
||||||
|
@ -269,12 +273,13 @@ class Http3:
|
||||||
Internal helper for managing the HTTP/3 request/response cycle
|
Internal helper for managing the HTTP/3 request/response cycle
|
||||||
"""
|
"""
|
||||||
|
|
||||||
HANDLER_PROPERTY_MAPPING = {
|
if HTTP3_AVAILABLE:
|
||||||
DataReceived: "stream_id",
|
HANDLER_PROPERTY_MAPPING = {
|
||||||
HeadersReceived: "stream_id",
|
DataReceived: "stream_id",
|
||||||
DatagramReceived: "flow_id",
|
HeadersReceived: "stream_id",
|
||||||
WebTransportStreamDataReceived: "session_id",
|
DatagramReceived: "flow_id",
|
||||||
}
|
WebTransportStreamDataReceived: "session_id",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -34,7 +34,7 @@ from sanic.exceptions import (
|
||||||
RangeNotSatisfiable,
|
RangeNotSatisfiable,
|
||||||
)
|
)
|
||||||
from sanic.handlers import ContentRangeHandler
|
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.futures import FutureRoute, FutureStatic
|
||||||
from sanic.models.handler_types import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
from sanic.response import HTTPResponse, file, file_stream
|
from sanic.response import HTTPResponse, file, file_stream
|
||||||
|
@ -1025,17 +1025,6 @@ class RouteMixin(metaclass=SanicMeta):
|
||||||
nonlocal types
|
nonlocal types
|
||||||
|
|
||||||
with suppress(AttributeError):
|
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
|
checks = [node.value.func.id] # type: ignore
|
||||||
if node.value.keywords: # type: ignore
|
if node.value.keywords: # type: ignore
|
||||||
checks += [
|
checks += [
|
||||||
|
@ -1066,7 +1055,7 @@ class RouteMixin(metaclass=SanicMeta):
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"Cannot use restricted route context: "
|
"Cannot use restricted route context: "
|
||||||
f"{restricted_arguments}. This limitation is only in place "
|
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 "
|
"conflict. See https://github.com/sanic-org/sanic/issues/2303 "
|
||||||
"for more information."
|
"for more information."
|
||||||
)
|
)
|
||||||
|
|
|
@ -565,11 +565,7 @@ class RunnerMixin(metaclass=SanicMeta):
|
||||||
if self.config.MOTD_DISPLAY:
|
if self.config.MOTD_DISPLAY:
|
||||||
extra.update(self.config.MOTD_DISPLAY)
|
extra.update(self.config.MOTD_DISPLAY)
|
||||||
|
|
||||||
logo = (
|
logo = get_logo(coffee=self.state.coffee)
|
||||||
get_logo(coffee=self.state.coffee)
|
|
||||||
if self.config.LOGO == "" or self.config.LOGO is True
|
|
||||||
else self.config.LOGO
|
|
||||||
)
|
|
||||||
|
|
||||||
MOTD.output(logo, serve_location, display, extra)
|
MOTD.output(logo, serve_location, display, extra)
|
||||||
|
|
||||||
|
|
|
@ -427,8 +427,7 @@ def redirect(
|
||||||
class ResponseStream:
|
class ResponseStream:
|
||||||
"""
|
"""
|
||||||
ResponseStream is a compat layer to bridge the gap after the deprecation
|
ResponseStream is a compat layer to bridge the gap after the deprecation
|
||||||
of StreamingHTTPResponse. In v22.6 it will be removed when:
|
of StreamingHTTPResponse. It will be removed when:
|
||||||
- stream is removed
|
|
||||||
- file_stream is moved to new style streaming
|
- file_stream is moved to new style streaming
|
||||||
- file and file_stream are combined into a single API
|
- file and file_stream are combined into a single API
|
||||||
"""
|
"""
|
||||||
|
@ -556,38 +555,3 @@ async def file_stream(
|
||||||
headers=headers,
|
headers=headers,
|
||||||
content_type=mime_type,
|
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 typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sanic.exceptions import SanicException
|
from sanic.exceptions import SanicException
|
||||||
from sanic.log import deprecation
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -35,15 +34,6 @@ class AsyncioServer:
|
||||||
self.serve_coro = serve_coro
|
self.serve_coro = serve_coro
|
||||||
self.server = None
|
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):
|
def startup(self):
|
||||||
"""
|
"""
|
||||||
Trigger "before_server_start" events
|
Trigger "before_server_start" events
|
||||||
|
|
|
@ -2,8 +2,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from aioquic.h3.connection import H3_ALPN, H3Connection
|
|
||||||
|
|
||||||
from sanic.http.constants import HTTP
|
from sanic.http.constants import HTTP
|
||||||
from sanic.http.http3 import Http3
|
from sanic.http.http3 import Http3
|
||||||
from sanic.touchup.meta import TouchUpMeta
|
from sanic.touchup.meta import TouchUpMeta
|
||||||
|
@ -17,13 +15,6 @@ import sys
|
||||||
from asyncio import CancelledError
|
from asyncio import CancelledError
|
||||||
from time import monotonic as current_time
|
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.exceptions import RequestTimeout, ServiceUnavailable
|
||||||
from sanic.http import Http, Stage
|
from sanic.http import Http, Stage
|
||||||
from sanic.log import Colors, error_logger, logger
|
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
|
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:
|
class HttpProtocolMixin:
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
__version__: HTTP
|
__version__: HTTP
|
||||||
|
@ -278,7 +284,7 @@ class HttpProtocol(HttpProtocolMixin, SanicProtocol, metaclass=TouchUpMeta):
|
||||||
error_logger.exception("protocol.data_received")
|
error_logger.exception("protocol.data_received")
|
||||||
|
|
||||||
|
|
||||||
class Http3Protocol(HttpProtocolMixin, QuicConnectionProtocol):
|
class Http3Protocol(HttpProtocolMixin, ConnectionProtocol): # type: ignore
|
||||||
HTTP_CLASS = Http3
|
HTTP_CLASS = Http3
|
||||||
__version__ = HTTP.VERSION_3
|
__version__ = HTTP.VERSION_3
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ssl import SSLContext
|
||||||
from typing import TYPE_CHECKING, Dict, Optional, Type, Union
|
from typing import TYPE_CHECKING, Dict, Optional, Type, Union
|
||||||
|
|
||||||
from sanic.config import Config
|
from sanic.config import Config
|
||||||
|
from sanic.exceptions import ServerError
|
||||||
from sanic.http.constants import HTTP
|
from sanic.http.constants import HTTP
|
||||||
from sanic.http.tls import get_ssl_context
|
from sanic.http.tls import get_ssl_context
|
||||||
from sanic.server.events import trigger_events
|
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 SIG_IGN, SIGINT, SIGTERM, Signals
|
||||||
from signal import signal as signal_func
|
from signal import signal as signal_func
|
||||||
|
|
||||||
from aioquic.asyncio import serve as quic_serve
|
|
||||||
|
|
||||||
from sanic.application.ext import setup_ext
|
from sanic.application.ext import setup_ext
|
||||||
from sanic.compat import OS_IS_WINDOWS, ctrlc_workaround_for_windows
|
from sanic.compat import OS_IS_WINDOWS, ctrlc_workaround_for_windows
|
||||||
from sanic.http.http3 import SessionTicketStore, get_config
|
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(
|
def serve(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
@ -273,6 +280,10 @@ def _serve_http_3(
|
||||||
register_sys_signals: bool = True,
|
register_sys_signals: bool = True,
|
||||||
run_multiple: bool = False,
|
run_multiple: bool = False,
|
||||||
):
|
):
|
||||||
|
if not HTTP3_AVAILABLE:
|
||||||
|
raise ServerError(
|
||||||
|
"Cannot run HTTP/3 server without aioquic installed. "
|
||||||
|
)
|
||||||
protocol = partial(Http3Protocol, app=app)
|
protocol = partial(Http3Protocol, app=app)
|
||||||
ticket_store = SessionTicketStore()
|
ticket_store = SessionTicketStore()
|
||||||
ssl_context = get_ssl_context(app, ssl)
|
ssl_context = get_ssl_context(app, ssl)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import re
|
||||||
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from os import environ
|
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -113,19 +112,6 @@ def test_create_server_main_convenience(app, caplog):
|
||||||
) in caplog.record_tuples
|
) 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):
|
def test_app_loop_not_running(app):
|
||||||
with pytest.raises(SanicException) as excinfo:
|
with pytest.raises(SanicException) as excinfo:
|
||||||
app.loop
|
app.loop
|
||||||
|
@ -385,40 +371,6 @@ def test_get_app_default_ambiguous():
|
||||||
Sanic.get_app()
|
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):
|
def test_app_set_attribute_warning(app):
|
||||||
message = (
|
message = (
|
||||||
"Setting variables on Sanic instances is not allowed. You should "
|
"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
|
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):
|
def test_config_set_methods(app: Sanic, monkeypatch: MonkeyPatch):
|
||||||
post_set = Mock()
|
post_set = Mock()
|
||||||
monkeypatch.setattr(Config, "_post_set", post_set)
|
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.exceptions import BadRequest, Forbidden, NotFound, ServerError
|
||||||
from sanic.handlers import ErrorHandler
|
from sanic.handlers import ErrorHandler
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import stream, text
|
from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
async def sample_streaming_fn(response):
|
|
||||||
await response.write("foo,")
|
|
||||||
await asyncio.sleep(0.001)
|
|
||||||
await response.write("bar")
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorWithRequestCtx(ServerError):
|
class ErrorWithRequestCtx(ServerError):
|
||||||
|
@ -81,10 +75,10 @@ def exception_handler_app():
|
||||||
|
|
||||||
@exception_handler_app.exception(Forbidden)
|
@exception_handler_app.exception(Forbidden)
|
||||||
async def async_handler_exception(request, exception):
|
async def async_handler_exception(request, exception):
|
||||||
return stream(
|
response = await request.respond(content_type="text/csv")
|
||||||
sample_streaming_fn,
|
await response.send("foo,")
|
||||||
content_type="text/csv",
|
await asyncio.sleep(0.001)
|
||||||
)
|
await response.send("bar")
|
||||||
|
|
||||||
@exception_handler_app.middleware
|
@exception_handler_app.middleware
|
||||||
async def some_request_middleware(request):
|
async def some_request_middleware(request):
|
||||||
|
@ -183,7 +177,7 @@ def test_exception_handler_lookup(exception_handler_app: Sanic):
|
||||||
class ModuleNotFoundError(ImportError):
|
class ModuleNotFoundError(ImportError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
handler = ErrorHandler("auto")
|
handler = ErrorHandler()
|
||||||
handler.add(ImportError, import_error_handler)
|
handler.add(ImportError, import_error_handler)
|
||||||
handler.add(CustomError, custom_error_handler)
|
handler.add(CustomError, custom_error_handler)
|
||||||
handler.add(ServerError, server_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")
|
_, response = app.test_client.get("/1")
|
||||||
assert "some text" in response.text
|
assert "some text" in response.text
|
||||||
|
|
||||||
# Change to assert warning not in the records in the future version.
|
|
||||||
message_in_records(
|
message_in_records(
|
||||||
caplog.records,
|
caplog.records,
|
||||||
(
|
(
|
||||||
|
|
|
@ -19,35 +19,6 @@ def test_logo_base(app, run_startup):
|
||||||
assert logs[0][2] == BASE_LOGO
|
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):
|
def test_motd_with_expected_info(app, run_startup):
|
||||||
logs = run_startup(app)
|
logs = run_startup(app)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import contextlib
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic.response import stream, text
|
from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -43,18 +43,16 @@ async def test_stream_request_cancel_when_conn_lost(app):
|
||||||
async def post(request, id):
|
async def post(request, id):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, asyncio.Queue)
|
||||||
|
|
||||||
async def streaming(response):
|
response = await request.respond()
|
||||||
while True:
|
|
||||||
body = await request.stream.get()
|
|
||||||
if body is None:
|
|
||||||
break
|
|
||||||
await response.write(body.decode("utf-8"))
|
|
||||||
|
|
||||||
await asyncio.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
# at this point client is already disconnected
|
# at this point client is already disconnected
|
||||||
app.ctx.still_serving_cancelled_request = True
|
app.ctx.still_serving_cancelled_request = True
|
||||||
|
while True:
|
||||||
return stream(streaming)
|
body = await request.stream.get()
|
||||||
|
if body is None:
|
||||||
|
break
|
||||||
|
await response.send(body.decode("utf-8"))
|
||||||
|
|
||||||
# schedule client call
|
# schedule client call
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
|
@ -27,7 +27,6 @@ from sanic.response import (
|
||||||
file_stream,
|
file_stream,
|
||||||
json,
|
json,
|
||||||
raw,
|
raw,
|
||||||
stream,
|
|
||||||
text,
|
text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,10 +48,13 @@ def test_response_body_not_a_string(app):
|
||||||
assert b"Internal Server Error" in response.body
|
assert b"Internal Server Error" in response.body
|
||||||
|
|
||||||
|
|
||||||
async def sample_streaming_fn(response):
|
async def sample_streaming_fn(request, response=None):
|
||||||
await response.write("foo,")
|
if not response:
|
||||||
|
response = await request.respond(content_type="text/csv")
|
||||||
|
await response.send("foo,")
|
||||||
await asyncio.sleep(0.001)
|
await asyncio.sleep(0.001)
|
||||||
await response.write("bar")
|
await response.send("bar")
|
||||||
|
await response.eof()
|
||||||
|
|
||||||
|
|
||||||
def test_method_not_allowed():
|
def test_method_not_allowed():
|
||||||
|
@ -217,10 +219,7 @@ def test_no_content(json_app):
|
||||||
def streaming_app(app):
|
def streaming_app(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def test(request: Request):
|
async def test(request: Request):
|
||||||
return stream(
|
await sample_streaming_fn(request)
|
||||||
sample_streaming_fn,
|
|
||||||
content_type="text/csv",
|
|
||||||
)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -229,11 +228,11 @@ def streaming_app(app):
|
||||||
def non_chunked_streaming_app(app):
|
def non_chunked_streaming_app(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def test(request: Request):
|
async def test(request: Request):
|
||||||
return stream(
|
response = await request.respond(
|
||||||
sample_streaming_fn,
|
|
||||||
headers={"Content-Length": "7"},
|
headers={"Content-Length": "7"},
|
||||||
content_type="text/csv",
|
content_type="text/csv",
|
||||||
)
|
)
|
||||||
|
await sample_streaming_fn(request, response)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -283,18 +282,6 @@ def test_non_chunked_streaming_returns_correct_content(
|
||||||
assert response.text == "foo,bar"
|
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):
|
def test_stream_response_with_cookies(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def test(request: Request):
|
async def test(request: Request):
|
||||||
|
@ -317,7 +304,7 @@ def test_stream_response_with_cookies(app):
|
||||||
def test_stream_response_without_cookies(app):
|
def test_stream_response_without_cookies(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def test(request: Request):
|
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("/")
|
request, response = app.test_client.get("/")
|
||||||
assert response.cookies == {}
|
assert response.cookies == {}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user