Coffee. On the rocks!

Customizing Flask-Restful

Sun 10 November 2013

Its been some time that i have been using flask-restful for writing APIs and i feel that it really provides some out of the box features that makes life easy but over some time i realized that i needed it to do some request and response processing that i couldnt immediately figure out from the documentation. Here i will do some basic request and response processing. Here is an example of an API that a mobile app uses to post some data to the server

from flask import Flask
from flask.ext.restful import reqparse, Resource, Api
class Data(Resource):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('app_key', type=str, required=True)
        parser.add_argument('device_id', type=int, required=True)
        parser.add_argument('auth_token', type=str, required=True)
        parser.add_argument('tracking_data', type=str, required=True)
        request_params = parser.parse_args()
        result = process_the_request(request_params)
        response = {
             'result': result,
             'error_code': 0
        }
        return response


app = Flask(__name__)
api = Api(app)
api.add_resource(Data, '/data')
if __name__ == '__main__':
    app.run(port=8000)

Now its easy to see the above endpoint /data expects 4 parameters all of which are mandatory. app_key, auth_token and tracking_data are of string type and device_id is integer type. So if you pass device_id as abc123 it will return an HTTP 400 error saying that it requires an integer value for device_id. All this validation work is done for you by reqparse.RequestParser. Pretty simple.

Now your tracking_data that you mobile app sends you must be JSON but sometimes the app goes crazy and sends you invalid JSON. Here is how you handle it. Define a function that takes some string and converts it to JSON and if its unable to convert raises a ValueError

def json_type(data):
    try:
        return json.loads(data)
    except:
        raise ValueError('Malformed JSON')

Next you pass this function as the type argument for the add_argument function like this

parser.add_argument('tracking_data', type=json_type, required=True, message='Tracking data should be JSON')

Now whenever the app sends tracking_data that is not in JSON format an HTTP 400 error is raised. Similarly you can do other processing on request parameters. Say you have to convert all spaces in a parameter name to underscores so something like abc 1 2 3 should be converted to abc_1_2_3.

def sane(data):
    return data.replace(' ', '_')

parser.add_argument('name', type=sane)

See how simple it is to do some processing on request parameters. Now lets do some processing on the response. When you return a dictionary as a response it is converted to JSON automatically by flask-restful but sometimes the dictionay might contain something that is not convertible to JSON straigtaway. Like the dictionary you return has a datetime field which you want to be returned as a Unix timestamp. So for that you have to override the respresentations property of api object. But first i need to write a custom JSON encoder that can convert datetime objects to Unix timestamp

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return int(obj.strftime('%s'))
        return json.JSONEncoder.default(self, obj)

Flask-Restful internally uses a dictionary called representations that knows what function is to be used for generating responses for a particular mimetype. It uses a function called output_json to convert your dictionary responses to JSON and calls flask's make_response to return response. So the idea is to create another function that converts to JSON using the class CustomEncoder defined above and assign this function to the key application/json of representations. (I could have gone with subclassing restful.Api and overiding representations property of the child class)

from flask import make_response

def custom_json_output(data, code, headers=None):
    dumped = json.dumps(data, cls=CustomEncoder)
    resp = make_response(dumped, code)
    resp.headers.extend(headers or {})
    return resp

api = Api(app)
api.representations.update({
    'application/json': custom_json_output
})

Lets deal with a little authentication now. Say some of your resources can only be accessed when one is authenticated(or maybe authorized to accessed it). Say you do the authentication as HTTP basic auth. Now you can create a decorator that does the authentication and assign it to the method_decorators attribute of Resource

from functools import wraps
def auth_me(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not auth(auth.username, auth.password):
            return {'success': False, 'error_code': 1, 'message': 'Auth Fail'}, 401
        return f(*args, **kwargs)
    return decorated


class ProtectedResource(Resource):
    method_decorators = [auth_me]

Now any of your resources that need authenticated access should subclass the ProtectedResource above like this

class Secrets(ProtectedResource):
    def get(self):
          ......

Also if some of your resources require authentication only on creation but not on retrieval (only on HTTP POST not on HTTP GET) you can do this

class Posts(Resource):
    def get(self):
        ......

    @auth_me
    def post(self):
        .............

Look at my next post for some error handling and logging in flask-restful

This entry was tagged as flask flask-restful request processing response processing

blog comments powered by Disqus