使用JSON响应为Flask-RESTful定制参数验证 [英] Custom parameter validation with JSON response for Flask-RESTful
问题描述
我使用Flask-RESTful来创建API端点,并使用这种方式指定URL:
api.add_resource( ListVersionsByStaff'/ shots / versions /< int:project_id> /< int:staff_id>')
api.add_resource(ListSeasons,'/ seasons /< int:project_id>')
如果给定的参数不是 int $ c,Flask-RESTful将返回错误响应$ c>,它会返回一个HTML响应。
如何返回自定义的JSON错误响应,例如:
除了InvalidParameter作为错误:
abort(err.status_code,** err.to_dict())
检查这个值的方式也行不通,参数总是键入String类型的项目(资源)
:
def get(self,project_id):
print(DEBUG:project_id is [,project_id,],file = sys.stderr)
print(DEBUG:Type is如果是isinstance(project_id,int):
pass
else:
message ='{,type(project_id),],file = sys.stderr)格式(project_id)
错误= {}
错误['resource'] =项目
}是不是一个有效的project_id提示:这是一个表示主键的数字。错误['field'] =project_id
错误['code'] =无效
错误['s tack_trace'] =
abort(400,message = message,errors = errors)
输出:
DEBUG:project_id是[1]
DEBUG:类型是[< class'str'> ; ]
我的解决方案是扩展Flask-RESTful Api
类并实现我的自定义错误处理程序。 官方文档解释了一些关于扩展的内容,但是没有进入足够的细节。
自定义错误消息
我的应用程序中的所有错误都在这个结构中
$
$ b $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ None,
field = None,code = None,stack_trace = None):
Exception .__ init __(self)
self.message = message
如果status_code不是None:
self.status_code = status_code
self.resource = resource
self.field = field
self.code = code
self.stack_trace = stack_trace
def to_dict(self):
rv = {}
errors = {}
rv ['message'] = self.message
errors ['resource'] = self。资源
errors ['field'] = self.field
errors ['code'] = self.code
errors ['stack_trace'] = self.stack_trace
rv [错误] =错误
return rv
这是默认的Flask-RESTful返回值
<!DOCTYPE HTML PUBLIC - // W3C // DTD HTML 3.2 Final // EN>
< title> 404未找到< / title>
< h1>未找到< / h1>
< p>在服务器上找不到请求的URL。如果您手动输入网址,请检查您的拼写,然后重试。< / p>
输出应该是JSON,格式为
{
message:在服务器上找不到请求的URL,如果手动输入了URL,请检查拼写并重试。但是你的意思是/ projects /< int:project_id>或/ projects或/ project / new?,
errors:{
resource:null,
field:null,
code:invalid_url,
stack_trace:null
}
}
解决方案
扩展 I要重新使用已生成的 >需要去除由Flask-RESTful添加的 构建JSON响应是 Api
类并覆盖 404
错误
class CustomApi(Api):
def handle_error(self,e):
code = getattr(e,'code',404)
if code == 404:
response_dict = Api.handle_error(self,e).__ dict__
resp_message = response_dict ['re ('。*)'。*','''','resf_message.decode'('utf-8'),flags = (错误信息,stack_trace = None,$ b $ resource = None,code =invalid_url,field = None)
返回self.make_response(err.to_dict() ,404)#{'message':something,'error':{'sub1':'val1'}},404)
return super(Api,self).handle_error(e)
{'headers':Headers([('Content-Type','application / json'),('Content-Length ','263')]),'_status_code':404,'_status':'404 NOT FOUND','direct_passthrough':False,'_on_close':[],'response':[b'{\\\
消息:在服务器上找不到请求的URL。如果您手动输入网址,请检查拼写,然后重试。你已经请求了这个URI [/ projects / 1asd],但是你的意思是/ projects /< int:project_id>或/ projects或/ project / new?\\\
} \ n']}
'response':'message'
,但不是默认的格式。 message
是不正确的JSON格式,所以我去除了消息
的内容,使用这个正则表达式
error_message = re.sub(r'。*(。*)。*',r'\\1',resp_message.decode('utf-8'),flags =请注意,
re.DOTALL
\\\
。
self.make_response(err.to_dict(),404)
404错误(例如400,500,503),错误只传递给原始的Flask-RESTful Api类。
请注意,当您创建Flask应用程序时,你需要使用你自定义的Api cla ss,并捕获所有的404错误:
$ p $ app = Flask(__ name__)
api = CustomApi(app,catch_all_404s =真)
I am using Flask-RESTful for creating API endpoints, and I specify the URL this way:
api.add_resource(ListVersionsByStaff, '/shots/versions/<int:project_id>/<int:staff_id>')
api.add_resource(ListSeasons, '/seasons/<int:project_id>')
While Flask-RESTful will return an error response if the given argument is not int
, it will return a HTML response.
How can I return a custom JSON error response, for example:
except InvalidParameter as err:
abort(err.status_code, **err.to_dict())
Checking for the value this way also does not work, the parameter is always type String
class SpecificProject(Resource):
def get(self, project_id):
print("DEBUG: project_id is [", project_id, "]", file=sys.stderr)
print("DEBUG: Type is [", type(project_id), "]", file=sys.stderr)
if isinstance(project_id, int):
pass
else:
message = "'{}' is not a valid project_id. Hint: this is a number representing primary key.".format(project_id)
errors = {}
errors['resource'] = "Project"
errors['field'] = "project_id"
errors['code'] = "invalid"
errors['stack_trace'] = ""
abort(400, message=message, errors=errors)
output:
DEBUG: project_id is [ 1 ]
DEBUG: Type is [ <class 'str'> ]
My solution is to extend the Flask-RESTful Api
class and implement my custom error handler. The official documentation explains a little bit about extending, but did not go into sufficient details.
Custom Error Message
All errors in my application is in this structure
class InvalidParameter(Exception):
status_code = 400
def __init__(self, message, status_code=None, resource=None,
field=None, code=None, stack_trace=None):
Exception.__init__(self)
self.message = message
if status_code is not None:
self.status_code = status_code
self.resource = resource
self.field = field
self.code = code
self.stack_trace = stack_trace
def to_dict(self):
rv = {}
errors = {}
rv['message'] = self.message
errors['resource'] = self.resource
errors['field'] = self.field
errors['code'] = self.code
errors['stack_trace'] = self.stack_trace
rv['errors'] = errors
return rv
This is what Flask-RESTful return by default
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
The output should be JSON, in this format
{
"message": "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. You have requested this URI [/projects/1asd] but did you mean /projects/<int:project_id> or /projects or /project/new ?",
"errors": {
"resource": null,
"field": null,
"code": "invalid_url",
"stack_trace": null
}
}
Solution
Extend the Api
class and overwrite the handle_error
method for 404
errors
class CustomApi(Api):
def handle_error(self, e):
code = getattr(e, 'code', 404)
if code == 404:
response_dict = Api.handle_error(self, e).__dict__
resp_message = response_dict['response'][0]
error_message = re.sub(r'.*"(.*)".*', r'\1', resp_message.decode('utf-8'), flags=re.DOTALL)
err = InvalidParameter(error_message, stack_trace=None,
resource=None, code="invalid_url", field=None)
return self.make_response(err.to_dict(), 404) #{'message': "something", 'error': {'sub1': 'val1'}}, 404)
return super(Api, self).handle_error(e)
The handle_error
dictionary is in this format
{'headers': Headers([('Content-Type', 'application/json'), ('Content-Length', '263')]), '_status_code': 404, '_status': '404 NOT FOUND', 'direct_passthrough': False, '_on_close': [], 'response': [b'{\n "message": "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. You have requested this URI [/projects/1asd] but did you mean /projects/<int:project_id> or /projects or /project/new ?"\n}\n']}
I want to re-use the 'response':'message'
that was generated, but not in the default format. message
was not not properly JSON formatted, so I strip out everything except for the content of message
, using this regex
error_message = re.sub(r'.*"(.*)".*', r'\1', resp_message.decode('utf-8'), flags=re.DOTALL)
Note that re.DOTALL
is needed to strip out the \n
that was added by Flask-RESTful.
The code that actually builds the JSON response is self.make_response(err.to_dict(), 404)
For all other non-404 errors (e.g. 400, 500, 503), the error is just passed along to the original Flask-RESTful Api class.
Note that when you create your Flask application, you need to use your custom Api class, and catch all 404 errors:
app = Flask(__name__)
api = CustomApi(app, catch_all_404s=True)
这篇关于使用JSON响应为Flask-RESTful定制参数验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!