Autodiscovery of Blueprints, Middleware, and Listeners

How do I autodiscover the components I am using to build my application?

One of the first problems someone faces when building an application, is how to structure the project. Sanic makes heavy use of decorators to register route handlers, middleware, and listeners. And, after creating blueprints, they need to be mounted to the application.

A possible solution is a single file in which everything is imported and applied to the Sanic instance. Another is passing around the Sanic instance as a global variable. Both of these solutions have their drawbacks.

An alternative is autodiscovery. You point your application at modules (already imported, or strings), and let it wire everything up.

from sanic import Sanic
from sanic.response import empty

import blueprints
from utility import autodiscover

app = Sanic("auto", register=True)

app.route("/")(lambda _: empty())
[2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ nested
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level1
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level3
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside
[2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451]

from glob import glob
from importlib import import_module, util
from inspect import getmembers
from pathlib import Path
from types import ModuleType
from typing import Union

from sanic.blueprints import Blueprint

def autodiscover(
    app, *module_names: Union[str, ModuleType], recursive: bool = False
    mod = app.__module__
    blueprints = set()
    _imported = set()

    def _find_bps(module):
        nonlocal blueprints

        for _, member in getmembers(module):
            if isinstance(member, Blueprint):

    for module in module_names:
        if isinstance(module, str):
            module = import_module(module, mod)

        if recursive:
            base = Path(module.__file__).parent
            for path in glob(f"{base}/**/*.py", recursive=True):
                if path not in _imported:
                    name = "module"
                    if "__init__" in path:
                        *_, name, __ = path.split("/")
                    spec = util.spec_from_file_location(name, path)
                    specmod = util.module_from_spec(spec)

    for bp in blueprints:


from sanic import Blueprint
from sanic.log import logger

level1 = Blueprint("level1")

def print_something(app, loop):
    logger.debug("something @ level1")


from sanic import Blueprint
from sanic.log import logger

level3 = Blueprint("level3")

def print_something(app, loop):
    logger.debug("something @ level3")


from sanic import Sanic
from sanic.log import logger

app = Sanic.get_app("auto")

def print_something(app, loop):


from sanic import Blueprint
from sanic.log import logger

bp = Blueprint("__init__")

def print_something(app, loop):
    logger.debug("something inside")


from sanic import Blueprint
from sanic.log import logger

nested = Blueprint("nested")

def print_something(app, loop):
    logger.debug("something @ nested")

here is the dir tree
generate with 'find . -type d -name "__pycache__" -exec rm -rf {} +; tree'

. # run 'sanic sever -d' here
β”œβ”€β”€ blueprints
β”‚   β”œβ”€β”€ # you need add this file, just empty
β”‚   β”œβ”€β”€
β”‚   └── one
β”‚       └── two
β”‚           └──
β”œβ”€β”€ listeners
β”‚   └──
β”œβ”€β”€ parent
β”‚   └── child
β”‚       β”œβ”€β”€
β”‚       └──
source ./.venv/bin/activate # activate the python venv which sanic is installed in
sanic sever -d # run this in the directory containing
you will see "something ***" like this:

[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something inside
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ level3
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ level1
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ nested