Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Flask Nested Blueprints Example

blog/cards/flask-nested-blueprints-example.jpg

They're handy for adding common behavior to admin pages, versioned APIs, etc. or grouping up related blueprints.

Quick Jump: Create App Function | Parent Blueprint (API v1) | Child Blueprint (Todos) | Testing It Out | Demo Video

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!

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per month (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments