From 86ae5f981cbe64fd85bee34fee3989b009e90dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20P=C3=A9rez?= <25409753+prryplatypus@users.noreply.github.com> Date: Thu, 12 May 2022 19:39:35 +0200 Subject: [PATCH] refactor: consistent exception naming (#2420) Co-authored-by: Adam Hopkins --- sanic/app.py | 4 ++-- sanic/errorpages.py | 4 ++-- sanic/exceptions.py | 28 ++++++++++++++++++++-------- sanic/handlers.py | 10 +++++----- sanic/http.py | 24 ++++++++++++------------ sanic/mixins/listeners.py | 4 ++-- sanic/mixins/routes.py | 8 ++++---- sanic/models/asgi.py | 4 ++-- sanic/request.py | 4 ++-- sanic/router.py | 4 ++-- tests/test_asgi.py | 4 ++-- tests/test_blueprint_group.py | 6 +++--- tests/test_blueprints.py | 4 ++-- tests/test_exceptions.py | 18 ++++++++++++++++-- tests/test_exceptions_handler.py | 4 ++-- tests/test_server_events.py | 4 ++-- tests/test_signal_handlers.py | 4 ++-- tests/test_views.py | 1 - 18 files changed, 82 insertions(+), 57 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 4dbb1b2e..b79d16e4 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -58,7 +58,7 @@ from sanic.blueprints import Blueprint from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support from sanic.config import SANIC_PREFIX, Config from sanic.exceptions import ( - InvalidUsage, + BadRequest, SanicException, ServerError, URLBuildError, @@ -281,7 +281,7 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): valid = ", ".join( map(lambda x: x.lower(), ListenerEvent.__members__.keys()) ) - raise InvalidUsage(f"Invalid event: {event}. Use one of: {valid}") + raise BadRequest(f"Invalid event: {event}. Use one of: {valid}") if "." in _event: self.signal(_event.value)( diff --git a/sanic/errorpages.py b/sanic/errorpages.py index 06495b0a..e5d123b1 100644 --- a/sanic/errorpages.py +++ b/sanic/errorpages.py @@ -19,7 +19,7 @@ import typing as t from functools import partial from traceback import extract_tb -from sanic.exceptions import InvalidUsage, SanicException +from sanic.exceptions import BadRequest, SanicException from sanic.helpers import STATUS_CODES from sanic.request import Request from sanic.response import HTTPResponse, html, json, text @@ -506,7 +506,7 @@ def exception_response( # $ curl localhost:8000 -d '{"foo": "bar"}' # And provide them with JSONRenderer renderer = JSONRenderer if request.json else base - except InvalidUsage: + except BadRequest: renderer = base else: renderer = RENDERERS_BY_CONFIG.get(render_format, renderer) diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 7d9da083..720a5834 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -42,7 +42,7 @@ class NotFound(SanicException): quiet = True -class InvalidUsage(SanicException): +class BadRequest(SanicException): """ **Status**: 400 Bad Request """ @@ -51,11 +51,14 @@ class InvalidUsage(SanicException): quiet = True -class BadURL(InvalidUsage): +InvalidUsage = BadRequest + + +class BadURL(BadRequest): ... -class MethodNotSupported(SanicException): +class MethodNotAllowed(SanicException): """ **Status**: 405 Method Not Allowed """ @@ -68,6 +71,9 @@ class MethodNotSupported(SanicException): self.headers = {"Allow": ", ".join(allowed_methods)} +MethodNotSupported = MethodNotAllowed + + class ServerError(SanicException): """ **Status**: 500 Internal Server Error @@ -129,19 +135,19 @@ class PayloadTooLarge(SanicException): quiet = True -class HeaderNotFound(InvalidUsage): +class HeaderNotFound(BadRequest): """ **Status**: 400 Bad Request """ -class InvalidHeader(InvalidUsage): +class InvalidHeader(BadRequest): """ **Status**: 400 Bad Request """ -class ContentRangeError(SanicException): +class RangeNotSatisfiable(SanicException): """ **Status**: 416 Range Not Satisfiable """ @@ -154,7 +160,10 @@ class ContentRangeError(SanicException): self.headers = {"Content-Range": f"bytes */{content_range.total}"} -class HeaderExpectationFailed(SanicException): +ContentRangeError = RangeNotSatisfiable + + +class ExpectationFailed(SanicException): """ **Status**: 417 Expectation Failed """ @@ -163,6 +172,9 @@ class HeaderExpectationFailed(SanicException): quiet = True +HeaderExpectationFailed = ExpectationFailed + + class Forbidden(SanicException): """ **Status**: 403 Forbidden @@ -172,7 +184,7 @@ class Forbidden(SanicException): quiet = True -class InvalidRangeType(ContentRangeError): +class InvalidRangeType(RangeNotSatisfiable): """ **Status**: 416 Range Not Satisfiable """ diff --git a/sanic/handlers.py b/sanic/handlers.py index 917edd23..69b2b3f2 100644 --- a/sanic/handlers.py +++ b/sanic/handlers.py @@ -10,9 +10,9 @@ from sanic.errorpages import ( exception_response, ) from sanic.exceptions import ( - ContentRangeError, HeaderNotFound, InvalidRangeType, + RangeNotSatisfiable, SanicException, ) from sanic.helpers import Default, _default @@ -296,18 +296,18 @@ class ContentRangeHandler: try: self.start = int(start_b) if start_b else None except ValueError: - raise ContentRangeError( + raise RangeNotSatisfiable( "'%s' is invalid for Content Range" % (start_b,), self ) try: self.end = int(end_b) if end_b else None except ValueError: - raise ContentRangeError( + raise RangeNotSatisfiable( "'%s' is invalid for Content Range" % (end_b,), self ) if self.end is None: if self.start is None: - raise ContentRangeError( + raise RangeNotSatisfiable( "Invalid for Content Range parameters", self ) else: @@ -319,7 +319,7 @@ class ContentRangeHandler: self.start = self.total - self.end self.end = self.total - 1 if self.start >= self.end: - raise ContentRangeError( + raise RangeNotSatisfiable( "Invalid for Content Range parameters", self ) self.size = self.end - self.start + 1 diff --git a/sanic/http.py b/sanic/http.py index c7523f33..330732b2 100644 --- a/sanic/http.py +++ b/sanic/http.py @@ -12,8 +12,8 @@ from enum import Enum from sanic.compat import Header from sanic.exceptions import ( - HeaderExpectationFailed, - InvalidUsage, + BadRequest, + ExpectationFailed, PayloadTooLarge, ServerError, ServiceUnavailable, @@ -53,14 +53,14 @@ class Http(metaclass=TouchUpMeta): :raises ServerError: :raises PayloadTooLarge: :raises Exception: - :raises InvalidUsage: - :raises HeaderExpectationFailed: + :raises BadRequest: + :raises ExpectationFailed: :raises RuntimeError: :raises ServerError: :raises ServerError: - :raises InvalidUsage: - :raises InvalidUsage: - :raises InvalidUsage: + :raises BadRequest: + :raises BadRequest: + :raises BadRequest: :raises PayloadTooLarge: :raises RuntimeError: """ @@ -248,7 +248,7 @@ class Http(metaclass=TouchUpMeta): headers.append(h) except Exception: - raise InvalidUsage("Bad Request") + raise BadRequest("Bad Request") headers_instance = Header(headers) self.upgrade_websocket = ( @@ -281,7 +281,7 @@ class Http(metaclass=TouchUpMeta): if expect.lower() == "100-continue": self.expecting_continue = True else: - raise HeaderExpectationFailed(f"Unknown Expect: {expect}") + raise ExpectationFailed(f"Unknown Expect: {expect}") if headers.getone("transfer-encoding", None) == "chunked": self.request_body = "chunked" @@ -510,7 +510,7 @@ class Http(metaclass=TouchUpMeta): if len(buf) > 64: self.keep_alive = False - raise InvalidUsage("Bad chunked encoding") + raise BadRequest("Bad chunked encoding") await self._receive_more() @@ -518,14 +518,14 @@ class Http(metaclass=TouchUpMeta): size = int(buf[2:pos].split(b";", 1)[0].decode(), 16) except Exception: self.keep_alive = False - raise InvalidUsage("Bad chunked encoding") + raise BadRequest("Bad chunked encoding") if size <= 0: self.request_body = None if size < 0: self.keep_alive = False - raise InvalidUsage("Bad chunked encoding") + raise BadRequest("Bad chunked encoding") # Consume CRLF, chunk size 0 and the two CRLF that follow pos += 4 diff --git a/sanic/mixins/listeners.py b/sanic/mixins/listeners.py index e8effa13..c60725ee 100644 --- a/sanic/mixins/listeners.py +++ b/sanic/mixins/listeners.py @@ -3,7 +3,7 @@ from functools import partial from typing import Callable, List, Optional, Union, overload from sanic.base.meta import SanicMeta -from sanic.exceptions import InvalidUsage +from sanic.exceptions import BadRequest from sanic.models.futures import FutureListener from sanic.models.handler_types import ListenerType, Sanic @@ -86,7 +86,7 @@ class ListenerMixin(metaclass=SanicMeta): if callable(listener_or_event): if event_or_none is None: - raise InvalidUsage( + raise BadRequest( "Invalid event registration: Missing event name." ) return register_listener(listener_or_event, event_or_none) diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index 9e2cf96f..3e43c6b3 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -18,10 +18,10 @@ from sanic.compat import stat_async from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS from sanic.errorpages import RESPONSE_MAPPING from sanic.exceptions import ( - ContentRangeError, + BadRequest, FileNotFound, HeaderNotFound, - InvalidUsage, + RangeNotSatisfiable, ) from sanic.handlers import ContentRangeHandler from sanic.log import deprecation, error_logger @@ -778,7 +778,7 @@ class RouteMixin(metaclass=SanicMeta): # Using this to determine if the URL is trying to break out of the path # served. os.path.realpath seems to be very slow if __file_uri__ and "../" in __file_uri__: - raise InvalidUsage("Invalid URL") + raise BadRequest("Invalid URL") # Merge served directory and requested file if provided # Strip all / that in the beginning of the URL to help prevent python # from herping a derp and treating the uri as an absolute path @@ -865,7 +865,7 @@ class RouteMixin(metaclass=SanicMeta): file_path, headers=headers, _range=_range ) return await file(file_path, headers=headers, _range=_range) - except ContentRangeError: + except RangeNotSatisfiable: raise except FileNotFoundError: raise FileNotFound( diff --git a/sanic/models/asgi.py b/sanic/models/asgi.py index 74e0e8b2..2b0ee0ed 100644 --- a/sanic/models/asgi.py +++ b/sanic/models/asgi.py @@ -3,7 +3,7 @@ import sys from typing import Any, Awaitable, Callable, MutableMapping, Optional, Union -from sanic.exceptions import InvalidUsage +from sanic.exceptions import BadRequest from sanic.server.websockets.connection import WebSocketConnection @@ -84,7 +84,7 @@ class MockTransport: # no cov try: return self._websocket_connection except AttributeError: - raise InvalidUsage("Improper websocket connection.") + raise BadRequest("Improper websocket connection.") def create_websocket_connection( self, send: ASGISend, receive: ASGIReceive diff --git a/sanic/request.py b/sanic/request.py index 3b0153f5..5405de0b 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -35,7 +35,7 @@ from httptools.parser.errors import HttpParserInvalidURLError # type: ignore from sanic.compat import CancelledErrors, Header from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE -from sanic.exceptions import BadURL, InvalidUsage, ServerError +from sanic.exceptions import BadRequest, BadURL, ServerError from sanic.headers import ( AcceptContainer, Options, @@ -379,7 +379,7 @@ class Request: except Exception: if not self.body: return None - raise InvalidUsage("Failed when parsing body as json") + raise BadRequest("Failed when parsing body as json") return self.parsed_json diff --git a/sanic/router.py b/sanic/router.py index bad471c6..01c268b9 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -14,7 +14,7 @@ from sanic_routing.route import Route # type: ignore from sanic.constants import HTTP_METHODS from sanic.errorpages import check_error_format -from sanic.exceptions import MethodNotSupported, NotFound, SanicException +from sanic.exceptions import MethodNotAllowed, NotFound, SanicException from sanic.models.handler_types import RouteHandler @@ -43,7 +43,7 @@ class Router(BaseRouter): except RoutingNotFound as e: raise NotFound("Requested URL {} not found".format(e.path)) except NoMethod as e: - raise MethodNotSupported( + raise MethodNotAllowed( "Method {} not allowed for URL {}".format(method, path), method=method, allowed_methods=e.allowed_methods, diff --git a/tests/test_asgi.py b/tests/test_asgi.py index 3687f576..c51c657f 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -9,7 +9,7 @@ import uvicorn from sanic import Sanic from sanic.application.state import Mode from sanic.asgi import MockTransport -from sanic.exceptions import Forbidden, InvalidUsage, ServiceUnavailable +from sanic.exceptions import Forbidden, BadRequest, ServiceUnavailable from sanic.request import Request from sanic.response import json, text from sanic.server.websockets.connection import WebSocketConnection @@ -392,7 +392,7 @@ async def test_websocket_accept_with_multiple_subprotocols( def test_improper_websocket_connection(transport, send, receive): - with pytest.raises(InvalidUsage): + with pytest.raises(BadRequest): transport.get_websocket_connection() transport.create_websocket_connection(send, receive) diff --git a/tests/test_blueprint_group.py b/tests/test_blueprint_group.py index 09729c15..4c99b42b 100644 --- a/tests/test_blueprint_group.py +++ b/tests/test_blueprint_group.py @@ -5,7 +5,7 @@ from sanic.blueprint_group import BlueprintGroup from sanic.blueprints import Blueprint from sanic.exceptions import ( Forbidden, - InvalidUsage, + BadRequest, SanicException, ServerError, ) @@ -104,7 +104,7 @@ def test_bp_group(app: Sanic): @blueprint_1.route("/invalid") def blueprint_1_error(request: Request): - raise InvalidUsage("Invalid") + raise BadRequest("Invalid") @blueprint_2.route("/") def blueprint_2_default_route(request): @@ -120,7 +120,7 @@ def test_bp_group(app: Sanic): blueprint_3 = Blueprint("blueprint_3", url_prefix="/bp3") - @blueprint_group_1.exception(InvalidUsage) + @blueprint_group_1.exception(BadRequest) def handle_group_exception(request, exception): return text("BP1_ERR_OK") diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 9c326ded..543d472b 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -8,7 +8,7 @@ from sanic.app import Sanic from sanic.blueprints import Blueprint from sanic.constants import HTTP_METHODS from sanic.exceptions import ( - InvalidUsage, + BadRequest, NotFound, SanicException, ServerError, @@ -448,7 +448,7 @@ def test_bp_exception_handler(app): @blueprint.route("/1") def handler_1(request): - raise InvalidUsage("OK") + raise BadRequest("OK") @blueprint.route("/2") def handler_2(request): diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index e603718d..5ab86786 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -6,9 +6,16 @@ from bs4 import BeautifulSoup from sanic import Sanic from sanic.exceptions import ( + BadRequest, + ContentRangeError, + ExpectationFailed, Forbidden, + HeaderExpectationFailed, InvalidUsage, + MethodNotAllowed, + MethodNotSupported, NotFound, + RangeNotSatisfiable, SanicException, ServerError, Unauthorized, @@ -77,7 +84,7 @@ def exception_app(): @app.route("/invalid") def handler_invalid(request): - raise InvalidUsage("OK") + raise BadRequest("OK") @app.route("/abort/401") def handler_401_error(request): @@ -136,7 +143,7 @@ def test_server_error_exception(exception_app): def test_invalid_usage_exception(exception_app): - """Test the built-in InvalidUsage exception works""" + """Test the built-in BadRequest exception works""" request, response = exception_app.test_client.get("/invalid") assert response.status == 400 @@ -375,3 +382,10 @@ def test_contextual_exception_functional_message(override): assert response.status == 418 assert response.json["message"] == error_message assert response.json["context"] == {"foo": "bar"} + + +def test_exception_aliases(): + assert InvalidUsage is BadRequest + assert MethodNotSupported is MethodNotAllowed + assert ContentRangeError is RangeNotSatisfiable + assert HeaderExpectationFailed is ExpectationFailed diff --git a/tests/test_exceptions_handler.py b/tests/test_exceptions_handler.py index e9bdb21e..534a6d14 100644 --- a/tests/test_exceptions_handler.py +++ b/tests/test_exceptions_handler.py @@ -10,7 +10,7 @@ from bs4 import BeautifulSoup from pytest import LogCaptureFixture, MonkeyPatch from sanic import Sanic, handlers -from sanic.exceptions import Forbidden, InvalidUsage, NotFound, ServerError +from sanic.exceptions import Forbidden, BadRequest, NotFound, ServerError from sanic.handlers import ErrorHandler from sanic.request import Request from sanic.response import stream, text @@ -32,7 +32,7 @@ def exception_handler_app(): @exception_handler_app.route("/1", error_format="html") def handler_1(request): - raise InvalidUsage("OK") + raise BadRequest("OK") @exception_handler_app.route("/2", error_format="html") def handler_2(request): diff --git a/tests/test_server_events.py b/tests/test_server_events.py index 7a529655..2333ba6b 100644 --- a/tests/test_server_events.py +++ b/tests/test_server_events.py @@ -8,7 +8,7 @@ import pytest from sanic_testing.testing import HOST, PORT -from sanic.exceptions import InvalidUsage, SanicException +from sanic.exceptions import BadRequest, SanicException AVAILABLE_LISTENERS = [ @@ -137,7 +137,7 @@ async def test_trigger_before_events_create_server_missing_event(app): class MySanicDb: pass - with pytest.raises(InvalidUsage): + with pytest.raises(BadRequest): @app.listener async def init_db(app, loop): diff --git a/tests/test_signal_handlers.py b/tests/test_signal_handlers.py index ee28f548..8b823ba0 100644 --- a/tests/test_signal_handlers.py +++ b/tests/test_signal_handlers.py @@ -11,7 +11,7 @@ import pytest from sanic_testing.testing import HOST, PORT from sanic.compat import ctrlc_workaround_for_windows -from sanic.exceptions import InvalidUsage +from sanic.exceptions import BadRequest from sanic.response import HTTPResponse @@ -122,6 +122,6 @@ def test_signals_with_invalid_invocation(app): return HTTPResponse() with pytest.raises( - InvalidUsage, match="Invalid event registration: Missing event name" + BadRequest, match="Invalid event registration: Missing event name" ): app.listener(stop) diff --git a/tests/test_views.py b/tests/test_views.py index dd9eac7b..ab35679e 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -2,7 +2,6 @@ import pytest from sanic.blueprints import Blueprint from sanic.constants import HTTP_METHODS -from sanic.exceptions import InvalidUsage from sanic.request import Request from sanic.response import HTTPResponse, text from sanic.views import HTTPMethodView