22.3 Deprecations and changes (#2362)

This commit is contained in:
Adam Hopkins 2022-01-12 16:28:43 +02:00 committed by GitHub
parent 8b0eaa097c
commit 8dfa49b648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 110 additions and 115 deletions

View File

@ -1061,6 +1061,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
host: Optional[str] = None, host: Optional[str] = None,
port: Optional[int] = None, port: Optional[int] = None,
*, *,
dev: bool = False,
debug: bool = False, debug: bool = False,
auto_reload: Optional[bool] = None, auto_reload: Optional[bool] = None,
ssl: Union[None, SSLContext, dict, str, list, tuple] = None, ssl: Union[None, SSLContext, dict, str, list, tuple] = None,
@ -1143,9 +1144,11 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
"#asynchronous-support" "#asynchronous-support"
) )
if auto_reload or auto_reload is None and debug: if dev:
debug = True
auto_reload = True auto_reload = True
if os.environ.get("SANIC_SERVER_RUNNING") != "true":
if auto_reload and os.environ.get("SANIC_SERVER_RUNNING") != "true":
return reloader_helpers.watchdog(1.0, self) return reloader_helpers.watchdog(1.0, self)
if sock is None: if sock is None:

View File

@ -79,13 +79,6 @@ Or, a path to a directory to run as a simple HTTP server:
error_logger.exception("Failed to run app") error_logger.exception("Failed to run app")
def _precheck(self): def _precheck(self):
if self.args.debug and self.main_process:
error_logger.warning(
"Starting in v22.3, --debug will no "
"longer automatically run the auto-reloader.\n Switch to "
"--dev to continue using that functionality."
)
# # Custom TLS mismatch handling for better diagnostics # # Custom TLS mismatch handling for better diagnostics
if self.main_process and ( if self.main_process and (
# one of cert/key missing # one of cert/key missing
@ -174,8 +167,9 @@ Or, a path to a directory to run as a simple HTTP server:
"workers": self.args.workers, "workers": self.args.workers,
} }
if self.args.auto_reload: for maybe_arg in ("auto_reload", "dev"):
kwargs["auto_reload"] = True if getattr(self.args, maybe_arg, False):
kwargs[maybe_arg] = True
if self.args.path: if self.args.path:
if self.args.auto_reload or self.args.debug: if self.args.auto_reload or self.args.debug:

View File

