Herramientas de usuario

Herramientas del sitio


wiki2:python:flask:restful

¡Esta es una revisión vieja del documento!


Flask RESTful extension

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, '/<string:todo_id>')
 
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/<int:todo_id>', 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: api.html).

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/<str:id>')
api.add_resource(Bar, '/Bar', '/Bar/<str:id>')
api.add_resource(Baz, '/Baz', '/Baz/<str:id>')

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/<int:id>')
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))
wiki2/python/flask/restful.1439033022.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)