Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

22.3 Deprecations and changes #2362

Merged
merged 8 commits into from Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions sanic/app.py
Expand Up @@ -1061,6 +1061,7 @@ def run(
host: Optional[str] = None,
port: Optional[int] = None,
*,
dev: bool = False,
debug: bool = False,
auto_reload: Optional[bool] = None,
ssl: Union[None, SSLContext, dict, str, list, tuple] = None,
Expand Down Expand Up @@ -1143,10 +1144,12 @@ def run(
"#asynchronous-support"
)

if auto_reload or auto_reload is None and debug:
if dev:
debug = True
auto_reload = True
if os.environ.get("SANIC_SERVER_RUNNING") != "true":
return reloader_helpers.watchdog(1.0, self)

if auto_reload and os.environ.get("SANIC_SERVER_RUNNING") != "true":
return reloader_helpers.watchdog(1.0, self)

if sock is None:
host, port = host or "127.0.0.1", port or 8000
Expand Down
12 changes: 3 additions & 9 deletions sanic/cli/app.py
Expand Up @@ -79,13 +79,6 @@ def run(self):
error_logger.exception("Failed to run app")

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
if self.main_process and (
# one of cert/key missing
Expand Down Expand Up @@ -174,8 +167,9 @@ def _build_run_kwargs(self):
"workers": self.args.workers,
}

if self.args.auto_reload:
kwargs["auto_reload"] = True
for maybe_arg in ("auto_reload", "dev"):
if getattr(self.args, maybe_arg, False):
kwargs[maybe_arg] = True

if self.args.path:
if self.args.auto_reload or self.args.debug:
Expand Down
15 changes: 7 additions & 8 deletions sanic/cli/arguments.py
Expand Up @@ -180,19 +180,18 @@ def attach(self):
"--debug",
dest="debug",
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(
"-d",
"--dev",
dest="debug",
dest="dev",
action="store_true",
help=(
"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."
),
help=("Debug + auto_reload."),
)
self.container.add_argument(
"-r",
Expand Down
40 changes: 6 additions & 34 deletions sanic/handlers.py
@@ -1,13 +1,12 @@
from __future__ import annotations

from inspect import signature
from typing import Dict, List, Optional, Tuple, Type, Union

from sanic.config import Config
from sanic.errorpages import (
DEFAULT_FORMAT,
BaseRenderer,
HTMLRenderer,
TextRenderer,
exception_response,
)
from sanic.exceptions import (
Expand Down Expand Up @@ -35,13 +34,11 @@ class ErrorHandler:

"""

# Beginning in v22.3, the base renderer will be TextRenderer
def __init__(
self,
fallback: Union[str, Default] = _default,
base: Type[BaseRenderer] = HTMLRenderer,
base: Type[BaseRenderer] = TextRenderer,
):
self.handlers: List[Tuple[Type[BaseException], RouteHandler]] = []
self.cached_handlers: Dict[
Tuple[Type[BaseException], Optional[str]], Optional[RouteHandler]
] = {}
Expand Down Expand Up @@ -95,8 +92,8 @@ def _get_fallback_value(cls, error_handler: ErrorHandler, config: Config):
def finalize(
cls,
error_handler: ErrorHandler,
config: Config,
fallback: Optional[str] = None,
config: Optional[Config] = None,
):
if fallback:
deprecation(
Expand All @@ -107,14 +104,10 @@ def finalize(
22.6,
)

if config is None:
deprecation(
"Starting in v22.3, config will be a required argument "
"for ErrorHandler.finalize().",
22.3,
)
if not fallback:
fallback = config.FALLBACK_ERROR_FORMAT

if fallback and fallback != DEFAULT_FORMAT:
if fallback != DEFAULT_FORMAT:
if error_handler._fallback is not _default:
error_logger.warning(
f"Setting the fallback value to {fallback}. This changes "
Expand All @@ -128,27 +121,9 @@ def finalize(
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):
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):
"""
Add a new exception handler to an already existing handler object.
Expand All @@ -162,9 +137,6 @@ def add(self, exception, handler, route_names: Optional[List[str]] = None):

:return: None
"""
# self.handlers is deprecated and will be removed in version 22.3
self.handlers.append((exception, handler))

if route_names:
for route in route_names:
self.cached_handlers[(exception, route)] = handler
Expand Down
26 changes: 1 addition & 25 deletions sanic/server/protocols/websocket_protocol.py
Expand Up @@ -5,7 +5,7 @@
from websockets.typing import Subprotocol

from sanic.exceptions import ServerError
from sanic.log import deprecation, error_logger
from sanic.log import error_logger
from sanic.server import HttpProtocol

from ..websockets.impl import WebsocketImplProtocol
Expand All @@ -29,9 +29,6 @@ def __init__(
*args,
websocket_timeout: float = 10.0,
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_timeout: Optional[float] = 20.0,
**kwargs,
Expand All @@ -40,27 +37,6 @@ def __init__(
self.websocket: Optional[WebsocketImplProtocol] = None
self.websocket_timeout = websocket_timeout
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_timeout = websocket_ping_timeout

Expand Down
34 changes: 34 additions & 0 deletions tests/asyncmock.py
@@ -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)
17 changes: 15 additions & 2 deletions tests/test_cli.py
Expand Up @@ -103,7 +103,7 @@ def test_tls_wrong_options(cmd):
assert not out
lines = err.decode().split("\n")

errmsg = lines[8]
errmsg = lines[6]
assert errmsg == "TLS certificates must be specified by either of:"


Expand Down Expand Up @@ -200,13 +200,25 @@ def test_num_workers(num, cmd):
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):
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["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["auto_reload"] is True

Expand All @@ -220,6 +232,7 @@ def test_auto_reload(cmd):

assert info["debug"] is False
assert info["auto_reload"] is True
assert "dev" not in info


@pytest.mark.parametrize(
Expand Down
36 changes: 29 additions & 7 deletions tests/test_errorpages.py
Expand Up @@ -67,15 +67,15 @@ def test_auto_fallback_with_data(app):

_, response = app.test_client.get("/error")
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"})
assert response.status == 500
assert response.content_type == "application/json"

_, response = app.test_client.post("/error", data={"foo": "bar"})
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):
Expand All @@ -91,7 +91,7 @@ def test_auto_fallback_with_content_type(app):
"/error", headers={"content-type": "foo/bar", "accept": "*/*"}
)
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):
Expand Down Expand Up @@ -174,6 +174,17 @@ def handler(request):
...


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):
app.config.FALLBACK_ERROR_FORMAT = "auto"

Expand All @@ -186,10 +197,10 @@ def test_fallback_with_content_type_mismatch_accept(app):

_, response = app.test_client.get(
"/error",
headers={"content-type": "text/plain", "accept": "foo/bar"},
headers={"content-type": "text/html", "accept": "foo/bar"},
)
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()

Expand All @@ -208,7 +219,7 @@ def handler(_):
headers={"accept": "foo/bar"},
)
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(
"/alt1",
headers={"accept": "foo/bar,*/*"},
Expand All @@ -221,7 +232,7 @@ def handler(_):
headers={"accept": "foo/bar"},
)
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(
"/alt2",
headers={"accept": "foo/bar,*/*"},
Expand All @@ -234,6 +245,13 @@ def handler(_):
headers={"accept": "foo/bar"},
)
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"


Expand Down Expand Up @@ -288,6 +306,10 @@ async def start(app, _):
def test_setting_fallback_on_config_changes_as_expected(app):
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")
assert response.content_type == "text/html; charset=utf-8"

Expand Down