Exception handling of Flask development skills

catalog

I usually use in the development, or learn some of the flask development skills, need to have a more solid basis for flask.

1. Flash built in exception handling

In order to handle exceptions well in flask, there is a set of exception handling mechanism. First of all, we must know how flask handles exceptions. Go to the source code of flask and find out that it's in the source code of flask app.py Under the file, there are many methods that throw exceptions. For example:

    def handle_exception(self, e):
        """Default exception handling that kicks in when an exception
        occurs that is not caught.  In debug mode the exception will
        be re-raised immediately, otherwise it is logged and the handler
        for a 500 internal server error is used.  If no such handler
        exists, a default 500 internal server error message is displayed.

        .. versionadded:: 0.3
        """
        exc_type, exc_value, tb = sys.exc_info()

        got_request_exception.send(self, exception=e)
        handler = self._find_error_handler(InternalServerError())

        if self.propagate_exceptions:
            # if we want to repropagate the exception, we can attempt to
            # raise it with the whole traceback in case we can do that
            # (the function was actually called from the except part)
            # otherwise, we just raise the error again
            if exc_value is e:
                reraise(exc_type, exc_value, tb)
            else:
                raise e

        self.log_exception((exc_type, exc_value, tb))
        if handler is None:
            return InternalServerError()
        return self.finalize_request(handler(e), from_error_handler=True)

We found that for the 500 exception within the flash, such an error class InternalServerError() will be thrown

class InternalServerError(HTTPException):
    ......

So far, we find that the internal exception of flag is handled by inheriting the HTTPException class, which is the focus of our research.

2. HTTPException class analysis

@implements_to_string
class HTTPException(Exception):
    """Baseclass for all HTTP exceptions.  This exception can be called as WSGI
    application to render a default error page or you can catch the subclasses
    of it independently and render nicer error messages.
    """

    code = None
    description = None

    def __init__(self, description=None, response=None):
        super(HTTPException, self).__init__()
        if description is not None:
            self.description = description
        self.response = response

    @classmethod
    def wrap(cls, exception, name=None):
        """Create an exception that is a subclass of the calling HTTP
        exception and the ``exception`` argument.

        The first argument to the class will be passed to the
        wrapped ``exception``, the rest to the HTTP exception. If
        ``e.args`` is not empty and ``e.show_exception`` is ``True``,
        the wrapped exception message is added to the HTTP error
        description.

        .. versionchanged:: 0.15.5
            The ``show_exception`` attribute controls whether the
            description includes the wrapped exception message.

        .. versionchanged:: 0.15.0
            The description includes the wrapped exception message.
        """

        class newcls(cls, exception):
            _description = cls.description
            show_exception = False

            def __init__(self, arg=None, *args, **kwargs):
                super(cls, self).__init__(*args, **kwargs)

                if arg is None:
                    exception.__init__(self)
                else:
                    exception.__init__(self, arg)

            @property
            def description(self):
                if self.show_exception:
                    return "{}\n{}: {}".format(
                        self._description, exception.__name__, exception.__str__(self)
                    )

                return self._description

            @description.setter
            def description(self, value):
                self._description = value

        newcls.__module__ = sys._getframe(1).f_globals.get("__name__")
        name = name or cls.__name__ + exception.__name__
        newcls.__name__ = newcls.__qualname__ = name
        return newcls

    @property
    def name(self):
        """The status name."""
        from .http import HTTP_STATUS_CODES

        return HTTP_STATUS_CODES.get(self.code, "Unknown Error")

    def get_description(self, environ=None):
        """Get the description."""
        return u"<p>%s</p>" % escape(self.description).replace("\n", "<br>")

    def get_body(self, environ=None):
        """Get the HTML body."""
        return text_type(
            (
                u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
                u"<title>%(code)s %(name)s</title>\n"
                u"<h1>%(name)s</h1>\n"
                u"%(description)s\n"
            )
            % {
                "code": self.code,
                "name": escape(self.name),
                "description": self.get_description(environ),
            }
        )

    def get_headers(self, environ=None):
        """Get a list of headers."""
        return [("Content-Type", "text/html; charset=utf-8")]

    def get_response(self, environ=None):
        """Get a response object.  If one was passed to the exception
        it's returned directly.

        :param environ: the optional environ for the request.  This
                        can be used to modify the response depending
                        on how the request looked like.
        :return: a :class:`Response` object or a subclass thereof.
        """
        from .wrappers.response import Response

        if self.response is not None:
            return self.response
        if environ is not None:
            environ = _get_environ(environ)
        headers = self.get_headers(environ)
        return Response(self.get_body(environ), self.code, headers)

    ......
  • Analysis of several important methods for intercepting this class, get_ The headers method defines the response header returned, which is an html document.

  • get_ The body method defines the returned response body, which is also the content of a piece of html.

  • Finally, define the Response body, status code and Response header to return.

