noirbizarre / flask-restplus

Fully featured framework for fast, easy and documented API development with Flask
http://flask-restplus.readthedocs.org
Other
2.73k stars 506 forks source link

Representing output type without using marshal and marshaling basic types #43

Open jpbufe3 opened 9 years ago

jpbufe3 commented 9 years ago

I have a very simple method that is generating a list of strings and returning it. Currently the only way that the swagger docs will show the response type is if the method has @api.marshal_with() or @api.marshal_list_with(). However, neither of these methods seem to work appropriately with simple objects. Below is an example:

from flask import Flask
from flask.ext.restplus import Api, Resource, fields
from nltk.tokenize import word_tokenize
import os, nltk

app = Flask(__name__)
api = Api(app, version='1.0', title='NLP Tools API',
    description='A set of easy to use NLP tools powered by NLTK.',
)

ns = api.namespace('nlptools/api/v1', description='NLP operations')
parser = api.parser()
parser.add_argument('text', type=str, required=True, help='The text to be processed', location='json')

@ns.route('/tokenize')
@api.doc(responses={400: 'Invalid request'})
class Tokenizer(Resource):
    @api.doc(parser=parser, description='Tokenizes the input text into a list of strings.')
    @api.marshal_list_with(fields.String)
    def post(self):
        args = parser.parse_args()
        return word_tokenize(args['text'])

if __name__ == '__main__':
    app.run(host=os.getenv('VCAP_APP_HOST', '127.0.0.1'), port=int(os.getenv('VCAP_APP_PORT', '5000')), debug=True)

Running this with a REST client with a json body of {"text":"this is a test sentence to tokenize."} returns the following error:

Traceback (most recent call last):
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 265, in error_router
    return original_handler(e)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.4/site-packages/flask/_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 265, in error_router
    return original_handler(e)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.4/site-packages/flask/_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 446, in wrapper
    resp = resource(*args, **kwargs)
  File "/usr/local/lib/python3.4/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 550, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 647, in wrapper
    return marshal(resp, self.fields, self.envelope)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 602, in marshal
    for k, v in fields.items())
AttributeError: type object 'String' has no attribute 'items'

I have also tried return json.dumps(word_tokenize(args['text'])) and @api.marshal_with(fields.List(fields.String)) with the same result in all four cases. Is there an alternative way to represent the response object? Is there a way to appropriate marshal basic fields?

Thanks.

noirbizarre commented 9 years ago

Hi !

Until now, I tried to stick to the upstream flask-restful which doesn't support this.

flask-restplus provides a workaround with the model kwarg for the @api.doc decorator:

from flask import Flask
from flask.ext.restplus import Api, Resource, fields
from nltk.tokenize import word_tokenize
import os, nltk

app = Flask(__name__)
api = Api(app, version='1.0', title='NLP Tools API',
    description='A set of easy to use NLP tools powered by NLTK.',
)

ns = api.namespace('nlptools/api/v1', description='NLP operations')
parser = api.parser()
parser.add_argument('text', type=str, required=True, help='The text to be processed', location='json')

@ns.route('/tokenize')
@api.doc(responses={400: 'Invalid request'})
class Tokenizer(Resource):
    @api.doc(parser=parser, description='Tokenizes the input text into a list of strings.')
    @api.doc(model=[str])
    def post(self):
        args = parser.parse_args()
        return word_tokenize(args['text'])

if __name__ == '__main__':
    app.run(host=os.getenv('VCAP_APP_HOST', '127.0.0.1'), port=int(os.getenv('VCAP_APP_PORT', '5000')), debug=True)

It should do the job. Just let me know if I can close the issue.

jpbufe3 commented 9 years ago

Thanks for the response.

I added in the change, and it fixed the method so that the REST call ran properly! 127.0.0.1 - - [08/May/2015 17:43:00] "POST /nlptools/api/v1/tokenize HTTP/1.1" 200 -

However, the swagger doc generation now fails.

127.0.0.1 - - [08/May/2015 17:32:21] "GET /swagger.json HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 265, in error_router
    return original_handler(e)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.4/site-packages/flask/_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 265, in error_router
    return original_handler(e)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.4/site-packages/flask/_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.4/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 450, in wrapper
    return self.make_response(data, code, headers=headers)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/__init__.py", line 474, in make_response
    resp = self.representations[mediatype](data, *args, **kwargs)
  File "/usr/local/lib/python3.4/site-packages/flask_restful/representations/json.py", line 24, in output_json
    dumped = dumps(data, **local_settings)
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 194, in encode
    chunks = list(chunks)
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 422, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 396, in _iterencode_dict
    yield from chunks
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 396, in _iterencode_dict
    yield from chunks
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 396, in _iterencode_dict
    yield from chunks
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 396, in _iterencode_dict
    yield from chunks
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 345, in _iterencode_dict
    items = sorted(dct.items(), key=lambda kv: kv[0])
TypeError: unorderable types: str() < int()

Any thoughts?