@ -180,19 +180,18 @@ class DevelopmentGroup(Group):
"--debug", "--debug",
dest="debug", dest="debug",
action="store_true", action="store_true",
help="Run the server in debug mode", help=(
"Run the server in DEBUG mode. It includes DEBUG logging, "
"additional context on exceptions, and other settings "
"not-safe for PRODUCTION, but helpful for debugging problems."
),
) )
self.container.add_argument( self.container.add_argument(
"-d", "-d",
"--dev", "--dev",
dest="debug", dest="dev",
action="store_true", action="store_true",
help=( help=("Debug + auto_reload."),
"Currently is an alias for --debug. But starting in v22.3, \n"
"--debug will no longer automatically trigger auto_restart. \n"
"However, --dev will continue, effectively making it the \n"
"same as debug + auto_reload."
),
) )
self.container.add_argument( self.container.add_argument(
"-r", "-r",

View File

@ -1,13 +1,12 @@
from __future__ import annotations from __future__ import annotations
from inspect import signature
from typing import Dict, List, Optional, Tuple, Type, Union from typing import Dict, List, Optional, Tuple, Type, Union
from sanic.config import Config from sanic.config import Config
from sanic.errorpages import ( from sanic.errorpages import (
DEFAULT_FORMAT, DEFAULT_FORMAT,
BaseRenderer, BaseRenderer,
HTMLRenderer, TextRenderer,
exception_response, exception_response,
) )
from sanic.exceptions import ( from sanic.exceptions import (
@ -35,13 +34,11 @@ class ErrorHandler:
""" """
# Beginning in v22.3, the base renderer will be TextRenderer
def __init__( def __init__(
self, self,
fallback: Union[str, Default] = _default, fallback: Union[str, Default] = _default,
base: Type[BaseRenderer] = HTMLRenderer, base: Type[BaseRenderer] = TextRenderer,
): ):
self.handlers: List[Tuple[Type[BaseException], RouteHandler]] = []
self.cached_handlers: Dict[ self.cached_handlers: Dict[
Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler] Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler]
] = {} ] = {}
@ -95,8 +92,8 @@ class ErrorHandler:
def finalize( def finalize(
cls, cls,
error_handler: ErrorHandler, error_handler: ErrorHandler,
config: Config,
fallback: Optional[str] = None, fallback: Optional[str] = None,
config: Optional[Config] = None,
): ):
if fallback: if fallback:
deprecation( deprecation(
@ -107,14 +104,10 @@ class ErrorHandler:
22.6, 22.6,
) )
if config is None: if not fallback:
deprecation( fallback = config.FALLBACK_ERROR_FORMAT
"Starting in v22.3, config will be a required argument "
"for ErrorHandler.finalize().",
22.3,
)
if fallback and fallback != DEFAULT_FORMAT: if fallback != DEFAULT_FORMAT:
if error_handler._fallback is not _default: if error_handler._fallback is not _default:
error_logger.warning( error_logger.warning(
f"Setting the fallback value to {fallback}. This changes " f"Setting the fallback value to {fallback}. This changes "
@ -128,27 +121,9 @@ class ErrorHandler:
f"Error handler is non-conforming: {type(error_handler)}" f"Error handler is non-conforming: {type(error_handler)}"
) )
sig = signature(error_handler.lookup)
if len(sig.parameters) == 1:
deprecation(
"You are using a deprecated error handler. The lookup "
"method should accept two positional parameters: "
"(exception, route_name: Optional[str]). "
"Until you upgrade your ErrorHandler.lookup, Blueprint "
"specific exceptions will not work properly. Beginning "
"in v22.3, the legacy style lookup method will not "
"work at all.",
22.3,
)
legacy_lookup = error_handler._legacy_lookup
error_handler._lookup = legacy_lookup # type: ignore
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)
def _legacy_lookup(self, exception, route_name: Optional[str] = None):
return self.lookup(exception)
def add(self, exception, handler, route_names: Optional[List[str]] = None): def add(self, exception, handler, route_names: Optional[List[str]] = None):
""" """
Add a new exception handler to an already existing handler object. Add a new exception handler to an already existing handler object.
@ -162,9 +137,6 @@ class ErrorHandler:
:return: None :return: None
""" """
# self.handlers is deprecated and will be removed in version 22.3
self.handlers.append((exception, handler))
if route_names: if route_names:
for route in route_names: for route in route_names:
self.cached_handlers[(exception, route)] = handler self.cached_handlers[(exception, route)] = handler

View File

@ -5,7 +5,7 @@ from websockets.server import ServerConnection
from websockets.typing import Subprotocol from websockets.typing import Subprotocol
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.log import deprecation, error_logger from sanic.log import error_logger
from sanic.server import HttpProtocol from sanic.server import HttpProtocol
from ..websockets.impl import WebsocketImplProtocol from ..websockets.impl import WebsocketImplProtocol
@ -29,9 +29,6 @@ class WebSocketProtocol(HttpProtocol):
*args, *args,
websocket_timeout: float = 10.0, websocket_timeout: float = 10.0,
websocket_max_size: Optional[int] = None, websocket_max_size: Optional[int] = None,
websocket_max_queue: Optional[int] = None, # max_queue is deprecated
websocket_read_limit: Optional[int] = None, # read_limit is deprecated
websocket_write_limit: Optional[int] = None, # write_limit deprecated
websocket_ping_interval: Optional[float] = 20.0, websocket_ping_interval: Optional[float] = 20.0,
websocket_ping_timeout: Optional[float] = 20.0, websocket_ping_timeout: Optional[float] = 20.0,
**kwargs, **kwargs,
@ -40,27 +37,6 @@ class WebSocketProtocol(HttpProtocol):
self.websocket: Optional[WebsocketImplProtocol] = None self.websocket: Optional[WebsocketImplProtocol] = None
self.websocket_timeout = websocket_timeout self.websocket_timeout = websocket_timeout
self.websocket_max_size = websocket_max_size self.websocket_max_size = websocket_max_size
if websocket_max_queue is not None and websocket_max_queue > 0:
# TODO: Reminder remove this warning in v22.3
deprecation(
"Websocket no longer uses queueing, so websocket_max_queue"
" is no longer required.",
22.3,
)
if websocket_read_limit is not None and websocket_read_limit > 0:
# TODO: Reminder remove this warning in v22.3
deprecation(
"Websocket no longer uses read buffers, so "
"websocket_read_limit is not required.",
22.3,
)
if websocket_write_limit is not None and websocket_write_limit > 0:
# TODO: Reminder remove this warning in v22.3
deprecation(
"Websocket no longer uses write buffers, so "
"websocket_write_limit is not required.",
22.3,
)
self.websocket_ping_interval = websocket_ping_interval self.websocket_ping_interval = websocket_ping_interval
self.websocket_ping_timeout = websocket_ping_timeout self.websocket_ping_timeout = websocket_ping_timeout

34
tests/asyncmock.py Normal file
View File

@ -0,0 +1,34 @@
"""
For 3.7 compat
"""
from unittest.mock import Mock
class AsyncMock(Mock):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.await_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
parent = super(AsyncMock, self)
async def dummy():
self.await_count += 1
return parent.__call__(*args, **kwargs)
return dummy()
def __await__(self):
return self().__await__()
def assert_awaited_once(self):
if not self.await_count == 1:
msg = (
f"Expected to have been awaited once."
f" Awaited {self.await_count} times."
)
raise AssertionError(msg)

View File

@ -103,7 +103,7 @@ def test_tls_wrong_options(cmd):
assert not out assert not out
lines = err.decode().split("\n") lines = err.decode().split("\n")
errmsg = lines[8] errmsg = lines[6]
assert errmsg == "TLS certificates must be specified by either of:" assert errmsg == "TLS certificates must be specified by either of:"
@ -200,13 +200,25 @@ def test_num_workers(num, cmd):
assert len(worker_lines) == num * 2, f"Lines found: {lines}" assert len(worker_lines) == num * 2, f"Lines found: {lines}"
@pytest.mark.parametrize("cmd", ("--debug", "-d")) @pytest.mark.parametrize("cmd", ("--debug",))
def test_debug(cmd): def test_debug(cmd):
command = ["sanic", "fake.server.app", cmd] command = ["sanic", "fake.server.app", cmd]
out, err, exitcode = capture(command) out, err, exitcode = capture(command)
lines = out.split(b"\n") lines = out.split(b"\n")
info = read_app_info(lines) info = read_app_info(lines)
assert info["debug"] is True
assert info["auto_reload"] is False
assert "dev" not in info
@pytest.mark.parametrize("cmd", ("--dev", "-d"))
def test_dev(cmd):
command = ["sanic", "fake.server.app", cmd]
out, err, exitcode = capture(command)
lines = out.split(b"\n")
info = read_app_info(lines)
assert info["debug"] is True assert info["debug"] is True
assert info["auto_reload"] is True assert info["auto_reload"] is True
@ -220,6 +232,7 @@ def test_auto_reload(cmd):
assert info["debug"] is False assert info["debug"] is False
assert info["auto_reload"] is True assert info["auto_reload"] is True
assert "dev" not in info
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -67,7 +67,7 @@ def test_auto_fallback_with_data(app):
_, response = app.test_client.get("/error") _, response = app.test_client.get("/error")
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/plain; charset=utf-8"
_, response = app.test_client.post("/error", json={"foo": "bar"}) _, response = app.test_client.post("/error", json={"foo": "bar"})
assert response.status == 500 assert response.status == 500
@ -75,7 +75,7 @@ def test_auto_fallback_with_data(app):
_, response = app.test_client.post("/error", data={"foo": "bar"}) _, response = app.test_client.post("/error", data={"foo": "bar"})
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/plain; charset=utf-8"
def test_auto_fallback_with_content_type(app): def test_auto_fallback_with_content_type(app):
@ -91,7 +91,7 @@ def test_auto_fallback_with_content_type(app):
"/error", headers={"content-type": "foo/bar", "accept": "*/*"} "/error", headers={"content-type": "foo/bar", "accept": "*/*"}
) )
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/plain; charset=utf-8"
def test_route_error_format_set_on_auto(app): def test_route_error_format_set_on_auto(app):
@ -174,6 +174,17 @@ def test_route_error_format_unknown(app):
... ...
def test_fallback_with_content_type_html(app):
app.config.FALLBACK_ERROR_FORMAT = "auto"
_, response = app.test_client.get(
"/error",
headers={"content-type": "application/json", "accept": "text/html"},
)
assert response.status == 500
assert response.content_type == "text/html; charset=utf-8"
def test_fallback_with_content_type_mismatch_accept(app): def test_fallback_with_content_type_mismatch_accept(app):
app.config.FALLBACK_ERROR_FORMAT = "auto" app.config.FALLBACK_ERROR_FORMAT = "auto"
@ -186,10 +197,10 @@ def test_fallback_with_content_type_mismatch_accept(app):
_, response = app.test_client.get( _, response = app.test_client.get(
"/error", "/error",
headers={"content-type": "text/plain", "accept": "foo/bar"}, headers={"content-type": "text/html", "accept": "foo/bar"},
) )
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/plain; charset=utf-8"
app.router.reset() app.router.reset()
@ -208,7 +219,7 @@ def test_fallback_with_content_type_mismatch_accept(app):
headers={"accept": "foo/bar"}, headers={"accept": "foo/bar"},
) )
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/plain; charset=utf-8"
_, response = app.test_client.get( _, response = app.test_client.get(
"/alt1", "/alt1",
headers={"accept": "foo/bar,*/*"}, headers={"accept": "foo/bar,*/*"},
@ -221,7 +232,7 @@ def test_fallback_with_content_type_mismatch_accept(app):
headers={"accept": "foo/bar"}, headers={"accept": "foo/bar"},
) )
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/plain; charset=utf-8"
_, response = app.test_client.get( _, response = app.test_client.get(
"/alt2", "/alt2",
headers={"accept": "foo/bar,*/*"}, headers={"accept": "foo/bar,*/*"},
@ -234,6 +245,13 @@ def test_fallback_with_content_type_mismatch_accept(app):
headers={"accept": "foo/bar"}, headers={"accept": "foo/bar"},
) )
assert response.status == 500 assert response.status == 500
assert response.content_type == "text/plain; charset=utf-8"
_, response = app.test_client.get(
"/alt3",
headers={"accept": "foo/bar,text/html"},
)
assert response.status == 500
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/html; charset=utf-8"
@ -288,6 +306,10 @@ def test_allow_fallback_error_format_set_main_process_start(app):
def test_setting_fallback_on_config_changes_as_expected(app): def test_setting_fallback_on_config_changes_as_expected(app):
app.error_handler = ErrorHandler() app.error_handler = ErrorHandler()
_, response = app.test_client.get("/error")
assert response.content_type == "text/plain; charset=utf-8"
app.config.FALLBACK_ERROR_FORMAT = "html"
_, response = app.test_client.get("/error") _, response = app.test_client.get("/error")
assert response.content_type == "text/html; charset=utf-8" assert response.content_type == "text/html; charset=utf-8"

