Flask
Flask application patterns for routing, blueprints, extensions, and application factories
You are an expert in Flask for building web applications with a lightweight, composable architecture.
## Key Points
- Always use the application factory pattern. It enables testing with different configs and avoids circular imports.
- Organize code into blueprints by domain area. Each blueprint gets its own templates, static files, and routes.
- Use Flask-Migrate (Alembic) for all schema changes instead of `db.create_all()` in production.
- Store secrets in environment variables and load them through `app.config.from_prefixed_env()` or a dedicated config class.
- Use `flask.g` for per-request state (like database connections) and `flask.session` for user session data.
- Register error handlers at the application level with `app_errorhandler` so they apply across all blueprints.
- Working outside the application context. Accessing `db.session`, `current_app`, or `url_for` outside a request or CLI command raises a `RuntimeError`. Wrap such code in `with app.app_context():`.
- Circular imports between `__init__.py` and route modules. Fix this by importing routes at the bottom of `__init__.py` after the blueprint is created.
- Using `db.create_all()` in production instead of migrations. This cannot handle schema changes on existing tables.
- Forgetting to call `db.session.rollback()` in error handlers, leaving the session in a broken state for the next request.
- Not setting `SECRET_KEY` to a strong random value in production, which compromises session cookie security.
## Quick Example
```bash
pip install flask flask-sqlalchemy flask-migrate flask-login flask-wtf
```
```python
# app/api/__init__.py
from flask import Blueprint
bp = Blueprint("api", __name__)
from . import routes # noqa
```skilldb get python-web-skills/FlaskFull skill: 314 linesFlask — Python Web Development
You are an expert in Flask for building web applications with a lightweight, composable architecture.
Core Philosophy
Overview
Flask is a micro-framework for Python that provides routing, request handling, templating with Jinja2, and a robust extension ecosystem. Its minimal core gives developers full control over project structure, making it well suited for small services and large applications alike.
Setup & Configuration
pip install flask flask-sqlalchemy flask-migrate flask-login flask-wtf
Application factory pattern:
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
login_manager.login_view = "auth.login"
from .main import bp as main_bp
app.register_blueprint(main_bp)
from .api import bp as api_bp
app.register_blueprint(api_bp, url_prefix="/api")
return app
# config.py
import os
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY", "change-me")
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
config = {
"default": DevelopmentConfig,
"production": ProductionConfig,
}
Core Patterns
Blueprints
# app/main/__init__.py
from flask import Blueprint
bp = Blueprint("main", __name__, template_folder="templates")
from . import routes # noqa: E402, F401
# app/main/routes.py
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user
from . import bp
from .. import db
from ..models import Article
@bp.route("/")
def index():
page = request.args.get("page", 1, type=int)
articles = (
Article.query
.filter_by(published=True)
.order_by(Article.created_at.desc())
.paginate(page=page, per_page=20)
)
return render_template("index.html", articles=articles)
@bp.route("/article/<slug>")
def article_detail(slug):
article = Article.query.filter_by(slug=slug).first_or_404()
return render_template("article.html", article=article)
@bp.route("/article/new", methods=["GET", "POST"])
@login_required
def create_article():
form = ArticleForm()
if form.validate_on_submit():
article = Article(
title=form.title.data,
body=form.body.data,
author=current_user,
)
db.session.add(article)
db.session.commit()
flash("Article created.", "success")
return redirect(url_for("main.article_detail", slug=article.slug))
return render_template("create_article.html", form=form)
Models with Flask-SQLAlchemy
# app/models.py
from datetime import datetime, timezone
from flask_login import UserMixin
from slugify import slugify
from . import db, login_manager
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
articles = db.relationship("Article", backref="author", lazy="dynamic")
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
slug = db.Column(db.String(200), unique=True, nullable=False)
body = db.Column(db.Text, nullable=False)
published = db.Column(db.Boolean, default=False)
author_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
created_at = db.Column(
db.DateTime, default=lambda: datetime.now(timezone.utc)
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if not self.slug:
self.slug = slugify(self.title)
Error Handlers
@bp.app_errorhandler(404)
def not_found(error):
return render_template("errors/404.html"), 404
@bp.app_errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template("errors/500.html"), 500
JSON API Endpoints
# app/api/__init__.py
from flask import Blueprint
bp = Blueprint("api", __name__)
from . import routes # noqa
# app/api/routes.py
from flask import jsonify, request, abort
from . import bp
from ..models import Article
from .. import db
@bp.route("/articles")
def list_articles():
page = request.args.get("page", 1, type=int)
pagination = Article.query.filter_by(published=True).paginate(
page=page, per_page=20
)
return jsonify({
"items": [a.to_dict() for a in pagination.items],
"total": pagination.total,
"page": page,
"pages": pagination.pages,
})
@bp.route("/articles", methods=["POST"])
def create_article():
data = request.get_json()
if not data or "title" not in data:
abort(400)
article = Article(title=data["title"], body=data.get("body", ""))
db.session.add(article)
db.session.commit()
return jsonify(article.to_dict()), 201
Context Processors and Template Helpers
@bp.app_context_processor
def inject_globals():
return {
"site_name": "My Blog",
"current_year": datetime.now().year,
}
@bp.app_template_filter("timeago")
def timeago_filter(dt):
delta = datetime.now(timezone.utc) - dt
if delta.days > 0:
return f"{delta.days}d ago"
hours = delta.seconds // 3600
return f"{hours}h ago"
Testing
import pytest
from app import create_app, db as _db
@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_index(client):
response = client.get("/")
assert response.status_code == 200
def test_create_article(client):
response = client.post(
"/api/articles",
json={"title": "Test", "body": "Content"},
)
assert response.status_code == 201
Best Practices
- Always use the application factory pattern. It enables testing with different configs and avoids circular imports.
- Organize code into blueprints by domain area. Each blueprint gets its own templates, static files, and routes.
- Use Flask-Migrate (Alembic) for all schema changes instead of
db.create_all()in production. - Store secrets in environment variables and load them through
app.config.from_prefixed_env()or a dedicated config class. - Use
flask.gfor per-request state (like database connections) andflask.sessionfor user session data. - Register error handlers at the application level with
app_errorhandlerso they apply across all blueprints.
Common Pitfalls
- Working outside the application context. Accessing
db.session,current_app, orurl_foroutside a request or CLI command raises aRuntimeError. Wrap such code inwith app.app_context():. - Circular imports between
__init__.pyand route modules. Fix this by importing routes at the bottom of__init__.pyafter the blueprint is created. - Using
db.create_all()in production instead of migrations. This cannot handle schema changes on existing tables. - Forgetting to call
db.session.rollback()in error handlers, leaving the session in a broken state for the next request. - Not setting
SECRET_KEYto a strong random value in production, which compromises session cookie security.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add python-web-skills
Related Skills
Celery
Celery patterns for distributed task queues, scheduling, retries, and worker management
Django Admin
Django admin customization patterns for list views, forms, inlines, actions, and permissions
Django ORM
Django ORM patterns for models, querysets, migrations, and database optimization
Django REST Framework
Django REST Framework patterns for building, serializing, and securing RESTful APIs
Fastapi
FastAPI patterns for async APIs, dependency injection, Pydantic models, and OpenAPI integration
Python Websockets
WebSocket patterns for real-time communication using FastAPI, Django Channels, and the websockets library