Difference between revisions of "Python - Flask"
(→Rest APIs Development with Flask-RESTful) |
|||
(2 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
= Rest APIs Development with Flask-RESTful= | = Rest APIs Development with Flask-RESTful= | ||
+ | |||
+ | * a nice quick book for Lask-RESTful : https://readthedocs.org/projects/flask-restful/downloads/pdf/latest/ | ||
Flask extension that simplifies the creation of APIs. | Flask extension that simplifies the creation of APIs. | ||
Line 152: | Line 154: | ||
==Generating Responses== | ==Generating Responses== | ||
+ | My original REST server generates the responses using Flask's jsonify helper function. Flask-RESTful automatically handles the conversion to JSON, so instead of this: | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | return jsonify( { 'task': make_public_task(task) } ) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | we can do this: | ||
+ | <syntaxhighlight lang="python"> | ||
+ | return { 'task': make_public_task(task) } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Flask-RESTful also supports passing a custom status code back when necessary: | ||
+ | <syntaxhighlight lang="python"> | ||
+ | return { 'task': make_public_task(task) }, 201 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | But there is more. The make_public_task wrapper from the original server converted a task from its internal representation to the external representation that clients expected. The conversion included removing the id field and adding a uri field in its place. Flask-RESTful provides a helper function to do this in a much more elegant way that not only generates the uri but also does type conversion on the remaining fields: | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | from flask_restful import fields, marshal | ||
+ | |||
+ | task_fields = { | ||
+ | 'title': fields.String, | ||
+ | 'description': fields.String, | ||
+ | 'done': fields.Boolean, | ||
+ | 'uri': fields.Url('task') | ||
+ | } | ||
+ | |||
+ | class TaskAPI(Resource): | ||
+ | # ... | ||
+ | |||
+ | def put(self, id): | ||
+ | # ... | ||
+ | return { 'task': marshal(task, task_fields) } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | The task_fields structure serves as a template for the marshal function. The fields.Url type is a special type that generates a URL. The argument it takes is the endpoint (recall that we have used explicit endpoints when we registered the resources specifically so that we can refer to them when needed). | ||
+ | |||
+ | == Authentication== | ||
+ | The routes in the REST server are all protected with HTTP basic authentication. In the original server the protection was added using the decorator provided by the Flask-HTTPAuth extension. | ||
+ | |||
+ | Since the Resouce class inherits from Flask's MethodView, it is possible to attach decorators to the methods by defining a decorators class variable: | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | from flask_httpauth import HTTPBasicAuth | ||
+ | # ... | ||
+ | auth = HTTPBasicAuth() | ||
+ | # ... | ||
+ | |||
+ | class TaskAPI(Resource): | ||
+ | decorators = [auth.login_required] | ||
+ | # ... | ||
+ | |||
+ | class TaskAPI(Resource): | ||
+ | decorators = [auth.login_required] | ||
+ | # ... | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | resource: https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful |
Latest revision as of 07:26, 22 September 2020
Contents
Rest APIs Development with Flask-RESTful
- a nice quick book for Lask-RESTful : https://readthedocs.org/projects/flask-restful/downloads/pdf/latest/
Flask extension that simplifies the creation of APIs.
The RESTful server
Here is the definition of the ToDo List web service that has been serving as an example:
HTTP Method URI Action
GET http://[hostname]/todo/api/v1.0/tasks Retrieve list of tasks
GET http://[hostname]/todo/api/v1.0/tasks/[task_id] Retrieve a task
POST http://[hostname]/todo/api/v1.0/tasks Create a new task
PUT http://[hostname]/todo/api/v1.0/tasks/[task_id] Update an existing task
DELETE http://[hostname]/todo/api/v1.0/tasks/[task_id] Delete a task
The only resource exposed by this service is a "task", which has the following data fields:
- uri: unique URI for the task. String type.
- title: short task description. String type.
- description: long task description. Text type.
- done: task completion state. Boolean type.
Routing
Flask-RESTful provides a Resource base class that can define the routing for one or more HTTP methods for a given URL. For example, to define a User resource with GET, PUT and DELETE methods you would write:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class UserAPI(Resource):
def get(self, id):
pass
def put(self, id):
pass
def delete(self, id):
pass
api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
The add_resource function registers the routes with the framework using the given endpoint. If an endpoint isn't given then Flask-RESTful generates one for you from the class name, but since sometimes the endpoint is needed for functions such as url_for I prefer to make it explicit.
My ToDo API defines two URLs: /todo/api/v1.0/tasks for the list of tasks, and /todo/api/v1.0/tasks/<int:id> for an individual task. Since Flask-RESTful's Resource class can wrap a single URL this server will need two resources:
class TaskListAPI(Resource):
def get(self):
pass
def post(self):
pass
class TaskAPI(Resource):
def get(self, id):
pass
def put(self, id):
pass
def delete(self, id):
pass
api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint = 'tasks')
api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint = 'task')
Note that while the method views of TaskListAPI receive no arguments the ones in TaskAPI all receive the id, as specified in the URL under which the resource is registered.
Request Parsing and Validation
When you implemented your own validation of the request data. For example, look at how long the PUT handler is in that version:
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['PUT'])
@auth.login_required
def update_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify( { 'task': make_public_task(task[0]) } )
Here you have to make sure the data given with the request is valid before using it, and that makes the function pretty long.
Flask-RESTful provides a much better way to handle this with the RequestParser class. This class works in a similar way as argparse for command line arguments.
First, for each resource I define the arguments and how to validate them:
from flask_restful import reqparse
class TaskListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, required = True,
help = 'No task title provided', location = 'json')
self.reqparse.add_argument('description', type = str, default = "", location = 'json')
super(TaskListAPI, self).__init__()
# ...
class TaskAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, location = 'json')
self.reqparse.add_argument('description', type = str, location = 'json')
self.reqparse.add_argument('done', type = bool, location = 'json')
super(TaskAPI, self).__init__()
# ...
In the TaskListAPI resource the POST method is the only one the receives arguments. The title argument is required here, so I included an error message that Flask-RESTful will send as a response to the client when the field is missing. The description field is optional, and when it is missing a default value of an empty string will be used. One interesting aspect of the RequestParser class is that by default it looks for fields in request.values, so the location optional argument must be set to indicate that the fields are coming in request.json.
The request parser for the TaskAPI is constructed in a similar way, but has a few differences. In this case it is the PUT method that will need to parse arguments, and for this method all the arguments are optional, including the done field that was not part of the request in the other resource.
Now that the request parsers are initialized, parsing and validating a request is pretty easy. For example, note how much simpler the TaskAPI.put() method becomes:
def put(self, id):
task = filter(lambda t: t['id'] == id, tasks)
if len(task) == 0:
abort(404)
task = task[0]
args = self.reqparse.parse_args()
for k, v in args.iteritems():
if v != None:
task[k] = v
return jsonify( { 'task': make_public_task(task) } )
A side benefit of letting Flask-RESTful do the validation is that now there is no need to have a handler for the bad request code 400 error, this is all taken care of by the extension.
Generating Responses
My original REST server generates the responses using Flask's jsonify helper function. Flask-RESTful automatically handles the conversion to JSON, so instead of this:
return jsonify( { 'task': make_public_task(task) } )
we can do this:
return { 'task': make_public_task(task) }
Flask-RESTful also supports passing a custom status code back when necessary:
return { 'task': make_public_task(task) }, 201
But there is more. The make_public_task wrapper from the original server converted a task from its internal representation to the external representation that clients expected. The conversion included removing the id field and adding a uri field in its place. Flask-RESTful provides a helper function to do this in a much more elegant way that not only generates the uri but also does type conversion on the remaining fields:
from flask_restful import fields, marshal
task_fields = {
'title': fields.String,
'description': fields.String,
'done': fields.Boolean,
'uri': fields.Url('task')
}
class TaskAPI(Resource):
# ...
def put(self, id):
# ...
return { 'task': marshal(task, task_fields) }
The task_fields structure serves as a template for the marshal function. The fields.Url type is a special type that generates a URL. The argument it takes is the endpoint (recall that we have used explicit endpoints when we registered the resources specifically so that we can refer to them when needed).
Authentication
The routes in the REST server are all protected with HTTP basic authentication. In the original server the protection was added using the decorator provided by the Flask-HTTPAuth extension.
Since the Resouce class inherits from Flask's MethodView, it is possible to attach decorators to the methods by defining a decorators class variable:
from flask_httpauth import HTTPBasicAuth
# ...
auth = HTTPBasicAuth()
# ...
class TaskAPI(Resource):
decorators = [auth.login_required]
# ...
class TaskAPI(Resource):
decorators = [auth.login_required]
# ...
resource: https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful