deprecate abort (#2077)
This commit is contained in:
parent
eba7821a6d
commit
42b1e7143e
|
@ -3,26 +3,18 @@ from typing import Optional, Union
|
||||||
from sanic.helpers import STATUS_CODES
|
from sanic.helpers import STATUS_CODES
|
||||||
|
|
||||||
|
|
||||||
_sanic_exceptions = {}
|
|
||||||
|
|
||||||
|
|
||||||
def add_status_code(code, quiet=None):
|
|
||||||
"""
|
|
||||||
Decorator used for adding exceptions to :class:`SanicException`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def class_decorator(cls):
|
|
||||||
cls.status_code = code
|
|
||||||
if quiet or quiet is None and code != 500:
|
|
||||||
cls.quiet = True
|
|
||||||
_sanic_exceptions[code] = cls
|
|
||||||
return cls
|
|
||||||
|
|
||||||
return class_decorator
|
|
||||||
|
|
||||||
|
|
||||||
class SanicException(Exception):
|
class SanicException(Exception):
|
||||||
def __init__(self, message, status_code=None, quiet=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: Optional[Union[str, bytes]] = None,
|
||||||
|
status_code: Optional[int] = None,
|
||||||
|
quiet: Optional[bool] = None,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
if message is None and status_code is not None:
|
||||||
|
msg: bytes = STATUS_CODES.get(status_code, b"")
|
||||||
|
message = msg.decode("utf8")
|
||||||
|
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
if status_code is not None:
|
if status_code is not None:
|
||||||
|
@ -33,45 +25,42 @@ class SanicException(Exception):
|
||||||
self.quiet = True
|
self.quiet = True
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(404)
|
|
||||||
class NotFound(SanicException):
|
class NotFound(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 404 Not Found
|
**Status**: 404 Not Found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 404
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(400)
|
|
||||||
class InvalidUsage(SanicException):
|
class InvalidUsage(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 400 Bad Request
|
**Status**: 400 Bad Request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 400
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(405)
|
|
||||||
class MethodNotSupported(SanicException):
|
class MethodNotSupported(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 405 Method Not Allowed
|
**Status**: 405 Method Not Allowed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
status_code = 405
|
||||||
|
|
||||||
def __init__(self, message, method, allowed_methods):
|
def __init__(self, message, method, allowed_methods):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.headers = {"Allow": ", ".join(allowed_methods)}
|
self.headers = {"Allow": ", ".join(allowed_methods)}
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(500)
|
|
||||||
class ServerError(SanicException):
|
class ServerError(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 500 Internal Server Error
|
**Status**: 500 Internal Server Error
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 500
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(503)
|
|
||||||
class ServiceUnavailable(SanicException):
|
class ServiceUnavailable(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 503 Service Unavailable
|
**Status**: 503 Service Unavailable
|
||||||
|
@ -80,7 +69,7 @@ class ServiceUnavailable(SanicException):
|
||||||
down for maintenance). Generally, this is a temporary state.
|
down for maintenance). Generally, this is a temporary state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 503
|
||||||
|
|
||||||
|
|
||||||
class URLBuildError(ServerError):
|
class URLBuildError(ServerError):
|
||||||
|
@ -88,7 +77,7 @@ class URLBuildError(ServerError):
|
||||||
**Status**: 500 Internal Server Error
|
**Status**: 500 Internal Server Error
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 500
|
||||||
|
|
||||||
|
|
||||||
class FileNotFound(NotFound):
|
class FileNotFound(NotFound):
|
||||||
|
@ -102,7 +91,6 @@ class FileNotFound(NotFound):
|
||||||
self.relative_url = relative_url
|
self.relative_url = relative_url
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(408)
|
|
||||||
class RequestTimeout(SanicException):
|
class RequestTimeout(SanicException):
|
||||||
"""The Web server (running the Web site) thinks that there has been too
|
"""The Web server (running the Web site) thinks that there has been too
|
||||||
long an interval of time between 1) the establishment of an IP
|
long an interval of time between 1) the establishment of an IP
|
||||||
|
@ -112,16 +100,15 @@ class RequestTimeout(SanicException):
|
||||||
server has 'timed out' on that particular socket connection.
|
server has 'timed out' on that particular socket connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 408
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(413)
|
|
||||||
class PayloadTooLarge(SanicException):
|
class PayloadTooLarge(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 413 Payload Too Large
|
**Status**: 413 Payload Too Large
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 413
|
||||||
|
|
||||||
|
|
||||||
class HeaderNotFound(InvalidUsage):
|
class HeaderNotFound(InvalidUsage):
|
||||||
|
@ -129,36 +116,35 @@ class HeaderNotFound(InvalidUsage):
|
||||||
**Status**: 400 Bad Request
|
**Status**: 400 Bad Request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 400
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(416)
|
|
||||||
class ContentRangeError(SanicException):
|
class ContentRangeError(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 416 Range Not Satisfiable
|
**Status**: 416 Range Not Satisfiable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
status_code = 416
|
||||||
|
|
||||||
def __init__(self, message, content_range):
|
def __init__(self, message, content_range):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.headers = {"Content-Range": f"bytes */{content_range.total}"}
|
self.headers = {"Content-Range": f"bytes */{content_range.total}"}
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(417)
|
|
||||||
class HeaderExpectationFailed(SanicException):
|
class HeaderExpectationFailed(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 417 Expectation Failed
|
**Status**: 417 Expectation Failed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 417
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(403)
|
|
||||||
class Forbidden(SanicException):
|
class Forbidden(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 403 Forbidden
|
**Status**: 403 Forbidden
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 403
|
||||||
|
|
||||||
|
|
||||||
class InvalidRangeType(ContentRangeError):
|
class InvalidRangeType(ContentRangeError):
|
||||||
|
@ -166,7 +152,7 @@ class InvalidRangeType(ContentRangeError):
|
||||||
**Status**: 416 Range Not Satisfiable
|
**Status**: 416 Range Not Satisfiable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
status_code = 416
|
||||||
|
|
||||||
|
|
||||||
class PyFileError(Exception):
|
class PyFileError(Exception):
|
||||||
|
@ -174,7 +160,6 @@ class PyFileError(Exception):
|
||||||
super().__init__("could not execute config file %s", file)
|
super().__init__("could not execute config file %s", file)
|
||||||
|
|
||||||
|
|
||||||
@add_status_code(401)
|
|
||||||
class Unauthorized(SanicException):
|
class Unauthorized(SanicException):
|
||||||
"""
|
"""
|
||||||
**Status**: 401 Unauthorized
|
**Status**: 401 Unauthorized
|
||||||
|
@ -210,6 +195,8 @@ class Unauthorized(SanicException):
|
||||||
realm="Restricted Area")
|
realm="Restricted Area")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
status_code = 401
|
||||||
|
|
||||||
def __init__(self, message, status_code=None, scheme=None, **kwargs):
|
def __init__(self, message, status_code=None, scheme=None, **kwargs):
|
||||||
super().__init__(message, status_code)
|
super().__init__(message, status_code)
|
||||||
|
|
||||||
|
@ -241,9 +228,13 @@ def abort(status_code: int, message: Optional[Union[str, bytes]] = None):
|
||||||
:param status_code: The HTTP status code to return.
|
:param status_code: The HTTP status code to return.
|
||||||
:param message: The HTTP response body. Defaults to the messages in
|
:param message: The HTTP response body. Defaults to the messages in
|
||||||
"""
|
"""
|
||||||
if message is None:
|
import warnings
|
||||||
msg: bytes = STATUS_CODES[status_code]
|
|
||||||
# These are stored as bytes in the STATUS_CODES dict
|
warnings.warn(
|
||||||
message = msg.decode("utf8")
|
"sanic.exceptions.abort has been marked as deprecated, and will be "
|
||||||
sanic_exception = _sanic_exceptions.get(status_code, SanicException)
|
"removed in release 21.12.\n To migrate your code, simply replace "
|
||||||
raise sanic_exception(message=message, status_code=status_code)
|
"abort(status_code, msg) with raise SanicException(msg, status_code), "
|
||||||
|
"or even better, raise an appropriate SanicException subclass."
|
||||||
|
)
|
||||||
|
|
||||||
|
raise SanicException(message=message, status_code=status_code)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
|
||||||
|
|
||||||
from collections import deque, namedtuple
|
from collections import deque, namedtuple
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
|
SanicException,
|
||||||
Forbidden,
|
Forbidden,
|
||||||
InvalidUsage,
|
InvalidUsage,
|
||||||
NotFound,
|
NotFound,
|
||||||
ServerError,
|
ServerError,
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
abort,
|
abort
|
||||||
)
|
)
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
|
||||||
|
@ -68,16 +71,19 @@ def exception_app():
|
||||||
|
|
||||||
@app.route("/abort/401")
|
@app.route("/abort/401")
|
||||||
def handler_401_error(request):
|
def handler_401_error(request):
|
||||||
abort(401)
|
raise SanicException(status_code=401)
|
||||||
|
|
||||||
@app.route("/abort")
|
@app.route("/abort")
|
||||||
def handler_500_error(request):
|
def handler_500_error(request):
|
||||||
|
raise SanicException(status_code=500)
|
||||||
|
|
||||||
|
@app.route("/old_abort")
|
||||||
|
def handler_old_abort_error(request):
|
||||||
abort(500)
|
abort(500)
|
||||||
return text("OK")
|
|
||||||
|
|
||||||
@app.route("/abort/message")
|
@app.route("/abort/message")
|
||||||
def handler_abort_message(request):
|
def handler_abort_message(request):
|
||||||
abort(500, message="Abort")
|
raise SanicException(message="Custom Message", status_code=500)
|
||||||
|
|
||||||
@app.route("/divide_by_zero")
|
@app.route("/divide_by_zero")
|
||||||
def handle_unhandled_exception(request):
|
def handle_unhandled_exception(request):
|
||||||
|
@ -208,14 +214,21 @@ def test_exception_in_exception_handler_debug_on(exception_app):
|
||||||
assert response.body.startswith(b"Exception raised in exception ")
|
assert response.body.startswith(b"Exception raised in exception ")
|
||||||
|
|
||||||
|
|
||||||
def test_abort(exception_app):
|
def test_sanic_exception(exception_app):
|
||||||
"""Test the abort function"""
|
"""Test sanic exceptions are handled"""
|
||||||
request, response = exception_app.test_client.get("/abort/401")
|
request, response = exception_app.test_client.get("/abort/401")
|
||||||
assert response.status == 401
|
assert response.status == 401
|
||||||
|
|
||||||
request, response = exception_app.test_client.get("/abort")
|
request, response = exception_app.test_client.get("/abort")
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
|
# check fallback message
|
||||||
|
assert "Internal Server Error" in response.text
|
||||||
|
|
||||||
request, response = exception_app.test_client.get("/abort/message")
|
request, response = exception_app.test_client.get("/abort/message")
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
assert "Abort" in response.text
|
assert "Custom Message" in response.text
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
request, response = exception_app.test_client.get("/old_abort")
|
||||||
|
assert response.status == 500
|
||||||
|
assert len(w) == 1 and 'deprecated' in w[0].message.args[0]
|
||||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
||||||
from asyncio import CancelledError
|
from asyncio import CancelledError
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
|
||||||
from sanic.exceptions import NotFound, SanicException
|
from sanic.exceptions import NotFound
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import HTTPResponse, text
|
from sanic.response import HTTPResponse, text
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import quote, unquote
|
from urllib.parse import quote
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import pytest
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
from sanic.server import HttpProtocol
|
|
||||||
from sanic.views import CompositionView, HTTPMethodView
|
from sanic.views import CompositionView, HTTPMethodView
|
||||||
from sanic.views import stream as stream_decorator
|
from sanic.views import stream as stream_decorator
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,10 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
import httpcore
|
import httpcore
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from httpcore._async.base import (
|
|
||||||
AsyncByteStream,
|
|
||||||
AsyncHTTPTransport,
|
|
||||||
ConnectionState,
|
|
||||||
NewConnectionRequired,
|
|
||||||
)
|
|
||||||
from httpcore._async.connection import AsyncHTTPConnection
|
|
||||||
from httpcore._async.connection_pool import ResponseByteStream
|
|
||||||
from httpcore._exceptions import LocalProtocolError, UnsupportedProtocol
|
|
||||||
from httpcore._types import TimeoutDict
|
|
||||||
from httpcore._utils import url_to_origin
|
|
||||||
from sanic_testing.testing import SanicTestClient
|
from sanic_testing.testing import SanicTestClient
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import warnings
|
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from random import choice
|
from random import choice
|
||||||
from unittest.mock import MagicMock
|
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiofiles import os as async_os
|
from aiofiles import os as async_os
|
||||||
from sanic_testing.testing import HOST, PORT
|
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import (
|
from sanic.response import (
|
||||||
HTTPResponse,
|
HTTPResponse,
|
||||||
StreamingHTTPResponse,
|
|
||||||
empty,
|
empty,
|
||||||
file,
|
file,
|
||||||
file_stream,
|
file_stream,
|
||||||
|
@ -26,7 +22,6 @@ from sanic.response import (
|
||||||
stream,
|
stream,
|
||||||
text,
|
text,
|
||||||
)
|
)
|
||||||
from sanic.server import HttpProtocol
|
|
||||||
|
|
||||||
|
|
||||||
JSON_DATA = {"ok": True}
|
JSON_DATA = {"ok": True}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from time import monotonic as current_time
|
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
Loading…
Reference in New Issue
Block a user