Flask Nested Blueprints Example
They're handy for adding common behavior to admin pages, versioned APIs, etc. or grouping up related blueprints.
So you’ve decided nested blueprints are what you need and now you want to use them.
Maybe you’ve encountered circular dependency errors, other issues or the docs weren’t quite enough to get you going. Got it, let’s go over a real example of using them in a multi-file project that uses the Flask app factory pattern and write tests too.
We’re going to use my example Flask / Docker app so we have a baseline app to work with if you want to follow along but nothing here is specific to Docker.
I’m mainly going to show the new lines or files that were added to the example app with a brief summary so you can use it as a reference. The demo video goes into more detail.
# Create App Function
Let’s start with the code and a few comments and then I’ll add more details afterwards.
hello/app.py
:
# Import our parent Blueprint (we'll create this next):
from hello.api.v1 import api_v1
def create_app(settings_override=None):
# ...
# Register our parent Blueprint.
app.register_blueprint(api_v1)
# ...
The main takeaway here is our app factory function won’t be registering each individual child Blueprint. Instead we register the parent Blueprint and then we register all child Blueprints to the parent within the parent’s views file.
For example you can imagine the api_v1
Blueprint having many different child
Blueprints such as users
, todos
or whatever API endpoints your app has.
This pattern is nice because it avoids circular dependencies and it also sets the stage that your main app factory function doesn’t need to be updated as you add new API endpoints. That’s now a responsibility of the parent Blueprint.
# Parent Blueprint (API v1)
This is where our common behavior will live in our example API use case but if you’re not building an API you can do similar things.
For example maybe you have an admin
parent Blueprint with a bunch of child
Blueprints or you have an about
parent Blueprint with us
and policies
as
child Blueprints.
In either case, even if there’s no common behavior it could be nice if only to categorize and group up related code.
hello/api/v1/__init__.py
:
from functools import wraps
from flask import Blueprint
from flask import jsonify
from flask import request
# This is a child Blueprint we'll register down below.
from hello.api.v1.todos.views import todos
# This is the parent Blueprint we imported in app.py earlier.
api_v1 = blueprint("api_v1", __name__, url_prefix="/api/v1")
# Here's where we'll register all of our child Blueprints, we only have 1 here
# but you can register each one separately. We'll cover todos next.
api_v1.register_blueprint(todos)
# This function is not important for nested blueprints, but I did want to demo
# accessing the API with and without valid tokens. This is throw away code.
def token_auth():
"""
TODO: replace this function with a proper auth library such as flask-login.
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header_token = request.headers.get("authorization")
# This is meant to be a super simple and clearly not secure auth
# decorator. You'd normally set a `current_user` and then compare
# their encrypted token to what's in the header.
current_user_token = "abc123"
if not auth_header_token == f"bearer {current_user_token}":
# This decorator function isn't meant to be the best possible
# solution in the world. It's a toy example to demo using
# nested blueprints. I'd normally do this in a different spot.
data = {
"error": "invalid token",
}
return jsonify(data), 401
return f(*args, **kwargs)
return decorated_function
return decorator
# We're using our auth decorator above to ensure all API endpoints require
# authentication. This pattern is really useful from time to time! It takes
# advantage of Flask's before_request feature (not related to nested blueprints).
@api_v1.before_request
@token_auth()
def before_request():
"""we want all of our v1 endpoints to be authenticated."""
pass
An important takeaway is we’re calling register_blueprint
on todos
through
api_v1
instead the main Flask app. That’s the link which associates todos
as a child to the api_v1
parent.
Since todos
is a child, it’ll be authenticated due to the parent enforcing
authentication. Also, all of its URLs will be prefixed to /api/v1/
since we
set url_prefix
on the parent.
If you ever wanted to introduce v2 of your API you can make a new api_v2
parent Blueprint and register new child Blueprints to that.
# Child Blueprint (Todos)
This file has patterns you’ve probably seen a bunch of times.
hello/api/v1/todos/views.py
:
from flask import Blueprint
from flask import jsonify
# Here's our child Blueprint.
todos = Blueprint("todos", __name__, url_prefix="/todos")
# What we're doing isn't important, I just put together a basic API example.
@todos.get("/")
def index():
"""
GET /api/v1/todos/
"""
# Normally you would access this from a database. Personally I'd also
# replace `completed` with `completed_at` with a timestamp so you can
# track that it's completed and you know when it happened.
data = [
{
"id": 1,
"item": "Create nested blueprints example",
"completed": True,
}
]
return jsonify(data)
What’s nice here is our child Blueprint has no awareness of its parent. It’s built as if it’s a standalone Blueprint. It just happens to be registered onto a parent Blueprint instead of directly on the Flask app.
# Testing It Out
At this point, if you’re following along you can build and run the project and then use curl or any HTTP client to make requests to our glorious API.
First with a valid token:
$ curl \
--header "Content-Type: application/json" \
--header "Authorization: Bearer abc123" \
localhost:8000/api/v1/todos/
[
{
"completed": true,
"id": 1,
"item": "Create nested blueprints example"
}
]
And now with an invalid token:
$ curl \
--header "Content-Type: application/json" \
--header "Authorization: Bearer nope" \
localhost:8000/api/v1/todos/
{
"error": "Invalid token"
}
Creating Automated Tests with PyTest
Manually testing your app is a good idea but automated tests are a very welcome addition.
Also the tests below demonstrate how to access your child Blueprint using
Flask’s url_for
function. There’s nothing fancy going on, you prefix it with
the parent such as api_v1.todos.index
which you can see below.
hello/api/v1/todos/views.py
:
from flask import url_for
from lib.test import ViewTestMixin
class TestTodosViews(ViewTestMixin):
def test_todos_without_auth(self):
response = self.client.get(url_for("api_v1.todos.index"))
assert response.status_code == 401
def test_todos_with_invalid_auth(self):
response = self.client.get(
url_for("api_v1.todos.index"),
headers={"Authorization": "Bearer nope"},
content_type="application/json",
)
assert response.status_code == 401
def test_todos_with_valid_auth(self):
response = self.client.get(
url_for("api_v1.todos.index"),
headers={"Authorization": "Bearer abc123"},
content_type="application/json",
)
assert response.status_code == 200
We also have 100% test coverage:
hello/api/v1/__init__.py 23 0 100%
hello/api/v1/todos/__init__.py 0 0 100%
hello/api/v1/todos/views.py 7 0 100%
The demo video below goes into more detail but it covers the same code as above.
# Demo Video
Timestamps
- 0:04 – A few nested blueprints use cases
- 1:36 – We’ll make a versioned API
- 2:18 – Create app function
- 2:52 – Defining an API v1 parent blueprint with bearer token authentication
- 5:08 – Checking out the todos child blueprint of API v1
- 6:20 – Registering a blueprint on another blueprint
- 6:53 – Please don’t use this exact auth decorator in production
- 7:41 – Automated tests and referencing child blueprints with url_for
- 9:20 – Going over the git diff for all of the changes
References
What types of things are you using nested blueprints for? Let us know below!