Blueprints
Flask Blueprints provide a way to organize your application into modular components. They help structure large applications by grouping related views, templates, and static files.
Basic Blueprint Creation
Simple Blueprint
# app/auth/routes.py
from flask import Blueprint, render_template, request, redirect, url_for
auth_bp = Blueprint(
'auth', # Blueprint name
__name__, # Import name
template_folder='templates', # Templates directory
static_folder='static', # Static files directory
url_prefix='/auth' # URL prefix for all routes
)
@auth_bp.route('/login')
def login():
return render_template('auth/login.html')
@auth_bp.route('/register')
def register():
return render_template('auth/register.html')
Blueprint Registration
# app/__init__.py
from flask import Flask
from app.auth.routes import auth_bp
from app.main.routes import main_bp
def create_app():
app = Flask(__name__)
# Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
return app
Project Structure
Recommended Structure
project/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── auth/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── forms.py
│ │ └── templates/
│ │ └── auth/
│ │ ├── login.html
│ │ └── register.html
│ ├── main/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── templates/
│ │ └── main/
│ │ ├── index.html
│ │ └── about.html
│ ├── api/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── schemas.py
│ ├── static/
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ └── templates/
│ └── base.html
├── migrations/
├── config.py
└── run.py
Blueprint Configuration
Blueprint with Configuration
# app/auth/__init__.py
from flask import Blueprint
def create_auth_blueprint(config=None):
auth_bp = Blueprint(
'auth',
__name__,
template_folder='templates',
static_folder='static',
url_prefix='/auth'
)
# Import routes after blueprint creation
from . import routes, forms
return auth_bp
Blueprint Factory Pattern
# app/auth/routes.py
from flask import Blueprint, current_app
def create_auth_routes(blueprint):
@blueprint.route('/login', methods=['GET', 'POST'])
def login():
# Use current_app to access app config
timeout = current_app.config.get('LOGIN_TIMEOUT', 3600)
return render_template('auth/login.html')
@blueprint.route('/logout')
def logout():
return redirect(url_for('main.index'))
# app/auth/__init__.py
def create_auth_blueprint():
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
create_auth_routes(auth_bp)
return auth_bp
URL Generation
Cross-Blueprint URL Generation
# From one blueprint to another
@main_bp.route('/')
def index():
login_url = url_for('auth.login')
return render_template('main/index.html', login_url=login_url)
# In templates
<a href="{{ url_for('auth.login') }}">Login</a>
<a href="{{ url_for('main.index') }}">Home</a>
<a href="{{ url_for('api.users') }}">API Users</a>
Blueprint Static Files
# Blueprint with static files
blog_bp = Blueprint(
'blog',
__name__,
static_folder='static',
static_url_path='/blog/static'
)
# Access static files
<link rel="stylesheet" href="{{ url_for('blog.static', filename='css/blog.css') }}">
<script src="{{ url_for('blog.static', filename='js/blog.js') }}"></script>
Error Handlers
Blueprint Error Handlers
# app/auth/routes.py
@auth_bp.errorhandler(404)
def auth_not_found(error):
return render_template('auth/404.html'), 404
@auth_bp.errorhandler(403)
def auth_forbidden(error):
return render_template('auth/403.html'), 403
# Application-wide error handlers
@auth_bp.app_errorhandler(500)
def internal_error(error):
return render_template('500.html'), 500
Before/After Request Handlers
Blueprint Request Handlers
@auth_bp.before_request
def before_auth_request():
"""Runs before each request to auth blueprint"""
# Check if maintenance mode
if current_app.config.get('MAINTENANCE_MODE'):
return render_template('maintenance.html'), 503
@auth_bp.after_request
def after_auth_request(response):
"""Runs after each request to auth blueprint"""
response.headers['X-Auth-Blueprint'] = '1'
return response
@auth_bp.before_app_request
def before_all_requests():
"""Runs before every request to the application"""
g.request_start_time = time.time()
@auth_bp.after_app_request
def after_all_requests(response):
"""Runs after every request to the application"""
duration = time.time() - g.request_start_time
response.headers['X-Response-Time'] = str(duration)
return response
Context Processors
Blueprint Context Processors
@auth_bp.context_processor
def inject_auth_vars():
"""Make variables available to all auth templates"""
return {
'current_year': datetime.now().year,
'app_name': current_app.config.get('APP_NAME', 'My App')
}
# Now available in auth templates
<!-- auth/login.html -->
<footer>© {{ current_year }} {{ app_name }}</footer>
API Blueprint Example
RESTful API Blueprint
# app/api/__init__.py
from flask import Blueprint
api_bp = Blueprint('api', __name__, url_prefix='/api/v1')
from . import routes
# app/api/routes.py
from flask import jsonify, request
from app.models import User, Post
from app import db
@api_bp.route('/users', methods=['GET'])
def get_users():
page = request.args.get('page', 1, type=int)
users = User.query.paginate(
page=page,
per_page=20,
error_out=False
)
return jsonify({
'users': [user.to_dict() for user in users.items],
'total': users.total,
'pages': users.pages,
'current_page': page
})
@api_bp.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data or 'username' not in data:
return jsonify({'error': 'Missing username'}), 400
user = User(
username=data['username'],
email=data.get('email')
)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
@api_bp.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
@api_bp.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
if 'username' in data:
user.username = data['username']
if 'email' in data:
user.email = data['email']
db.session.commit()
return jsonify(user.to_dict())
@api_bp.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return '', 204
Nested Blueprints
Parent-Child Blueprint Structure
# app/admin/__init__.py
from flask import Blueprint
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
# app/admin/users/__init__.py
from flask import Blueprint
admin_users_bp = Blueprint(
'admin_users',
__name__,
url_prefix='/users'
)
# app/admin/users/routes.py
@admin_users_bp.route('/')
def list_users():
return render_template('admin/users/list.html')
@admin_users_bp.route('/<int:user_id>')
def view_user(user_id):
return render_template('admin/users/view.html')
# Register nested blueprints
# app/admin/__init__.py
from .users import admin_users_bp
admin_bp.register_blueprint(admin_users_bp)
# app/__init__.py
app.register_blueprint(admin_bp)
# URLs will be: /admin/users/ and /admin/users/<id>
Blueprint with Database Models
Model Organization
# app/blog/models.py
from app import db
from datetime import datetime
class Post(db.Model):
__tablename__ = 'blog_posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'created_at': self.created_at.isoformat(),
'author_id': self.author_id
}
# app/blog/routes.py
from .models import Post
from app import db
@blog_bp.route('/posts')
def list_posts():
posts = Post.query.order_by(Post.created_at.desc()).all()
return render_template('blog/posts.html', posts=posts)
Template Organization
Blueprint Template Folders
# Blueprint with custom template folder
blog_bp = Blueprint(
'blog',
__name__,
template_folder='templates',
static_folder='static'
)
# Template hierarchy
# app/blog/templates/
# └── blog/
# ├── base.html
# ├── posts.html
# └── post.html
# In routes
@blog_bp.route('/posts')
def list_posts():
return render_template('blog/posts.html') # Looks in blog/templates/
Template Inheritance with Blueprints
<!-- app/templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My App{% endblock %}</title>
{% block head %}{% endblock %}
</head>
<body>
<nav>
{% block nav %}
<ul>
<li><a href="{{ url_for('main.index') }}">Home</a></li>
<li><a href="{{ url_for('blog.list_posts') }}">Blog</a></li>
<li><a href="{{ url_for('auth.login') }}">Login</a></li>
</ul>
{% endblock %}
</nav>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
<!-- app/blog/templates/blog/posts.html -->
{% extends "base.html" %} {% block title %}Blog Posts{% endblock %} {% block
content %}
<h1>Blog Posts</h1>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.content[:200] }}...</p>
<a href="{{ url_for('blog.view_post', post_id=post.id) }}">Read More</a>
</article>
{% endfor %} {% endblock %}
Blueprint Testing
Testing Blueprint Routes
# tests/test_auth.py
import pytest
from app import create_app, db
from app.models import User
@pytest.fixture
def app():
app = create_app('testing')
with app.app_context():
db.create_all()
yield app
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
def test_auth_login_page(client):
response = client.get('/auth/login')
assert response.status_code == 200
assert b'Login' in response.data
def test_auth_login_post(client):
# Create test user
user = User(username='testuser', email='test@example.com')
user.set_password('password')
db.session.add(user)
db.session.commit()
# Test login
response = client.post('/auth/login', data={
'username': 'testuser',
'password': 'password'
})
assert response.status_code == 302 # Redirect after login
def test_api_users(client):
response = client.get('/api/v1/users')
assert response.status_code == 200
data = response.get_json()
assert 'users' in data
Advanced Blueprint Features
Blueprint Configuration
class BlueprintConfig:
"""Configuration class for blueprints"""
ITEMS_PER_PAGE = 20
CACHE_TIMEOUT = 300
# app/blog/__init__.py
def create_blog_blueprint(config=None):
blog_bp = Blueprint('blog', __name__)
if config:
blog_bp.config = config
else:
blog_bp.config = BlueprintConfig()
from . import routes
return blog_bp
# Use configuration in routes
@blog_bp.route('/posts')
def list_posts():
per_page = blog_bp.config.ITEMS_PER_PAGE
posts = Post.query.paginate(per_page=per_page)
return render_template('blog/posts.html', posts=posts)
Blueprint Decorators
# app/auth/decorators.py
from functools import wraps
from flask import abort
from flask_login import current_user
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated or not current_user.is_admin:
abort(403)
return f(*args, **kwargs)
return decorated_function
# app/admin/routes.py
from app.auth.decorators import admin_required
@admin_bp.route('/dashboard')
@admin_required
def dashboard():
return render_template('admin/dashboard.html')
Blueprint Utilities
# app/blog/utils.py
from flask import current_app
import os
def allowed_file(filename):
allowed_extensions = current_app.config.get('ALLOWED_EXTENSIONS', set())
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in allowed_extensions
def save_picture(form_picture, folder):
filename = secure_filename(form_picture.filename)
picture_path = os.path.join(current_app.root_path, 'static', folder, filename)
form_picture.save(picture_path)
return filename
# Use in routes
from .utils import allowed_file, save_picture
@blog_bp.route('/upload', methods=['POST'])
def upload_image():
if 'file' in request.files:
file = request.files['file']
if file and allowed_file(file.filename):
filename = save_picture(file, 'blog/images')
return jsonify({'filename': filename})
return jsonify({'error': 'Invalid file'}), 400
Best Practices
Blueprint Organization Guidelines
# 1. Keep blueprints focused and cohesive
# ✓ Good: auth_bp for authentication
# ✗ Bad: misc_bp for miscellaneous routes
# 2. Use meaningful URL prefixes
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
api_bp = Blueprint('api', __name__, url_prefix='/api/v1')
# 3. Import routes at the end of blueprint creation
def create_auth_blueprint():
auth_bp = Blueprint('auth', __name__)
# Import routes after blueprint creation to avoid circular imports
from . import routes
return auth_bp
# 4. Use blueprint-specific templates
blog_bp = Blueprint(
'blog',
__name__,
template_folder='templates' # blog/templates/
)
# 5. Handle errors appropriately
@blog_bp.errorhandler(404)
def blog_not_found(error):
return render_template('blog/404.html'), 404
# 6. Use context processors for common template variables
@blog_bp.context_processor
def inject_blog_vars():
return {
'recent_posts': Post.query.limit(5).all()
}
# 7. Organize related functionality together
# blog/
# ├── __init__.py
# ├── routes.py # View functions
# ├── models.py # Database models
# ├── forms.py # WTForms
# ├── utils.py # Helper functions
# └── templates/ # Templates