====== Flask RESTful extension ====== * An alternative is: https://flask-restless.readthedocs.org/en/latest/ ==== Basic code ==== from flask import Flask from flask_restful import Resource, Api app = Flask(__name__) api = Api(app) class HelloWorld(Resource): def get(self): return {'hello': 'world'} api.add_resource(HelloWorld, '/') if __name__ == '__main__': app.run(debug=True) To define verbs for a resource you use a class which inherits from Resource, then you override its methods. In the next example we create a class with GET and PUT methods. For PUT method we accept data: from flask import Flask, request from flask_restful import Resource, Api app = Flask(__name__) api = Api(app) todos = {} class TodoSimple(Resource): def get(self, todo_id): return {todo_id: todos[todo_id]} def put(self, todo_id): todos[todo_id] = request.form['data'] return {todo_id: todos[todo_id]} api.add_resource(TodoSimple, '/') if __name__ == '__main__': app.run(debug=True) Then... >>> from requests import put, get >>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json() {u'todo1': u'Remember the milk'} >>> get('http://localhost:5000/todo1').json() {u'todo1': u'Remember the milk'} ==== Custom return ==== We also could return custom result codes and headers: # Default to 200 OK return {'task': 'Hello world'} # Set the response code to 201 return {'task': 'Hello world'}, 201 # Set the response code to 201 and return custom headers return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'} ==== Endpoints ==== You can add more url's for a resource: api.add_resource(HelloWorld, '/', '/hello') In the ''request'' object you could access to the endpoint property. This is set as the lowercase of the class name, however you can change it: api.add_resource(Todo, '/todo/', endpoint='todo_ep') Flask RESTful could suggest a proper endpoint if the one provided by the user does not match. You can change it setting ERROR_404_HELP to False in config. ==== Data acquisition and validation ==== Flask RESTful framework has the ''reqparse'' tool for sended data in the request form. Its method ''parse_args()'' returns a dictionary. from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('rate', type=int, help='Rate to charge for this resource') args = parser.parse_args() Using it you ensure the sended data is right, if not a 404 error will be return. Specially if you call it with ''strict=True'', then it ensures that an error is thrown if the request includes arguments your parser does not define. args = parser.parse_args(strict=True) Also you can use ''inputs'' module. It allows you to obtain formated data (for example using regex, dates, basic data like ints or strings, urls...): parser = reqparse.RequestParser() parser.add_argument('example', type=inputs.regex('^[0-9]+$')) To indicate a required argument: parser.add_argument('name', required=True, help="Name cannot be blank!") To indicate a list as argument: parser.add_argument('name', action='append') # curl http://api.example.com -d "name=bob" -d "name=sue" -d "name=joe" args = parser.parse_args() args['name'] # ['bob', 'sue', 'joe'] Or you want to change the destination: parser.add_argument('name', dest='public_name') args = parser.parse_args() args['public_name'] You can set the locations of the parameter (querystring, POST data...) # Look only in the POST body parser.add_argument('name', type=int, location='form') # Look only in the querystring parser.add_argument('PageSize', type=int, location='args') # From the request headers parser.add_argument('User-Agent', location='headers') # From http cookies parser.add_argument('session_id', location='cookies') # From file uploads parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files') # Multiple locations parser.add_argument('text', location=['headers', 'values']) ==== How to return data ==== You can specify which data you want to return using ''fields'' module and ''@marshal_with'' decorator. Fields module allows you to use ORM's and Python objects to return them and format them. resource_fields = { 'name': fields.String, 'address': fields.String, 'date_updated': fields.DateTime(dt_format='rfc822'), } You can specify the data to be sent using ''@marshal_with''. It serializes a python object. In the next example it takes the url of the ''todo_ep'' end point (you can take a look to other fields: {{https://flask-restful.readthedocs.org/en/0.3.4/api.html#module-fields}}). from collections import OrderedDict from flask_restful import fields, marshal_with resource_fields = { 'task': fields.String, 'uri': fields.Url('todo_ep') } class TodoDao(object): def __init__(self, todo_id, task): self.todo_id = todo_id self.task = task # This field will not be sent in the response self.status = 'active' class Todo(Resource): @marshal_with(resource_fields) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task='Remember the milk') ''@marshal_with'' is a decorator for the function ''marshal'': def get(self, **kwargs): return marshal(db_get_todo(), resource_fields), 200 With fields you can rename or format attributes. Even you can give a default value: fields = { 'name': fields.String(attribute='private_name', default='No name'), 'address': fields.String, } fields = { 'name': fields.String(attribute=lambda x: x._private_name), 'address': fields.String, } To give a custom format: class UrgentItem(fields.Raw): def format(self, value): return "Urgent" if value & 0x01 else "Normal" class UnreadItem(fields.Raw): def format(self, value): return "Unread" if value & 0x02 else "Read" fields = { 'name': fields.String, 'priority': UrgentItem(attribute='flags'), 'status': UnreadItem(attribute='flags'), } To return an absolute url from a resoruce: fields = { 'uri': fields.Url('todo_resource', absolute=True) 'https_uri': fields.Url('todo_resource', absolute=True, scheme='https') } You can return a field list: >>> from flask_restful import fields, marshal >>> import json >>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)} >>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']} >>> json.dumps(marshal(data, resource_fields)) >>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}' Or a nested field: user_fields = { 'id': fields.Integer, 'name': fields.String, } user_list_fields = { fields.List(fields.Nested(user_fields)), } >>> from flask_restful import fields, marshal >>> import json >>> >>> resource_fields = {'name': fields.String} >>> resource_fields['address'] = {} >>> resource_fields['address']['line 1'] = fields.String(attribute='addr1') >>> resource_fields['address']['line 2'] = fields.String(attribute='addr2') >>> resource_fields['address']['city'] = fields.String >>> resource_fields['address']['state'] = fields.String >>> resource_fields['address']['zip'] = fields.String >>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> json.dumps(marshal(data, resource_fields)) '{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}' >>> from flask_restful import fields, marshal >>> import json >>> >>> address_fields = {} >>> address_fields['line 1'] = fields.String(attribute='addr1') >>> address_fields['line 2'] = fields.String(attribute='addr2') >>> address_fields['city'] = fields.String(attribute='city') >>> address_fields['state'] = fields.String(attribute='state') >>> address_fields['zip'] = fields.String(attribute='zip') >>> >>> resource_fields = {} >>> resource_fields['name'] = fields.String >>> resource_fields['billing_address'] = fields.Nested(address_fields) >>> resource_fields['shipping_address'] = fields.Nested(address_fields) >>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2} >>> >>> json.dumps(marshal_with(data, resource_fields)) '{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}' ==== Scaling the app ==== You can have in an ''app.py'' file the defined end points: from flask import Flask from flask_restful import Api from myapi.resources.foo import Foo from myapi.resources.bar import Bar from myapi.resources.baz import Baz app = Flask(__name__) api = Api(app) api.add_resource(Foo, '/Foo', '/Foo/') api.add_resource(Bar, '/Bar', '/Bar/') api.add_resource(Baz, '/Baz', '/Baz/') That ''app.py'' would configure the API. You could also add there actions for before_request() and after_request() functions. === To use Blueprints === from flask import Flask, Blueprint from flask_restful import Api, Resource, url_for app = Flask(__name__) api_bp = Blueprint('api', __name__) api = Api(api_bp) class TodoItem(Resource): def get(self, id): return {'task': 'Say "Hello, World!"'} api.add_resource(TodoItem, '/todos/') app.register_blueprint(api_bp) ==== Other ==== * You can use the **abort** function to send an error: ''abort(404, message="Todo {} doesn't exist".format(todo_id))''