From 13d5a442787080e5c1d79817ecc1b4600994153f Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 28 Jun 2022 15:25:46 +0300 Subject: [PATCH] Release 22.6 (#2487) --- CHANGELOG.rst | 14 ++-- docs/index.rst | 2 +- docs/sanic/changelog.rst | 1 + docs/sanic/releases/21/21.12.md | 6 +- docs/sanic/releases/22/22.6.md | 42 +++++++++++ sanic/__version__.py | 2 +- sanic/app.py | 40 ++--------- sanic/config.py | 17 +---- sanic/handlers.py | 93 +++---------------------- sanic/http/http3.py | 45 ++++++------ sanic/mixins/routes.py | 15 +--- sanic/mixins/runner.py | 6 +- sanic/response.py | 38 +--------- sanic/server/async_server.py | 10 --- sanic/server/protocols/http_protocol.py | 26 ++++--- sanic/server/runners.py | 15 +++- tests/test_app.py | 48 ------------- tests/test_config.py | 9 --- tests/test_exceptions_handler.py | 19 ++--- tests/test_motd.py | 29 -------- tests/test_request_cancel.py | 16 ++--- tests/test_response.py | 33 +++------ 22 files changed, 155 insertions(+), 371 deletions(-) create mode 100644 docs/sanic/releases/22/22.6.md diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d8a76c6c..f3f6ad88 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -313,8 +313,10 @@ Version 21.3.0 `#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 `_ 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 `_ Add disable app registry -Version 20.12.0 ---------------- +Version 20.12.0 🔹 +----------------- **Features** diff --git a/docs/index.rst b/docs/index.rst index 34e0e006..c4588c66 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,7 @@ API === .. toctree:: - :maxdepth: 2 + :maxdepth: 3 👥 User Guide sanic/api_reference diff --git a/docs/sanic/changelog.rst b/docs/sanic/changelog.rst index d810075a..4e8b363c 100644 --- a/docs/sanic/changelog.rst +++ b/docs/sanic/changelog.rst @@ -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 diff --git a/docs/sanic/releases/21/21.12.md b/docs/sanic/releases/21/21.12.md index f8f0d954..1ec1670a 100644 --- a/docs/sanic/releases/21/21.12.md +++ b/docs/sanic/releases/21/21.12.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 diff --git a/docs/sanic/releases/22/22.6.md b/docs/sanic/releases/22/22.6.md new file mode 100644 index 00000000..82654118 --- /dev/null +++ b/docs/sanic/releases/22/22.6.md @@ -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 + diff --git a/sanic/__version__.py b/sanic/__version__.py index ead0aa2f..ac0f77e8 100644 --- a/sanic/__version__.py +++ b/sanic/__version__.py @@ -1 +1 @@ -__version__ = "22.3.2" +__version__ = "22.6.0" diff --git a/sanic/app.py b/sanic/app.py index ec266147..62228726 100644 --- a/sanic/app.py +++ b/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) diff --git a/sanic/config.py b/sanic/config.py index fd63ca54..cfb55f73 100644 --- a/sanic/config.py +++ b/sanic/config.py @@ -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: diff --git a/sanic/handlers.py b/sanic/handlers.py index 69b2b3f2..13bcff94 100644 --- a/sanic/handlers.py +++ b/sanic/handlers.py @@ -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,91 +28,22 @@ 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(): + @classmethod + def finalize(cls, *args, **kwargs): 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, + "ErrorHandler.finalize is deprecated and no longer needed. " + "Please remove update your code to remove it. ", + 22.12, ) - @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): return self.lookup(exception, route_name) @@ -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, diff --git a/sanic/http/http3.py b/sanic/http/http3.py index f48fa7ee..18919ba5 100644 --- a/sanic/http/http3.py +++ b/sanic/http/http3.py @@ -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,14 +28,30 @@ 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] + HttpConnection = Union[H0Connection, H3Connection] class HTTP3Transport(TransportProtocol): @@ -269,12 +273,13 @@ class Http3: Internal helper for managing the HTTP/3 request/response cycle """ - HANDLER_PROPERTY_MAPPING = { - DataReceived: "stream_id", - HeadersReceived: "stream_id", - DatagramReceived: "flow_id", - WebTransportStreamDataReceived: "session_id", - } + if HTTP3_AVAILABLE: + HANDLER_PROPERTY_MAPPING = { + DataReceived: "stream_id", + HeadersReceived: "stream_id", + DatagramReceived: "flow_id", + WebTransportStreamDataReceived: "session_id", + } def __init__( self, diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index 5704c600..425b29e0 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -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." ) diff --git a/sanic/mixins/runner.py b/sanic/mixins/runner.py index ee787776..3d2b327d 100644 --- a/sanic/mixins/runner.py +++ b/sanic/mixins/runner.py @@ -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) diff --git a/sanic/response.py b/sanic/response.py index adb7a5b6..88359244 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -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, - ) diff --git a/sanic/server/async_server.py b/sanic/server/async_server.py index c13af464..e2cf4fa1 100644 --- a/sanic/server/async_server.py +++ b/sanic/server/async_server.py @@ -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 diff --git a/sanic/server/protocols/http_protocol.py b/sanic/server/protocols/http_protocol.py index b3d7625b..616e2303 100644 --- a/sanic/server/protocols/http_protocol.py +++ b/sanic/server/protocols/http_protocol.py @@ -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 diff --git a/sanic/server/runners.py b/sanic/server/runners.py index 81c8b64a..a1b86e81 100644 --- a/sanic/server/runners.py +++ b/sanic/server/runners.py @@ -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) diff --git a/tests/test_app.py b/tests/test_app.py index 1c8f705b..6c6f9e9a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -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 " diff --git a/tests/test_config.py b/tests/test_config.py index 764d7940..f52d8472 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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) diff --git a/tests/test_exceptions_handler.py b/tests/test_exceptions_handler.py index 78f79388..c505cead 100644 --- a/tests/test_exceptions_handler.py +++ b/tests/test_exceptions_handler.py @@ -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, ( diff --git a/tests/test_motd.py b/tests/test_motd.py index 83c7e4bf..51b838b1 100644 --- a/tests/test_motd.py +++ b/tests/test_motd.py @@ -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) diff --git a/tests/test_request_cancel.py b/tests/test_request_cancel.py index 8b5de18d..4680949b 100644 --- a/tests/test_request_cancel.py +++ b/tests/test_request_cancel.py @@ -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() diff --git a/tests/test_response.py b/tests/test_response.py index a25a1318..632806cd 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -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 == {}