Search code examples
pythonrestflaskmime-typesflask-restful

Flask api representation affects all the views


I need to have two logically identical views to response the same data in different content types. I use Flask RESTful.

APP = flask.Flask(__name__)
API = flask_restful.Api(APP)

@API.representation('text/csv')                                               
def output_csv(data, code, headers=None):                                     
    data_is_list = isinstance(data, types.ListType)                           
    keys = data[0].keys() if data_is_list else data.keys()                    
    output = io.BytesIO()                                                     
    writer = csv.DictWriter(output, keys)                                     
    writer.writeheader()                                                      
    encode = lambda v: v.encode('utf-8') if isinstance(                       
            v, types.UnicodeType) else v                                      
    if data_is_list:                                                          
        writer.writerows([{k: encode(v) for k, v in i.items()} for i     in data])
    else:                                                                     
        writer.writerow({k: encode(v) for k, v in data.items()})              

    resp = flask.make_response(output.getvalue(), code)                       
    resp.headers.extend(headers or {})                                        

    return resp                                                               


class BaseAction(flask_restful.Resource):
    def get(self, id=None):
        # ...
        return actions[0] if id else actions # Dict or list of dicts.


class ActionAsCSV(BaseAction):
    def get(self, id=None):
        data = super(ActionAsCSV, self).get(id, closed)
        flask.Response(data, mimetype='text/csv')      
        return data                                    

There are several problems. After I added the representation view all the views return text/csv data with the apprpriate header. How to use the first view class to return application/json data and the second one to return text/csv data? The second issue is return value of CSV get method, if I return response object return flask.Response(data, mimetype='text/csv') a data become malformed - only keys without values. How to enable different mimetype without data damage?


Solution

  • Firstly, please note that you should not use the same resource to return either a single action or a list of actions. You should use two different resources:

    class Action(flask_restful.Resource):
        def get(self, action_id):
            return actions[action_id]
    
    class ActionList(flask_restful.Resource):
        def get(self):
            return actions
    

    Then, the simplest way to return different media types for the same resource is to use content negotiation. In this case, you do not need to declare a dedicated resource ActionAsCSV to specifically handle the case of returning a response in CSV format. For example, to use content negotiation with curl:

    curl -iH "Accept: text/csv" http://URL_TO_YOUR_VIEW

    Moreover, Flask-RESTful automatically add the right content-type header for you in the returned response: you do not need to define it in the get method of your resource.

    Also, the API is configured by default to return representations in JSON. However, you can modify that as follow:

    api = flask_restful.Api(app, default_mediatype="text/csv")
    

    If you absolutly want two different resources to handle either application/json or text/csv, withous using content negotiation and no fallback media type, this is possible:

    api = flask_restful.Api(app, default_mediatype=None)
    
    class ActionListMixin(object):
        def get(self):
            return actions
    
    class JsonActionList(ActionListMixin, flask_restful.Resource):
        representations = {'application/json': output_json}
    
    class CsvActionList(ActionListMixin, flask_restful.Resource):
        representations = {'text/csv': output_csv}
    

    Anoter similar option is to define representation transformers when adding your resources:

    api = flask_restful.Api(app, default_mediatype=None)
    
    class ActionList(flask_restful.Resource):
        def __init__(self, representations=None):
            self.representations = representations
            super(ActionList, self).__init__()
    
        def get(self):
            return actions
    
    api.add_resource(ActionList, '/actions_json', resource_class_kwargs={'representations': {'application/json': output_json}})
    api.add_resource(ActionList, '/actions_csv', resource_class_kwargs={'representations': {'text/csv': output_csv}})