So far, it's not hard to understand what HTTPException does. It's to define the response body, status code, and response header, and make a return. Of course, the return of this class is of html type. Now, the front and back-end separation interaction is in the form of json, so we can inherit from this class and define our own exception handling class.

3. Custom exception handling class

First of all, we understand that our own exception handling class should be overridden by inheriting from HTTPException. Our customized content should include the following points:

  • We need to define the json format of the error information we want to return, such as the internal error code, error information and other information we want to record.
  • The returned response header needs to be changed. The information response header returned in json format should be set to 'content type': 'application / json'
  • You need to define the status code as well as HTTPException

Define our own exception class APIException as follows. The returned information includes internal error code, error information and requested url

class APIException(HTTPException):
    code = 500
    msg = 'sorry, we made a mistake!'
    error_code = 999

    def __init__(self, msg=None, code=None, error_code=None, headers=None):
        if code:
            self.code = code
        if error_code:
            self.error_code = error_code
        if msg:
            self.msg = msg
        super(APIException, self).__init__(msg, None)

    def get_body(self, environ=None):
        body = dict(
            msg=self.msg,
            error_code=self.error_code,
            request=request.method + ' ' + self.get_url_no_param()
        )
        text = json.dumps(body)
        return text

    def get_headers(self, environ=None):
        """Get a list of headers."""
        return [('Content-Type', 'application/json')]

    @staticmethod
    def get_url_no_param():
        full_path = str(request.full_path)
        main_path = full_path.split('?')
        return main_path[0]

4. Easily define your own error class

With the APIException class we have rewritten above, we are free to define the errors of various status codes and corresponding error information, and then throw them at the appropriate location. For example:

class Success(APIException):
    code = 201
    msg = 'ok'
    error_code = 0


class DeleteSuccess(APIException):
    code = 202
    msg = 'delete ok'
    error_code = 1


class UpdateSuccess(APIException):
    code = 200
    msg = 'update ok'
    error_code = 2


class ServerError(APIException):
    code = 500
    msg = 'sorry, we made a mistake!'
    error_code = 999


class ParameterException(APIException):
    code = 400
    msg = 'invalid parameter'
    error_code = 1000


class NotFound(APIException):
    code = 404
    msg = 'the resource are not found'
    error_code = 1001


class AuthFailed(APIException):
    code = 401
    msg = 'authorization failed'
    error_code = 1005


class Forbidden(APIException):
    code = 403
    error_code = 1004
    msg = 'forbidden, not in scope'

With these custom error classes, we can not only throw them directly where we need them, but also have custom error codes. When an error occurs, it is very convenient to look up the corresponding error classes by referring to the error codes. In particular, although it is an error class, it can also be used to define the successful return of the response. For example, the 200201 class defined above can also be used as a successful return.

Use Demo:

user = User.query.first()
if not user:
    raise NotFound()

5. Precautions

Although we can inherit our own exception class, define our own error class and throw it everywhere we think it may be wrong, not all exceptions can be predicted in advance. For example, if we accept the parameters from the front end, the parameter type or value range is not correct, we can predict and handle them well, but if there is a problem in the logic processing, these are not under the control of our programmers. So it's not enough to just have custom error classes. We also need to catch exceptions in the global to judge, using the AOP idea.

# Global error AOP processing
@app.errorhandler(Exception)
def framework_error(e):
    api_logger.error("error info: %s" % e) # Log errors
    if isinstance(e, APIException):
        return e
    if isinstance(e, HTTPException):
        code = e.code
        msg = e.description
        error_code = 1007
        return APIException(msg, code, error_code)
    else:
        if not app.config['DEBUG']:
            return ServerError()
        else:
            return e

Here, all the errors thrown in the flash are captured, and then the log is recorded first. Then judge if it is our customized APIException, and return it directly. If it's not our custom HTTPException, but it's handled by flash, it's packaged as our custom APIException and returned. If not, it means that there are other errors on the server. Generally, the problem lies in our code. In the production environment, a 500 error is returned uniformly. In the debugging mode, it can be returned as is, so that we can locate and modify our code.

About the exception handling of flask, the above are some skills I have mastered at present. If there are any mistakes, please point out.

Blog Park: https://www.cnblogs.com/luyuze95/

GitHub: https://github.com/luyuze95

Keywords: Python JSON github REST Attribute

Added by SpectralDesign.Net on Fri, 22 May 2020 12:02:14 +0300