-
Notifications
You must be signed in to change notification settings - Fork 73
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
Improve errorhandler #644
base: main
Are you sure you want to change the base?
Improve errorhandler #644
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,30 +7,166 @@ class FlaskSmorestError(Exception): | |
"""Generic flask-smorest exception""" | ||
|
||
|
||
# Non-API exceptions | ||
class MissingAPIParameterError(FlaskSmorestError): | ||
"""Missing API parameter""" | ||
|
||
|
||
class NotModified(wexc.HTTPException, FlaskSmorestError): | ||
"""Resource was not modified (Etag is unchanged) | ||
class CurrentApiNotAvailableError(FlaskSmorestError): | ||
"""`current_api` not available""" | ||
|
||
Exception created to compensate for a lack in Werkzeug (and Flask) | ||
""" | ||
|
||
code = 304 | ||
description = "Resource not modified since last request." | ||
# API exceptions | ||
class ApiException(wexc.HTTPException, FlaskSmorestError): | ||
"""A generic API error.""" | ||
|
||
@classmethod | ||
def get_exception_by_code(cls, code): | ||
for exception in cls.__subclasses__(): | ||
if exception.code == code: | ||
return exception | ||
return InternalServerError | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'd return a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. # API exceptions
class ApiException(wexc.HTTPException, FlaskSmorestError):
"""A generic API error."""
@classmethod
def get_exception_by_code(cls, code):
for exception in cls.__subclasses__():
if exception.code == code:
return exception
return ValueError(f"Not a standard HTTP status code: {code}") I think this error message is appropriate, but if you have another one, please let me know.. :) |
||
|
||
|
||
class BadRequest(wexc.BadRequest, ApiException): | ||
"""Bad request""" | ||
|
||
|
||
class Unauthorized(wexc.Unauthorized, ApiException): | ||
"""Unauthorized access""" | ||
|
||
|
||
class Forbidden(wexc.Forbidden, ApiException): | ||
"""Forbidden access""" | ||
|
||
|
||
class NotFound(wexc.NotFound, ApiException): | ||
"""Resource not found""" | ||
|
||
|
||
class MethodNotAllowed(wexc.MethodNotAllowed, ApiException): | ||
"""Method not allowed""" | ||
|
||
|
||
class NotAcceptable(wexc.NotAcceptable, ApiException): | ||
"""Not acceptable""" | ||
|
||
|
||
class RequestTimeout(wexc.RequestTimeout, ApiException): | ||
"""Request timeout""" | ||
|
||
|
||
class Conflict(wexc.Conflict, ApiException): | ||
"""Conflict""" | ||
|
||
|
||
class Gone(wexc.Gone, ApiException): | ||
"""Resource gone""" | ||
|
||
|
||
class LengthRequired(wexc.LengthRequired, ApiException): | ||
"""Length required""" | ||
|
||
|
||
class PreconditionFailed(wexc.PreconditionFailed, ApiException): | ||
"""Etag required and wrong ETag provided""" | ||
|
||
|
||
class RequestEntityTooLarge(wexc.RequestEntityTooLarge, ApiException): | ||
"""Request entity too large""" | ||
|
||
|
||
class RequestURITooLarge(wexc.RequestURITooLarge, ApiException): | ||
"""Request URI too large""" | ||
|
||
|
||
class UnsupportedMediaType(wexc.UnsupportedMediaType, ApiException): | ||
"""Unsupported media type""" | ||
|
||
|
||
class RequestedRangeNotSatisfiable(wexc.RequestedRangeNotSatisfiable, ApiException): | ||
"""Requested range not satisfiable""" | ||
|
||
|
||
class PreconditionRequired(wexc.PreconditionRequired, FlaskSmorestError): | ||
class ExpectationFailed(wexc.ExpectationFailed, ApiException): | ||
"""Expectation failed""" | ||
|
||
|
||
class ImATeapot(wexc.ImATeapot, ApiException): | ||
"""I'm a teapot""" | ||
|
||
|
||
class UnprocessableEntity(wexc.UnprocessableEntity, ApiException): | ||
"""Unprocessable entity""" | ||
|
||
|
||
class Locked(wexc.Locked, ApiException): | ||
"""Locked""" | ||
|
||
|
||
class FailedDependency(wexc.FailedDependency, ApiException): | ||
"""Failed dependency""" | ||
|
||
|
||
class PreconditionRequired(wexc.PreconditionRequired, ApiException): | ||
"""Etag required but missing""" | ||
|
||
# Overriding description as we don't provide If-Unmodified-Since | ||
description = 'This request is required to be conditional; try using "If-Match".' | ||
|
||
|
||
class PreconditionFailed(wexc.PreconditionFailed, FlaskSmorestError): | ||
"""Etag required and wrong ETag provided""" | ||
class TooManyRequests(wexc.TooManyRequests, ApiException): | ||
"""Too many requests""" | ||
|
||
|
||
class CurrentApiNotAvailableError(FlaskSmorestError): | ||
"""`current_api` not available""" | ||
class RequestHeaderFieldsTooLarge(wexc.RequestHeaderFieldsTooLarge, ApiException): | ||
"""Request header fields too large""" | ||
|
||
|
||
class UnavailableForLegalReasons(wexc.UnavailableForLegalReasons, ApiException): | ||
"""Unavailable for legal reasons""" | ||
|
||
|
||
class InternalServerError(wexc.InternalServerError, ApiException): | ||
"""Internal server error""" | ||
|
||
|
||
class NotImplemented(wexc.NotImplemented, ApiException): | ||
"""Not implemented""" | ||
|
||
|
||
class BadGateway(wexc.BadGateway, ApiException): | ||
"""Bad gateway""" | ||
|
||
|
||
class ServiceUnavailable(wexc.ServiceUnavailable, ApiException): | ||
"""Service unavailable""" | ||
|
||
|
||
class GatewayTimeout(wexc.GatewayTimeout, ApiException): | ||
"""Gateway timeout""" | ||
|
||
|
||
class HTTPVersionNotSupported(wexc.HTTPVersionNotSupported, ApiException): | ||
"""HTTP version not supported""" | ||
|
||
|
||
class NotModified(ApiException): | ||
"""Resource was not modified (Etag is unchanged) | ||
|
||
Exception created to compensate for a lack in Werkzeug (and Flask) | ||
""" | ||
|
||
code = 304 | ||
description = "Resource not modified since last request." | ||
|
||
|
||
def abort(http_status_code, exc=None, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like to keep only exceptions in this file. We may want to put this somewhere else. It could be in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll fix the location of that code, I think it's a good idea to fix it as you say |
||
try: | ||
raise ApiException.get_exception_by_code(http_status_code) | ||
except ApiException as err: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have to raise and catch? Can't we tweak the instance before raising? |
||
err.data = kwargs | ||
err.exc = exc | ||
raise err | ||
except Exception as err: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the point of catching any exception to re-raise it? |
||
raise err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering it it needs to inherit
HTTPException
. Perhaps. No objection, just wondering.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of original exceptions.py for flask_smorest. As you can see here, flask_smorest inherits and overrides a few HttpExceptions, so I wanted to make sure that I was calling them "http API errors", so I overridden them. (This is good, in my opinion, because it makes it clear what are API errors and what are internal errors that are not).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you said, with this approach, we could subclass all Werkzeug errors to our exception class.
I'm a little curious here. Should we also allow users to import and use syntax like
from flask_smorest.exceptions import NotFound
? Because it doesn't seem to work when importing in code, not in the REPL.For example, what code should a user use to get a 404 error?
Would love to hear your thoughts :)