====== 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))''