.. _quickstart: Quickstart ========== .. currentmodule:: flask_restful It's time to write your first REST API. This guide assumes you have a working understanding of `Flask `_, and that you have already installed both Flask and Flask-RESTful. If not, then follow the steps in the :ref:`installation` section. A Minimal API ------------- A minimal Flask-RESTful API looks like this: :: 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) Save this as api.py and run it using your Python interpreter. Note that we've enabled `Flask debugging `_ mode to provide code reloading and better error messages. :: $ python api.py * Running on http://127.0.0.1:5000/ * Restarting with reloader .. warning:: Debug mode should never be used in a production environment! Now open up a new prompt to test out your API using curl :: $ curl http://127.0.0.1:5000/ {"hello": "world"} Resourceful Routing ------------------- The main building block provided by Flask-RESTful are resources. Resources are built on top of `Flask pluggable views `_, giving you easy access to multiple HTTP methods just by defining methods on your resource. A basic CRUD resource for a todo application (of course) looks like this: :: 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) You can try it like this: :: $ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT {"todo1": "Remember the milk"} $ curl http://localhost:5000/todo1 {"todo1": "Remember the milk"} $ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT {"todo2": "Change my brakepads"} $ curl http://localhost:5000/todo2 {"todo2": "Change my brakepads"} Or from python if you have the ``requests`` library installed:: >>> 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'} >>> put('http://localhost:5000/todo2', data={'data': 'Change my brakepads'}).json() {u'todo2': u'Change my brakepads'} >>> get('http://localhost:5000/todo2').json() {u'todo2': u'Change my brakepads'} Flask-RESTful understands multiple kinds of return values from view methods. Similar to Flask, you can return any iterable and it will be converted into a response, including raw Flask response objects. Flask-RESTful also support setting the response code and response headers using multiple return values, as shown below: :: class Todo1(Resource): def get(self): # Default to 200 OK return {'task': 'Hello world'} class Todo2(Resource): def get(self): # Set the response code to 201 return {'task': 'Hello world'}, 201 class Todo3(Resource): def get(self): # Set the response code to 201 and return custom headers return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'} Endpoints --------- Many times in an API, your resource will have multiple URLs. You can pass multiple URLs to the :meth:`~Api.add_resource` method on the `Api` object. Each one will be routed to your :class:`Resource` :: api.add_resource(HelloWorld, '/', '/hello') You can also match parts of the path as variables to your resource methods. :: api.add_resource(Todo, '/todo/', endpoint='todo_ep') .. note :: If a request does not match any of your application's endpoints, Flask-RESTful will return a 404 error message with suggestions of other endpoints that closely match the requested endpoint. This can be disabled by setting ``ERROR_404_HELP`` to ``False`` in your application config. Argument Parsing ---------------- While Flask provides easy access to request data (i.e. querystring or POST form encoded data), it's still a pain to validate form data. Flask-RESTful has built-in support for request data validation using a library similar to `argparse `_. :: 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() .. note :: Unlike the argparse module, :meth:`reqparse.RequestParser.parse_args` returns a Python dictionary instead of a custom data structure. Using the :class:`reqparse` module also gives you sane error messages for free. If an argument fails to pass validation, Flask-RESTful will respond with a 400 Bad Request and a response highlighting the error. :: $ curl -d 'rate=foo' http://127.0.0.1:5000/todos {'status': 400, 'message': 'foo cannot be converted to int'} The :class:`inputs` module provides a number of included common conversion functions such as :meth:`inputs.date` and :meth:`inputs.url`. Calling ``parse_args`` with ``strict=True`` ensures that an error is thrown if the request includes arguments your parser does not define. :: args = parser.parse_args(strict=True) Data Formatting --------------- By default, all fields in your return iterable will be rendered as-is. While this works great when you're just dealing with Python data structures, it can become very frustrating when working with objects. To solve this problem, Flask-RESTful provides the :class:`fields` module and the :meth:`marshal_with` decorator. Similar to the Django ORM and WTForm, you use the ``fields`` module to describe the structure of your response. :: 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') The above example takes a python object and prepares it to be serialized. The :meth:`marshal_with` decorator will apply the transformation described by ``resource_fields``. The only field extracted from the object is ``task``. The :class:`fields.Url` field is a special field that takes an endpoint name and generates a URL for that endpoint in the response. Many of the field types you need are already included. See the :class:`fields` guide for a complete list. Full Example ------------ Save this example in api.py :: from flask import Flask from flask_restful import reqparse, abort, Api, Resource app = Flask(__name__) api = Api(app) TODOS = { 'todo1': {'task': 'build an API'}, 'todo2': {'task': '?????'}, 'todo3': {'task': 'profit!'}, } def abort_if_todo_doesnt_exist(todo_id): if todo_id not in TODOS: abort(404, message="Todo {} doesn't exist".format(todo_id)) parser = reqparse.RequestParser() parser.add_argument('task') # Todo # shows a single todo item and lets you delete a todo item class Todo(Resource): def get(self, todo_id): abort_if_todo_doesnt_exist(todo_id) return TODOS[todo_id] def delete(self, todo_id): abort_if_todo_doesnt_exist(todo_id) del TODOS[todo_id] return '', 204 def put(self, todo_id): args = parser.parse_args() task = {'task': args['task']} TODOS[todo_id] = task return task, 201 # TodoList # shows a list of all todos, and lets you POST to add new tasks class TodoList(Resource): def get(self): return TODOS def post(self): args = parser.parse_args() todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1 todo_id = 'todo%i' % todo_id TODOS[todo_id] = {'task': args['task']} return TODOS[todo_id], 201 ## ## Actually setup the Api resource routing here ## api.add_resource(TodoList, '/todos') api.add_resource(Todo, '/todos/') if __name__ == '__main__': app.run(debug=True) Example usage :: $ python api.py * Running on http://127.0.0.1:5000/ * Restarting with reloader GET the list :: $ curl http://localhost:5000/todos {"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}} GET a single task :: $ curl http://localhost:5000/todos/todo3 {"task": "profit!"} DELETE a task :: $ curl http://localhost:5000/todos/todo2 -X DELETE -v > DELETE /todos/todo2 HTTP/1.1 > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3 > Host: localhost:5000 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 204 NO CONTENT < Content-Type: application/json < Content-Length: 0 < Server: Werkzeug/0.8.3 Python/2.7.2 < Date: Mon, 01 Oct 2012 22:10:32 GMT Add a new task :: $ curl http://localhost:5000/todos -d "task=something new" -X POST -v > POST /todos HTTP/1.1 > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3 > Host: localhost:5000 > Accept: */* > Content-Length: 18 > Content-Type: application/x-www-form-urlencoded > * HTTP 1.0, assume close after body < HTTP/1.0 201 CREATED < Content-Type: application/json < Content-Length: 25 < Server: Werkzeug/0.8.3 Python/2.7.2 < Date: Mon, 01 Oct 2012 22:12:58 GMT < * Closing connection #0 {"task": "something new"} Update a task :: $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v > PUT /todos/todo3 HTTP/1.1 > Host: localhost:5000 > Accept: */* > Content-Length: 20 > Content-Type: application/x-www-form-urlencoded > * HTTP 1.0, assume close after body < HTTP/1.0 201 CREATED < Content-Type: application/json < Content-Length: 27 < Server: Werkzeug/0.8.3 Python/2.7.3 < Date: Mon, 01 Oct 2012 22:13:00 GMT < * Closing connection #0 {"task": "something different"}