早期项目搭建的时候为了统一接口返回格式,基于werkzeug的HTTPException实现了APIException用于返回json格式,并制定了一套完善的错误码体系,但测试小伙伴对错误码对应的问题类型并不了解,因此需要编写一份文档用于方便查阅错误码和错误的原因。但是每次添加新的错误码都需要同步更新文档,这可不太妙。因此设计一个文档的同步更新机制迫在眉睫
APIException
flask 主要是使用werkzeug来实现WSGI application。当api请求错误会返回HTTPException。
查看源码发现返回类型是文本格式。
1 2 3 def get_headers (self, environ=None) : """Get a list of headers.""" return [("Content-Type" , "text/html" )]
为了统一返回的格式类型,继承HTTPException实现了APIException。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from flask import request, jsonfrom werkzeug.exceptions import HTTPExceptionclass APIException (HTTPException) : code = 500 description = 'sorry, we made a mistake (* ̄︶ ̄)!' error_code = 999 def __init__ (self, description=None, code=None, error_code=None, headers=None) : if code: self.code = code if error_code: self.error_code = error_code if description: self.description = description super(APIException, self).__init__(description) def get_body (self, environ=None) : body = dict( description=self.description, 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) : 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 ]
通过flask核心对象的errorhandler拦截所有的Exception并返回统一的APIException实现异常的请求也返回json格式。
1 2 3 4 5 6 7 8 9 10 11 @app.errorhandler(Exception) def global_error (error) : if isinstance(error, APIException): return error elif isinstance(error, HTTPException): return RecvHttpException(description=error.description, code=error.code) else : if not app.config['DEBUG' ]: return ServerError() else : raise error
自动化文档实现原理
继承APIException的类通常格式如下:
1 2 3 4 5 6 7 class PinyinTransferFail (APIException) : """ 无法识别的拼音字母 """ error_code = 1010 code = 400 description = "un-recognize pinyin"
项目结构:
项目中还有一些错误并不是由APIException定义的因此将其定义在special_error.json
中,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 [ { "error_code" : 1 , "description" : "任务执行失败" }, { "error_code" : -2 , "description" : "adb协议故障" },{ "error_code" : -3 , "description" : "设备没连/连错wifi" } ]
思路:
通常,动态读取所有的错误类型的类,并将类的error_code 和 doc(doc为空着取description) 一一对应,生成表单返回。
因此定义如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import importlibimport pkgutilfrom typing import Listimport pandas as pdimport inspectdef get_module_from_package (package) : """ 非递归,值获取package下第一层的module """ prefix = package.__name__ + "." return [importlib.import_module(modname) for importer, modname, ispkg in pkgutil.iter_modules(package.__path__, prefix) if not ispkg] def get_classes (module, superclass=object) : """ 获取module下的为 superclass 的子类的所有类 """ clsmembers = inspect.getmembers(module, inspect.isclass) return [_class for (name, _class) in clsmembers if issubclass(_class, superclass)] def convert_to_html (result: [List[List[str]]], title: List[str]) : """ 将[[str],[str],[str]] [str] 转化成html """ d = {} index = 0 for t in title: d[t] = result[index] index = index + 1 df = pd.DataFrame(d) df = df[title] h = df.to_html(index=False ) return h
定义接口,返回html表格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @app.route('/doc/') def doc () : from app.execption.outer.error import APIException from app.execption.outer import error_code from app.libs.extension.tools import convert_to_html, get_module_from_package, get_classes import json from collections import namedtuple SpecialException = namedtuple("SpecialException" , ["error_code" , "description" ]) title = ['code' , 'name' , 'description' ] def special_case () : special_error_file_path = os.path.join(BASE_DIR, "app" , "execption" , "outer" , "error_code" , "special_error.json" ) with open(special_error_file_path, "r" ) as json_file: special_msg_list = json.load(json_file) return [ SpecialException(error_code=special_msg["error_code" ], description=special_msg["description" ]) for special_msg in special_msg_list ] _all = special_case() result = [[], [], []] for module in get_module_from_package(error_code): _all += get_classes(module, APIException) for _class in sorted(set(_all), key=lambda obj: obj.error_code): result[0 ].append(_class.error_code) result[1 ].append(getattr(_class, "__name__" , "" )) result[2 ].append( _class.__doc__.strip() if not isinstance(_class, SpecialException) and getattr(_class, "__doc__" , "" ) else _class.description ) return convert_to_html(result, title)