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?
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}})