详解Flask开发技巧之异常处理


Posted in Python onJune 15, 2021

一、Flask内置异常处理

要想在Flask中处理好异常,有一套自己的异常处理机制,首先,我们必须先知道Flask自己是如何处理异常的。去flask的源码里找一找会发现,在flask源码的app.py文件下,有很多会抛出异常的方法,其中拿一个举例:

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)

我们发现在flask内部对于500异常,会抛出这样一个错误类InternalServerError()

class InternalServerError(HTTPException):

    ......

至此我们发现flask内部异常通过继承这个HTTPException类来处理,那么这个HTTPException类就是我们研究的重点。

二、HTTPException类分析

@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)
  • 截取这个类比较重要的几个方法分析,get_headers方法定义了这个返回的响应头,返回的是html文档。
  • get_body方法定义了返回的响应体,对应也是一段html的内容。
  • 最后在Response中将响应体,状态码,响应头定义好返回。

分析至此,其实这个HTTPException中做的事也不难理解,就是定义好响应体,状态码,还有响应头,做了一个返回。当然这个类返回是html类型的,现在前后端分离交互都是json形式的返回,所以我们可以继承自这个类,定义我们自己的异常处理类。

三、自定义异常处理类

首先我们理解我们自己的这个异常处理类,应该继承自HTTPException来改写。而我们自定义的内容应该包含以下几点:

  • 需要定义我们自己想要返回的错误信息的json格式,比如内部错误码、错误信息等我们想记录的信息。
  • 需要更改返回的响应头,返回json格式的信息响应头就应该设为'Content-Type': 'application/json'
  • 同样需要和HTTPException一样定义好状态码

如下定义我们自己的异常类APIException,返回的信息包括内部错误码,错误信息,请求的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]

四、方便的定义自己的错误类

有了上面我们改写好的APIException类,我们就可以自由的定义各种状态码的错误以及对应的错误信息,然后在合适的位置抛出。比如:

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'

有了这些自定义的错误类,我们不仅可以直接在需要的地方抛出,而且有了自定义的错误码,发生错误时,只要对照错误码去查找对应的错误类,非常方便。而且特别说明的是,虽然说是错误类,但是也是可以定义响应成功的返回的,比如上面定义的200,201的类,同样可以作为一个成功的返回。

使用演示:

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

五、注意事项

尽管我们可以在我们认为可能出错的所有地方,继承自己的异常类,定义自己的错误类,然后抛出,但是也不是所有的异常都是我们可以提前预知的。比如我们接受前端传来的参数,参数类型或取值范围不正确,这些我们可以预知并处理好,但是如果是逻辑处理中出现了问题,这些不是我们程序员可以控制并处理。所以光有自定义错误类还不够,我们还需要在全局捕获异常来判断,利用AOP思想。

# 全局错误AOP处理
@app.errorhandler(Exception)
def framework_error(e):
    api_logger.error("error info: %s" % e) # 对错误进行日志记录
    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

这里对于flask中抛出的所有的错误进行捕获,然后先进行日志的记录。然后判断如果是我们自定义的APIException,就直接返回。如果不是我们自定义的,但是是flask处理的HTTPException,包装成我们自定义的APIException再返回。如果都不是的话,说明是服务器出现的其他错误,问题一般出在我们的代码上,在生产环境下,一般统一返回一个500错误,在调试模式下,可以原样返回,便于我们定位修改自己的代码。

以上就是详解Flask开发技巧之异常处理的详细内容,更多关于Flask异常处理的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python检测远程udp端口是否打开的方法
Mar 14 Python
python中使用mysql数据库详细介绍
Mar 27 Python
python实现折半查找和归并排序算法
Apr 14 Python
Python 中的 global 标识对变量作用域的影响
Aug 12 Python
Python3分析处理声音数据的例子
Aug 27 Python
python3中pip3安装出错,找不到SSL的解决方式
Dec 12 Python
Python字符串格式化f-string多种功能实现
May 07 Python
Python如何实现的二分查找算法
May 27 Python
python 基于opencv操作摄像头
Dec 24 Python
一文带你掌握Pyecharts地理数据可视化的方法
Feb 06 Python
Pandas加速代码之避免使用for循环
May 30 Python
Python Pandas常用函数方法总结
Jun 15 Python
Python Pandas常用函数方法总结
Jun 15 #Python
深入理解python协程
Jun 15 #Python
2021年最新用于图像处理的Python库总结
Python中的xlrd模块使用整理
Jun 15 #Python
浅谈python中的多态
Jun 15 #Python
如何正确理解python装饰器
Jun 15 #Python
详解python网络进程
You might like
为什么《星际争霸》是测试人工智能的理想战场
2019/12/03 星际争霸
怎样在php中使用PDF文档功能
2006/10/09 PHP
Yii使用技巧大汇总
2015/12/29 PHP
PHP命令行执行整合pathinfo模拟定时任务实例
2016/08/12 PHP
thinkPHP商城公告功能开发问题分析
2016/12/01 PHP
PHP设计模式之建造者模式定义与用法简单示例
2018/08/13 PHP
php 使用html5 XHR2实现上传文件与进度显示功能示例
2020/03/03 PHP
JS网页播放声音实现代码兼容各种浏览器
2013/09/22 Javascript
可编辑下拉框的2种实现方式
2014/06/13 Javascript
Jquery ajax 同步阻塞引起的UI线程阻塞问题
2015/11/17 Javascript
js针对ip地址、子网掩码、网关的逻辑性判断
2016/01/06 Javascript
JS组件Bootstrap实现图片轮播效果
2016/05/16 Javascript
两种简单的跨域方法(jsonp、php)
2017/01/02 Javascript
JS数组去重(4种方法)
2017/03/27 Javascript
使用Vue完成一个简单的todolist的方法
2017/12/01 Javascript
ES6 对象的新功能与解构赋值介绍
2019/02/05 Javascript
微信小程序websocket聊天室的实现示例代码
2019/02/12 Javascript
Vue.js实现可编辑的表格
2019/12/11 Javascript
element跨分页操作选择详解
2020/06/29 Javascript
简单讲解Python编程中namedtuple类的用法
2016/06/21 Python
详解python的数字类型变量与其方法
2016/11/20 Python
Python进阶学习之特殊方法实例详析
2017/12/01 Python
利用python爬取斗鱼app中照片方法实例
2017/12/03 Python
TensorFlow数据输入的方法示例
2018/06/19 Python
Django 外键的使用方法详解
2019/07/19 Python
Pyinstaller 打包exe教程及问题解决
2019/08/16 Python
Python django搭建layui提交表单,表格,图标的实例
2019/11/18 Python
python打印文件的前几行或最后几行教程
2020/02/13 Python
pandas 数据类型转换的实现
2020/12/29 Python
Servlet的生命周期
2013/08/25 面试题
生产总经理岗位职责
2013/12/19 职场文书
社区领导班子四风问题原因分析及整改措施
2014/09/28 职场文书
师德师风剖析材料
2014/09/30 职场文书
四风问题党员个人整改措施
2014/10/27 职场文书
群众路线个人剖析材料及整改措施
2014/11/04 职场文书
送给火锅店的创意营销方案!
2019/07/08 职场文书