Intermediate Usage

This page covers building a slightly more complex Flask-RESTful app that will cover out some best practices when setting up a real-world Flask-RESTful-based API. The Quickstart section is great for getting started with your first Flask-RESTful app, so if you’re new to Flask-RESTful you’d be better off checking that out first.

Project Structure

There are many different ways to organize your Flask-RESTful app, but here we’ll describe one that scales pretty well with larger apps and maintains a nice level organization.

The basic idea is to split your app into three main parts: the routes, the resources, and any common infrastructure.

Here’s an example directory structure:

  1. myapi/
  2. __init__.py
  3. app.py # this file contains your app and routes
  4. resources/
  5. __init__.py
  6. foo.py # contains logic for /Foo
  7. bar.py # contains logic for /Bar
  8. common/
  9. __init__.py
  10. util.py # just some common infrastructure

The common directory would probably just contain a set of helper functions to fulfill common needs across your application. It could also contain, for example, any custom input/output types your resources need to get the job done.

In the resource files, you just have your resource objects. So here’s what foo.py might look like:

  1. from flask_restful import Resource
  2. class Foo(Resource):
  3. def get(self):
  4. pass
  5. def post(self):
  6. pass

The key to this setup lies in app.py:

  1. from flask import Flask
  2. from flask_restful import Api
  3. from myapi.resources.foo import Foo
  4. from myapi.resources.bar import Bar
  5. from myapi.resources.baz import Baz
  6. app = Flask(__name__)
  7. api = Api(app)
  8. api.add_resource(Foo, '/Foo', '/Foo/<str:id>')
  9. api.add_resource(Bar, '/Bar', '/Bar/<str:id>')
  10. api.add_resource(Baz, '/Baz', '/Baz/<str:id>')

As you can imagine with a particularly large or complex API, this file ends up being very valuable as a comprehensive list of all the routes and resources in your API. You would also use this file to set up any config values (before_request(), after_request()). Basically, this file configures your entire API.

The things in the common directory are just things you’d want to support your resource modules.

Use With Blueprints

See Modular Applications with Blueprints in the Flask documentation for what blueprints are and why you should use them. Here’s an example of how to link an Api up to a Blueprint.

  1. from flask import Flask, Blueprint
  2. from flask_restful import Api, Resource, url_for
  3. app = Flask(__name__)
  4. api_bp = Blueprint('api', __name__)
  5. api = Api(api_bp)
  6. class TodoItem(Resource):
  7. def get(self, id):
  8. return {'task': 'Say "Hello, World!"'}
  9. api.add_resource(TodoItem, '/todos/<int:id>')
  10. app.register_blueprint(api_bp)

Note

Calling Api.init_app() is not required here because registering the blueprint with the app takes care of setting up the routing for the application.

Full Parameter Parsing Example

Elsewhere in the documentation, we’ve described how to use the reqparse example in detail. Here we’ll set up a resource with multiple input parameters that exercise a larger amount of options. We’ll define a resource named “User”.

  1. from flask_restful import fields, marshal_with, reqparse, Resource
  2. def email(email_str):
  3. """Return email_str if valid, raise an exception in other case."""
  4. if valid_email(email_str):
  5. return email_str
  6. else:
  7. raise ValueError('{} is not a valid email'.format(email_str))
  8. post_parser = reqparse.RequestParser()
  9. post_parser.add_argument(
  10. 'username', dest='username',
  11. location='form', required=True,
  12. help='The user\'s username',
  13. )
  14. post_parser.add_argument(
  15. 'email', dest='email',
  16. type=email, location='form',
  17. required=True, help='The user\'s email',
  18. )
  19. post_parser.add_argument(
  20. 'user_priority', dest='user_priority',
  21. type=int, location='form',
  22. default=1, choices=range(5), help='The user\'s priority',
  23. )
  24. user_fields = {
  25. 'id': fields.Integer,
  26. 'username': fields.String,
  27. 'email': fields.String,
  28. 'user_priority': fields.Integer,
  29. 'custom_greeting': fields.FormattedString('Hey there {username}!'),
  30. 'date_created': fields.DateTime,
  31. 'date_updated': fields.DateTime,
  32. 'links': fields.Nested({
  33. 'friends': fields.Url('user_friends'),
  34. 'posts': fields.Url('user_posts'),
  35. }),
  36. }
  37. class User(Resource):
  38. @marshal_with(user_fields)
  39. def post(self):
  40. args = post_parser.parse_args()
  41. user = create_user(args.username, args.email, args.user_priority)
  42. return user
  43. @marshal_with(user_fields)
  44. def get(self, id):
  45. args = post_parser.parse_args()
  46. user = fetch_user(id)
  47. return user

