详解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中的数字类型及基本的数学计算
Mar 11 Python
python3.5仿微软计算器程序
Mar 30 Python
python skimage 连通性区域检测方法
Jun 21 Python
利用python和百度地图API实现数据地图标注的方法
May 13 Python
pyqt5实现绘制ui,列表窗口,滚动窗口显示图片的方法
Jun 20 Python
python  文件的基本操作 菜中菜功能的实例代码
Jul 17 Python
Python 可变类型和不可变类型及引用过程解析
Sep 27 Python
python GUI库图形界面开发之PyQt5布局控件QHBoxLayout详细使用方法与实例
Mar 06 Python
Python通过4种方式实现进程数据通信
Mar 12 Python
Django模板标签中url使用详解(url跳转到指定页面)
Mar 19 Python
终于搞懂了Keras中multiloss的对应关系介绍
Jun 22 Python
让文件路径提取变得更简单的Python Path库
May 27 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
AMFPHP php远程调用(RPC, Remote Procedure Call)工具 快速入门教程
2010/05/10 PHP
easyui的tabs update正确用法分享
2014/03/21 PHP
PHP大批量插入数据库的3种方法和速度对比
2014/07/08 PHP
php安全配置记录和常见错误梳理(总结)
2017/03/28 PHP
javascript 动态添加表格行
2006/06/22 Javascript
用js实现手把手教你月入万刀(转贴)
2007/11/07 Javascript
JavaScript 面向对象编程(2) 定义类
2010/05/18 Javascript
浅析js中取绝对值的2种方法
2013/07/09 Javascript
JavaScript关闭当前页面(窗口)不带任何提示
2014/03/26 Javascript
jQuery+ajax实现鼠标单击修改内容的思路
2014/06/29 Javascript
实例详解Nodejs 保存 payload 发送过来的文件
2016/01/14 NodeJs
Jquery技巧(必须掌握)
2016/03/16 Javascript
实战node静态文件服务器的示例代码
2018/03/08 Javascript
如何对react hooks进行单元测试的方法
2019/08/14 Javascript
详解从vue-loader源码分析CSS Scoped的实现
2019/09/23 Javascript
vue+vant实现购物车全选和反选功能
2020/11/17 Vue.js
[38:41]2014 DOTA2国际邀请赛中国区预选赛 LGD VS CNB
2014/05/22 DOTA
python 采集中文乱码问题的完美解决方法
2016/09/27 Python
Python使用paramiko操作linux的方法讲解
2019/02/25 Python
实例详解Matlab 与 Python 的区别
2019/04/26 Python
Python字符编码转码之GBK,UTF8互转
2020/02/09 Python
windows10 pycharm下安装pyltp库和加载模型实现语义角色标注的示例代码
2020/05/07 Python
HTML5几个设计和修改的页面范例分享
2015/09/29 HTML / CSS
美国玛丽莎收藏奢华时尚商店:Marissa Collections
2016/11/21 全球购物
英国家喻户晓的折扣商场:TK Maxx
2017/05/26 全球购物
丝芙兰香港官网:Sephora香港
2018/03/13 全球购物
创立科技Java面试题
2015/11/29 面试题
小学生推普周国旗下讲话稿
2014/09/21 职场文书
行政主管岗位职责
2015/02/03 职场文书
因公司原因离职的辞职信范文
2015/05/12 职场文书
500字作文之周记
2019/12/13 职场文书
使用logback实现按自己的需求打印日志到自定义的文件里
2021/08/30 Java/Android
一文了解MYSQL三大范式和表约束
2022/04/03 MySQL
前端JS获取URL参数的4种方法总结
2022/04/05 Javascript
tomcat下部署jenkins的方法
2022/05/06 Servers
Win11 Build 22000.829更新补丁KB5015882发布(附更新修复内容汇总)
2022/07/15 数码科技