Skip to main content

Modular Architecture

Modular architecture in this codebase is centered around the Blueprint system, which allows developers to organize applications into distinct, reusable components. This system enables the definition of routes, error handlers, and middleware in isolation, which are later "recorded" onto a central application instance.

The Blueprint Hierarchy

The modularity system is built on a three-tier class hierarchy located in src/flask/sansio/ and src/flask/:

  1. Scaffold (src/flask/sansio/scaffold.py): The base class shared by both the main Flask application and Blueprint. it provides the core decorators for routing and request lifecycle management, such as @route, @before_request, and @errorhandler.
  2. SansioBlueprint (src/flask/sansio/blueprints.py): A "Sans-I/O" implementation of the blueprint logic. It introduces the ability to "record" setup operations (like adding a route) into a deferred_functions list rather than applying them immediately.
  3. Blueprint (src/flask/blueprints.py): The standard implementation used by developers. It extends SansioBlueprint with WSGI-specific features, such as serving static files via send_static_file and providing a cli group for blueprint-specific terminal commands.

Defining and Registering Blueprints

A blueprint acts as a template for a section of an application. It is defined independently and then registered on the application instance, often within an application factory.

Basic Definition

In the flaskr tutorial example (examples/tutorial/flaskr/auth.py), a blueprint is used to isolate authentication logic:

from flask import Blueprint

# Define the blueprint with a name and URL prefix
bp = Blueprint("auth", __name__, url_prefix="/auth")

@bp.route("/register", methods=("GET", "POST"))
def register():
# View logic here
...

Application Registration

The blueprint is then attached to the application in the factory function (examples/tutorial/flaskr/__init__.py):

def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)

from . import auth
app.register_blueprint(auth.bp)

return app

The Registration Lifecycle

When app.register_blueprint(bp) is called, the following process occurs within src/flask/sansio/blueprints.py:

  1. State Creation: The blueprint creates a BlueprintSetupState object. This object holds the configuration for this specific registration, including the url_prefix, subdomain, and a reference to the app.
  2. Prefixing: The BlueprintSetupState.add_url_rule method automatically prepends the blueprint's url_prefix to all routes and prefixes the endpoint with the blueprint's name (e.g., auth.register).
  3. Execution: All functions stored in bp.deferred_functions (populated by decorators like @bp.route) are executed, passing the state object to each.
  4. Merging: The _merge_blueprint_funcs method merges the blueprint's error handlers and request hooks into the application's global collections.

Hierarchical Structures (Nested Blueprints)

Blueprints can be nested within other blueprints to create complex URL and logic hierarchies. When a blueprint is registered on another blueprint, prefixes and subdomains are concatenated.

As demonstrated in tests/test_blueprints.py:

parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__)
grandchild = Blueprint("grandchild", __name__)

# Nesting blueprints
child.register_blueprint(grandchild, url_prefix="/grandchild")
parent.register_blueprint(child, url_prefix="/child")
app.register_blueprint(parent, url_prefix="/parent")

In this structure, a route defined on grandchild at /index would be accessible at /parent/child/grandchild/index. The endpoint would be registered as parent.child.grandchild.index.

Scope and Isolation

The Scaffold class allows blueprints to maintain their own isolated request lifecycle and error handling. However, blueprints also provide "app-level" variants of these decorators to register handlers globally from within a module.

Local vs. Global Handlers

  • Local: @bp.before_request or @bp.errorhandler(404) only trigger for requests handled by that specific blueprint.
  • Global: Methods prefixed with app_ (defined in src/flask/sansio/blueprints.py) affect the entire application.
Local DecoratorGlobal EquivalentDescription
@bp.before_request@bp.before_app_requestRuns before every request in the app.
@bp.errorhandler@bp.app_errorhandlerHandles errors for the entire application.
@bp.context_processor@bp.app_context_processorInjects variables into all templates.

Template and Static File Isolation

Blueprints can define their own template_folder and static_folder.

  • Templates: Blueprint templates have lower precedence than the main application's templates, allowing users to override blueprint-provided templates by placing a file with the same name in the app's template directory.
  • Static Files: If a blueprint has a static_folder but no url_prefix, the application's main static route takes precedence. It is recommended to use a url_prefix when serving blueprint-specific static assets.