Release 21.9.3 (#2318)
* Allow non-conforming ErrorHandlers (#2259) * Allow non-conforming ErrorHandlers * Rename to legacy lookup * Updated depnotice * Bump version * Fix formatting * Remove unused import * Fix error messages * Add error format commit and merge conflicts * Make HTTP connections start in IDLE stage, avoiding delays and error messages (#2268) * Make all new connections start in IDLE stage, and switch to REQUEST stage only once any bytes are received from client. This makes new connections without any request obey keepalive timeout rather than request timeout like they currently do. * Revert typo * Remove request timeout endpoint test which is no longer working (still tested by mocking). Fix mock timeout test setup. Co-authored-by: L. Karkkainen <tronic@users.noreply.github.com> * Bump version * Add error format from config replacement objects * Cleanup mistaken print statement * Cleanup reversions * Bump version Co-authored-by: L. Kärkkäinen <98187+Tronic@users.noreply.github.com> Co-authored-by: L. Karkkainen <tronic@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ from os import environ
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from textwrap import dedent
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -350,3 +351,40 @@ def test_update_from_lowercase_key(app):
|
||||
d = {"test_setting_value": 1}
|
||||
app.update_config(d)
|
||||
assert "test_setting_value" not in app.config
|
||||
|
||||
|
||||
def test_deprecation_notice_when_setting_logo(app):
|
||||
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, monkeypatch):
|
||||
post_set = Mock()
|
||||
monkeypatch.setattr(Config, "_post_set", post_set)
|
||||
|
||||
app.config.FOO = 1
|
||||
post_set.assert_called_once_with("FOO", 1)
|
||||
post_set.reset_mock()
|
||||
|
||||
app.config["FOO"] = 2
|
||||
post_set.assert_called_once_with("FOO", 2)
|
||||
post_set.reset_mock()
|
||||
|
||||
app.config.update({"FOO": 3})
|
||||
post_set.assert_called_once_with("FOO", 3)
|
||||
post_set.reset_mock()
|
||||
|
||||
app.config.update([("FOO", 4)])
|
||||
post_set.assert_called_once_with("FOO", 4)
|
||||
post_set.reset_mock()
|
||||
|
||||
app.config.update(FOO=5)
|
||||
post_set.assert_called_once_with("FOO", 5)
|
||||
post_set.reset_mock()
|
||||
|
||||
app.config.update_config({"FOO": 6})
|
||||
post_set.assert_called_once_with("FOO", 6)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import pytest
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.config import Config
|
||||
from sanic.errorpages import HTMLRenderer, exception_response
|
||||
from sanic.exceptions import NotFound, SanicException
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic.request import Request
|
||||
from sanic.response import HTTPResponse, html, json, text
|
||||
|
||||
@@ -271,3 +273,72 @@ def test_combinations_for_auto(fake_request, accept, content_type, expected):
|
||||
)
|
||||
|
||||
assert response.content_type == expected
|
||||
|
||||
|
||||
def test_allow_fallback_error_format_set_main_process_start(app):
|
||||
@app.main_process_start
|
||||
async def start(app, _):
|
||||
app.config.FALLBACK_ERROR_FORMAT = "text"
|
||||
|
||||
request, response = app.test_client.get("/error")
|
||||
assert request.app.error_handler.fallback == "text"
|
||||
assert response.status == 500
|
||||
assert response.content_type == "text/plain; charset=utf-8"
|
||||
|
||||
|
||||
def test_setting_fallback_to_non_default_raise_warning(app):
|
||||
app.error_handler = ErrorHandler(fallback="text")
|
||||
|
||||
assert app.error_handler.fallback == "text"
|
||||
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match=(
|
||||
"Overriding non-default ErrorHandler fallback value. "
|
||||
"Changing from text to auto."
|
||||
),
|
||||
):
|
||||
app.config.FALLBACK_ERROR_FORMAT = "auto"
|
||||
|
||||
assert app.error_handler.fallback == "auto"
|
||||
|
||||
app.config.FALLBACK_ERROR_FORMAT = "text"
|
||||
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match=(
|
||||
"Overriding non-default ErrorHandler fallback value. "
|
||||
"Changing from text to json."
|
||||
),
|
||||
):
|
||||
app.config.FALLBACK_ERROR_FORMAT = "json"
|
||||
|
||||
assert app.error_handler.fallback == "json"
|
||||
|
||||
|
||||
def test_allow_fallback_error_format_in_config_injection():
|
||||
class MyConfig(Config):
|
||||
FALLBACK_ERROR_FORMAT = "text"
|
||||
|
||||
app = Sanic("test", config=MyConfig())
|
||||
|
||||
@app.route("/error", methods=["GET", "POST"])
|
||||
def err(request):
|
||||
raise Exception("something went wrong")
|
||||
|
||||
request, response = app.test_client.get("/error")
|
||||
assert request.app.error_handler.fallback == "text"
|
||||
assert response.status == 500
|
||||
assert response.content_type == "text/plain; charset=utf-8"
|
||||
|
||||
|
||||
def test_allow_fallback_error_format_in_config_replacement(app):
|
||||
class MyConfig(Config):
|
||||
FALLBACK_ERROR_FORMAT = "text"
|
||||
|
||||
app.config = MyConfig()
|
||||
|
||||
request, response = app.test_client.get("/error")
|
||||
assert request.app.error_handler.fallback == "text"
|
||||
assert response.status == 500
|
||||
assert response.content_type == "text/plain; charset=utf-8"
|
||||
|
||||
@@ -4,6 +4,7 @@ import warnings
|
||||
import pytest
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from websockets.version import version as websockets_version
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.exceptions import (
|
||||
@@ -16,7 +17,6 @@ from sanic.exceptions import (
|
||||
abort,
|
||||
)
|
||||
from sanic.response import text
|
||||
from websockets.version import version as websockets_version
|
||||
|
||||
|
||||
class SanicExceptionTestException(Exception):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -206,3 +207,23 @@ def test_exception_handler_processed_request_middleware(exception_handler_app):
|
||||
request, response = exception_handler_app.test_client.get("/8")
|
||||
assert response.status == 200
|
||||
assert response.text == "Done."
|
||||
|
||||
|
||||
def test_single_arg_exception_handler_notice(exception_handler_app, caplog):
|
||||
class CustomErrorHandler(ErrorHandler):
|
||||
def lookup(self, exception):
|
||||
return super().lookup(exception, None)
|
||||
|
||||
exception_handler_app.error_handler = CustomErrorHandler()
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_, response = exception_handler_app.test_client.get("/1")
|
||||
|
||||
assert caplog.records[0].message == (
|
||||
"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."
|
||||
)
|
||||
assert response.status == 400
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import asyncio
|
||||
|
||||
import httpcore
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from sanic_testing.testing import SanicTestClient
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import text
|
||||
|
||||
|
||||
class DelayableHTTPConnection(httpcore._async.connection.AsyncHTTPConnection):
|
||||
async def arequest(self, *args, **kwargs):
|
||||
await asyncio.sleep(2)
|
||||
return await super().arequest(*args, **kwargs)
|
||||
|
||||
async def _open_socket(self, *args, **kwargs):
|
||||
retval = await super()._open_socket(*args, **kwargs)
|
||||
if self._request_delay:
|
||||
await asyncio.sleep(self._request_delay)
|
||||
return retval
|
||||
|
||||
|
||||
class DelayableSanicConnectionPool(httpcore.AsyncConnectionPool):
|
||||
def __init__(self, request_delay=None, *args, **kwargs):
|
||||
self._request_delay = request_delay
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def _add_to_pool(self, connection, timeout):
|
||||
connection.__class__ = DelayableHTTPConnection
|
||||
connection._request_delay = self._request_delay
|
||||
await super()._add_to_pool(connection, timeout)
|
||||
|
||||
|
||||
class DelayableSanicSession(httpx.AsyncClient):
|
||||
def __init__(self, request_delay=None, *args, **kwargs) -> None:
|
||||
transport = DelayableSanicConnectionPool(request_delay=request_delay)
|
||||
super().__init__(transport=transport, *args, **kwargs)
|
||||
|
||||
|
||||
class DelayableSanicTestClient(SanicTestClient):
|
||||
def __init__(self, app, request_delay=None):
|
||||
super().__init__(app)
|
||||
self._request_delay = request_delay
|
||||
self._loop = None
|
||||
|
||||
def get_new_session(self):
|
||||
return DelayableSanicSession(request_delay=self._request_delay)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request_no_timeout_app():
|
||||
app = Sanic("test_request_no_timeout")
|
||||
app.config.REQUEST_TIMEOUT = 0.6
|
||||
|
||||
@app.route("/1")
|
||||
async def handler2(request):
|
||||
return text("OK")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request_timeout_default_app():
|
||||
app = Sanic("test_request_timeout_default")
|
||||
app.config.REQUEST_TIMEOUT = 0.6
|
||||
|
||||
@app.route("/1")
|
||||
async def handler1(request):
|
||||
return text("OK")
|
||||
|
||||
@app.websocket("/ws1")
|
||||
async def ws_handler1(request, ws):
|
||||
await ws.send("OK")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def test_default_server_error_request_timeout(request_timeout_default_app):
|
||||
client = DelayableSanicTestClient(request_timeout_default_app, 2)
|
||||
_, response = client.get("/1")
|
||||
assert response.status == 408
|
||||
assert "Request Timeout" in response.text
|
||||
|
||||
|
||||
def test_default_server_error_request_dont_timeout(request_no_timeout_app):
|
||||
client = DelayableSanicTestClient(request_no_timeout_app, 0.2)
|
||||
_, response = client.get("/1")
|
||||
assert response.status == 200
|
||||
assert response.text == "OK"
|
||||
|
||||
|
||||
def test_default_server_error_websocket_request_timeout(
|
||||
request_timeout_default_app,
|
||||
):
|
||||
|
||||
headers = {
|
||||
"Upgrade": "websocket",
|
||||
"Connection": "upgrade",
|
||||
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
||||
"Sec-WebSocket-Version": "13",
|
||||
}
|
||||
|
||||
client = DelayableSanicTestClient(request_timeout_default_app, 2)
|
||||
_, response = client.get("/ws1", headers=headers)
|
||||
|
||||
assert response.status == 408
|
||||
assert "Request Timeout" in response.text
|
||||
@@ -26,6 +26,7 @@ def protocol(app, mock_transport):
|
||||
protocol = HttpProtocol(loop=loop, app=app)
|
||||
protocol.connection_made(mock_transport)
|
||||
protocol._setup_connection()
|
||||
protocol._http.init_for_request()
|
||||
protocol._task = Mock(spec=asyncio.Task)
|
||||
protocol._task.cancel = Mock()
|
||||
return protocol
|
||||
|
||||
Reference in New Issue
Block a user