As you can see, we create a post_parser specifically to handle the parsing of arguments provided on POST. Let’s step through the definition of each argument.

  1. post_parser.add_argument(
  2. 'username', dest='username',
  3. location='form', required=True,
  4. help='The user\'s username',
  5. )

The username field is the most normal out of all of them. It takes a string from the POST body and converts it to a string type. This argument is required (required=True), which means that if it isn’t provided, Flask-RESTful will automatically return a 400 with a message along the lines of ‘the username field is required’.

  1. post_parser.add_argument(
  2. 'email', dest='email',
  3. type=email, location='form',
  4. required=True, help='The user\'s email',
  5. )

The email field has a custom type of email. A few lines earlier we defined an email function that takes a string and returns it if the type is valid, else it raises an exception, exclaiming that the email type was invalid.

  1. post_parser.add_argument(
  2. 'user_priority', dest='user_priority',
  3. type=int, location='form',
  4. default=1, choices=range(5), help='The user\'s priority',
  5. )

The user_priority type takes advantage of the choices argument. This means that if the provided user_priority value doesn’t fall in the range specified by the choices argument (in this case [0, 1, 2, 3, 4]), Flask-RESTful will automatically respond with a 400 and a descriptive error message.

That covers the inputs. We also defined some interesting field types in the user_fields dictionary to showcase a couple of the more exotic types.

  1. user_fields = {
  2. 'id': fields.Integer,
  3. 'username': fields.String,
  4. 'email': fields.String,
  5. 'user_priority': fields.Integer,
  6. 'custom_greeting': fields.FormattedString('Hey there {username}!'),
  7. 'date_created': fields.DateTime,
  8. 'date_updated': fields.DateTime,
  9. 'links': fields.Nested({
  10. 'friends': fields.Url('user_friends', absolute=True),
  11. 'posts': fields.Url('user_friends', absolute=True),
  12. }),
  13. }

First up, there’s fields.FormattedString.

  1. 'custom_greeting': fields.FormattedString('Hey there {username}!'),

This field is primarily used to interpolate values from the response into other values. In this instance, custom_greeting will always contain the value returned from the username field.

Next up, check out fields.Nested.

  1. 'links': fields.Nested({
  2. 'friends': fields.Url('user_friends', absolute=True),
  3. 'posts': fields.Url('user_posts', absolute=True),
  4. }),

This field is used to create a sub-object in the response. In this case, we want to create a links sub-object to contain urls of related objects. Note that we passed fields.Nested another dict which is built in such a way that it would be an acceptable argument to marshal() by itself.

Finally, we used the fields.Url field type.

  1. 'friends': fields.Url('user_friends', absolute=True),
  2. 'posts': fields.Url('user_friends', absolute=True),

It takes as its first parameter the name of the endpoint associated with the urls of the objects in the links sub-object. Passing absolute=True ensures that the generated urls will have the hostname included.

Passing Constructor Parameters Into Resources

Your Resource implementation may require outside dependencies. Those dependencies are best passed-in through the constructor to loosely couple each other. The Api.add_resource() method has two keyword arguments: resource_class_args and resource_class_kwargs. Their values will be forwarded and passed into your Resource implementation’s constructor.

So you could have a Resource:

  1. from flask_restful import Resource
  2. class TodoNext(Resource):
  3. def __init__(self, **kwargs):
  4. # smart_engine is a black box dependency
  5. self.smart_engine = kwargs['smart_engine']
  6. def get(self):
  7. return self.smart_engine.next_todo()

You can inject the required dependency into TodoNext like so:

  1. smart_engine = SmartEngine()
  2. api.add_resource(TodoNext, '/next',
  3. resource_class_kwargs={ 'smart_engine': smart_engine })

Same idea applies for forwarding args.

Logo

Table of Contents

Related Topics

This Page

Quick search