View File

@ -34,6 +34,7 @@ class SanicExceptionTestException(Exception):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def exception_app(): def exception_app():
app = Sanic("test_exceptions") app = Sanic("test_exceptions")
app.config.FALLBACK_ERROR_FORMAT = "html"
@app.route("/") @app.route("/")
def handler(request): def handler(request):

View File

@ -216,31 +216,6 @@ def test_exception_handler_processed_request_middleware(
assert response.text == "Done." assert response.text == "Done."
def test_single_arg_exception_handler_notice(
exception_handler_app: Sanic, caplog: LogCaptureFixture
):
class CustomErrorHandler(ErrorHandler):
def lookup(self, exception):
return super().lookup(exception, None)
exception_handler_app.error_handler = CustomErrorHandler()
message = (
"[DEPRECATION v22.3] You are using a deprecated error handler. The "
"lookup method should accept two positional parameters: (exception, "
"route_name: Optional[str]). Until you upgrade your "
"ErrorHandler.lookup, Blueprint specific exceptions will not work "
"properly. Beginning in v22.3, the legacy style lookup method will "
"not work at all."
)
with pytest.warns(DeprecationWarning) as record:
_, response = exception_handler_app.test_client.get("/1")
assert len(record) == 1
assert record[0].message.args[0] == message
assert response.status == 400
def test_error_handler_noisy_log( def test_error_handler_noisy_log(
exception_handler_app: Sanic, monkeypatch: MonkeyPatch exception_handler_app: Sanic, monkeypatch: MonkeyPatch
): ):
@ -279,7 +254,7 @@ def test_exception_handler_response_was_sent(
@app.route("/2") @app.route("/2")
async def handler2(request: Request): async def handler2(request: Request):
response = await request.respond() await request.respond()
raise ServerError("Exception") raise ServerError("Exception")
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):

View File

@ -1,7 +1,7 @@
import re import re
from asyncio import Event, Queue, TimeoutError from asyncio import Event, Queue, TimeoutError
from unittest.mock import AsyncMock, Mock, call from unittest.mock import Mock, call
import pytest import pytest
@ -11,6 +11,12 @@ from sanic.exceptions import ServerError
from sanic.server.websockets.frame import WebsocketFrameAssembler from sanic.server.websockets.frame import WebsocketFrameAssembler
try:
from unittest.mock import AsyncMock
except ImportError:
from asyncmock import AsyncMock # type: ignore
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_ws_frame_get_message_incomplete_timeout_0(): async def test_ws_frame_get_message_incomplete_timeout_0():
assembler = WebsocketFrameAssembler(Mock()) assembler = WebsocketFrameAssembler(Mock())