I have a flask application with calls expecting JSON payload. Before each call is processed, I have a 2-step error checking process:
Which is implemented in the following fashion:
@app.route('/activate', methods=['POST'])
def activate():
request_id = request.__hash__()
# Assert that the payload is a valid JSON
try:
input = request.json
except BadRequest, e:
msg = "payload must be a valid json"
return jsonify({"error": msg}), 400
# JSON Schema Validation
try:
validate(request.json, app.config['activate_schema'])
except ValidationError, e:
return jsonify({"error": e.message}), 400
Since this code is duplicated over many calls, I wonder If I can elegantly move it to a decorator, something in the formof:
@validate_json
@validate_schema(schema=app.config['activate_schema'])
@app.route('/activate', methods=['POST'])
def activate():
....
The problem is that the request
argument is implicit: I can refer to it within the function, but it is not a parameter to it. Therefore, I am not sure how to use it within the decorator.
How can I implement the validation checks using Python decorators?
Just use the request
context global in your decorator. It is available during any request.
from functools import wraps
from flask import (
current_app,
jsonify,
request,
)
def validate_json(f):
@wraps(f)
def wrapper(*args, **kw):
try:
request.json
except BadRequest, e:
msg = "payload must be a valid json"
return jsonify({"error": msg}), 400
return f(*args, **kw)
return wrapper
def validate_schema(schema_name):
def decorator(f):
@wraps(f)
def wrapper(*args, **kw):
try:
validate(request.json, current_app.config[schema_name])
except ValidationError, e:
return jsonify({"error": e.message}), 400
return f(*args, **kw)
return wrapper
return decorator
Apply these decorators before applying the @route
decorator; you want to register the wrapped function, not the original function for the route:
@app.route('/activate', methods=['POST'])
@validate_json
@validate_schema('activate_schema')
def activate():
input = request.json