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:
myapi/
__init__.py
app.py # this file contains your app and routes
resources/
__init__.py
foo.py # contains logic for /Foo
bar.py # contains logic for /Bar
common/
__init__.py
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:
from flask_restful import Resource
class Foo(Resource):
def get(self):
pass
def post(self):
pass
The key to this setup lies in app.py
:
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>')
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
.
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)
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”.
from flask_restful import fields, marshal_with, reqparse, Resource
def email(email_str):
"""Return email_str if valid, raise an exception in other case."""
if valid_email(email_str):
return email_str
else:
raise ValueError('{} is not a valid email'.format(email_str))
post_parser = reqparse.RequestParser()
post_parser.add_argument(
'username', dest='username',
location='form', required=True,
help='The user\'s username',
)
post_parser.add_argument(
'email', dest='email',
type=email, location='form',
required=True, help='The user\'s email',
)
post_parser.add_argument(
'user_priority', dest='user_priority',
type=int, location='form',
default=1, choices=range(5), help='The user\'s priority',
)
user_fields = {
'id': fields.Integer,
'username': fields.String,
'email': fields.String,
'user_priority': fields.Integer,
'custom_greeting': fields.FormattedString('Hey there {username}!'),
'date_created': fields.DateTime,
'date_updated': fields.DateTime,
'links': fields.Nested({
'friends': fields.Url('user_friends'),
'posts': fields.Url('user_posts'),
}),
}
class User(Resource):
@marshal_with(user_fields)
def post(self):
args = post_parser.parse_args()
user = create_user(args.username, args.email, args.user_priority)
return user
@marshal_with(user_fields)
def get(self, id):
args = post_parser.parse_args()
user = fetch_user(id)
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.
post_parser.add_argument(
'username', dest='username',
location='form', required=True,
help='The user\'s username',
)
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’.
post_parser.add_argument(
'email', dest='email',
type=email, location='form',
required=True, help='The user\'s email',
)
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.
post_parser.add_argument(
'user_priority', dest='user_priority',
type=int, location='form',
default=1, choices=range(5), help='The user\'s priority',
)
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.
user_fields = {
'id': fields.Integer,
'username': fields.String,
'email': fields.String,
'user_priority': fields.Integer,
'custom_greeting': fields.FormattedString('Hey there {username}!'),
'date_created': fields.DateTime,
'date_updated': fields.DateTime,
'links': fields.Nested({
'friends': fields.Url('user_friends', absolute=True),
'posts': fields.Url('user_friends', absolute=True),
}),
}
First up, there’s fields.FormattedString
.
'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
.
'links': fields.Nested({
'friends': fields.Url('user_friends', absolute=True),
'posts': fields.Url('user_posts', absolute=True),
}),
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.
'friends': fields.Url('user_friends', absolute=True),
'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
:
from flask_restful import Resource
class TodoNext(Resource):
def __init__(self, **kwargs):
# smart_engine is a black box dependency
self.smart_engine = kwargs['smart_engine']
def get(self):
return self.smart_engine.next_todo()
You can inject the required dependency into TodoNext like so:
smart_engine = SmartEngine()
api.add_resource(TodoNext, '/next',
resource_class_kwargs={ 'smart_engine': smart_engine })
Same idea applies for forwarding args.
Table of Contents
Related Topics
- Documentation overview
- Previous: Extending Flask-RESTful
- Next: API Docs