plugins/backend/skills/flask/SKILL.md
Expert skill for Flask web framework development. Covers application factory, blueprints, routing, request/response handling, Jinja2 templates, application and request context (g, current_app), configuration, extensions ecosystem (SQLAlchemy, Login, WTF, CORS, Migrate), error handling, testing, and deployment. WHEN: "Flask", "flask", "Werkzeug", "Jinja2", "Blueprint", "Flask blueprint", "flask factory", "create_app", "Flask-SQLAlchemy", "Flask-Login", "Flask-WTF", "Flask-Migrate", "Flask-CORS", "Flask-JWT-Extended", "Flask-Caching", "Flask-Smorest", "flask test_client", "flask extension", "flask context", "current_app", "g object", "flask deployment", "flask gunicorn". Do NOT use for general Python scripting, automation, or CLI-tool questions unrelated to the Flask framework — that's the `cli-scripting` plugin's `python` skill.
npx skillsauth add chrishuffman5/domain-expert flaskInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill covers Flask web framework development. Flask is a lightweight WSGI micro-framework built on Werkzeug (routing, request/response) and Jinja2 (templating). It provides a minimal core -- routing, request handling, and templates -- with an extensive extensions ecosystem for everything else. Flask 3.1 runs on Python 3.10+ and supports async views via asgiref. There is no version-specific reference material (single stable 3.x line).
Classify the request:
references/architecture.md for application factory, context stacks, Werkzeug foundation, routing internals, Jinja2, signals, extension loading, WSGI interface, async supportreferences/best-practices.md for project structure, extensions integration, testing, security, deployment, performance, Flask vs FastAPI decision guideGather context -- Check Python version (3.10+ for Flask 3.x), Flask version, which extensions are in use, sync vs async views, deployment target (Gunicorn, Docker, serverless).
Load context -- Read the relevant reference file before answering.
Analyze -- Apply Flask-specific reasoning. Consider application factory pattern, context stack behavior, extension initialization order, and the sync-first nature of Flask.
Recommend -- Provide concrete Python code examples with explanations. Always qualify trade-offs.
Verify -- Suggest validation steps: run tests with pytest, check route registration with flask routes, verify extension initialization order.
The cornerstone of production Flask applications. Instead of a global app, a create_app() function constructs and configures the application.
# myapp/__init__.py
from flask import Flask
from .extensions import db, migrate, login_manager, cache
from .config import config_by_name
def create_app(config_name: str = "production") -> Flask:
app = Flask(__name__, instance_relative_config=True)
# Load configuration
app.config.from_object(config_by_name[config_name])
app.config.from_pyfile("config.py", silent=True)
# Initialize extensions (order matters -- db before migrate)
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
cache.init_app(app)
# Register blueprints
from .api.v1 import api_v1_bp
from .auth import auth_bp
app.register_blueprint(auth_bp, url_prefix="/auth")
app.register_blueprint(api_v1_bp, url_prefix="/api/v1")
register_error_handlers(app)
return app
# myapp/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_caching import Cache
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
cache = Cache()
Key benefits: Multiple instances for testing, deferred extension initialization, clean import graphs (no circular imports).
Flask's modular unit. Blueprints carry routes, templates, static files, error handlers, and before/after request hooks.
from flask import Blueprint
auth_bp = Blueprint("auth", __name__, template_folder="templates", url_prefix="/auth")
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
return render_template("auth/login.html")
# Nesting (Flask 2.0+)
parent_bp = Blueprint("parent", __name__, url_prefix="/parent")
child_bp = Blueprint("child", __name__)
parent_bp.register_blueprint(child_bp, url_prefix="/child")
# Blueprint-scoped hooks
@auth_bp.before_request
def require_json():
if request.method in ("POST", "PUT", "PATCH") and not request.is_json:
abort(415)
# Variable rules with converters: string, int, float, path, uuid
@app.route("/users/<int:user_id>")
def get_user(user_id: int):
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
# Class-based views
from flask.views import MethodView
class UserResource(MethodView):
def get(self, user_id: int | None = None):
if user_id is None:
return jsonify([u.to_dict() for u in User.query.all()])
return jsonify(User.query.get_or_404(user_id).to_dict())
def post(self):
data = request.get_json(force=True)
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
user_view = UserResource.as_view("user_resource")
app.add_url_rule("/users/", view_func=user_view, methods=["GET", "POST"])
app.add_url_rule("/users/<int:user_id>", view_func=user_view, methods=["GET", "DELETE"])
from flask import request, jsonify, make_response, redirect, url_for, abort
# Incoming data
request.args # query string (ImmutableMultiDict)
request.form # form data
request.files # uploaded files
request.json # parsed JSON body
request.get_json(force=True, silent=True)
request.data # raw body bytes
request.headers # case-insensitive dict
# Response helpers
return jsonify({"status": "ok"}), 200
return redirect(url_for("auth.login"), 302)
abort(404)
# Full control
resp = make_response(jsonify({"error": "not found"}), 404)
resp.headers["X-Custom"] = "value"
resp.set_cookie("session_id", "abc123", httponly=True, samesite="Lax")
return resp
Flask uses two context stacks pushed around each request and CLI command.
| Object | Context | Purpose |
|---|---|---|
| current_app | App context | Proxy to the active Flask application |
| g | App context (per-request) | Request-scoped scratch space |
| request | Request context | Incoming HTTP request data |
| session | Request context | Signed cookie-based session |
from flask import g, current_app, session
@app.before_request
def load_logged_in_user():
user_id = session.get("user_id")
g.user = User.query.get(user_id) if user_id else None
# Manual context (scripts, Celery tasks, tests)
with app.app_context():
db.create_all()
class BaseConfig:
SECRET_KEY: str = os.environ["SECRET_KEY"]
SQLALCHEMY_TRACK_MODIFICATIONS: bool = False
class DevelopmentConfig(BaseConfig):
DEBUG: bool = True
SQLALCHEMY_DATABASE_URI: str = "sqlite:///dev.db"
class TestingConfig(BaseConfig):
TESTING: bool = True
SQLALCHEMY_DATABASE_URI: str = "sqlite:///:memory:"
WTF_CSRF_ENABLED: bool = False
class ProductionConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI: str = os.environ["DATABASE_URL"]
SESSION_COOKIE_SECURE: bool = True
SESSION_COOKIE_HTTPONLY: bool = True
config_by_name = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
}
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head><title>{% block title %}My App{% endblock %}</title></head>
<body>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
<!-- templates/users/list.html -->
{% extends "base.html" %}
{% block content %}
<ul>
{% for user in users %}
<li>{{ user.name | title }}</li>
{% else %}
<li>No users found.</li>
{% endfor %}
</ul>
{% endblock %}
| Extension | Purpose |
|---|---|
| Flask-SQLAlchemy | SQLAlchemy integration with session management |
| Flask-Migrate | Alembic migrations via flask db CLI |
| Flask-Login | Session-based user authentication |
| Flask-WTF | WTForms integration with CSRF protection |
| Flask-CORS | Cross-Origin Resource Sharing headers |
| Flask-JWT-Extended | JWT authentication for APIs |
| Flask-Caching | Response and function caching (Redis, Memcached) |
| Flask-Smorest | OpenAPI-first REST APIs with Marshmallow |
| Flask-Mail | Email sending |
| Flask-Talisman | Security headers (CSP, HSTS) |
from werkzeug.exceptions import HTTPException
@app.errorhandler(404)
def not_found(e):
if request.accept_mimetypes.accept_json:
return jsonify(error=str(e), code=404), 404
return render_template("errors/404.html"), 404
@app.errorhandler(HTTPException)
def handle_http_exception(e: HTTPException):
return jsonify(code=e.code, name=e.name, description=e.description), e.code
# Custom exceptions
class ResourceNotFound(Exception):
def __init__(self, resource: str, id: int):
self.resource = resource
self.id = id
@app.errorhandler(ResourceNotFound)
def handle_not_found(e):
return jsonify(error=str(e), resource=e.resource, id=e.id), 404
import pytest
@pytest.fixture(scope="session")
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()
@pytest.fixture
def cli_runner(app):
return app.test_cli_runner()
def test_create_user(client, db):
resp = client.post("/api/v1/users/", json={"email": "[email protected]", "password": "strong"})
assert resp.status_code == 201
assert resp.json["email"] == "[email protected]"
def test_protected_route_requires_auth(client):
resp = client.get("/api/v1/protected")
assert resp.status_code == 401
| Method | Command / Config |
|---|---|
| Development | flask run --debug |
| Gunicorn (Linux) | gunicorn "myapp:create_app('production')" -w 4 -b 0.0.0.0:8000 |
| Waitress (Windows) | waitress-serve --port=8000 "myapp:create_app('production')" |
| Docker | Gunicorn + Nginx reverse proxy, non-root user |
Never use the Flask development server in production. Always use a WSGI server (Gunicorn, Waitress).
| Pattern | When to Use |
|---|---|
| Application factory | Always in production. Enables testing, multiple configs |
| Blueprints | Any app with more than one logical module |
| before_request hooks | Auth checks, request logging, loading user into g |
| Extension init_app() | Deferred initialization in factory pattern |
| url_for() | Always. Never hardcode URLs |
| MethodView | RESTful resource endpoints |
| Instance folder | Machine-specific secrets, git-ignored config |
Flask 2.0+ supports async def views via asgiref, but Flask remains WSGI at the server level. Async views run in a thread pool, not on an event loop.
@app.route("/async-view")
async def async_view():
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/data")
return jsonify(resp.json())
Limitations: One request per thread. No true concurrency. For high-concurrency I/O, use FastAPI or Celery for background tasks.
app without factory -- Breaks testing, prevents multiple configs.create_app(), not at module top.db.session.commit() without error handling -- Always catch IntegrityError and rollback.url_for() everywhere.Load these for deep knowledge on specific topics:
references/architecture.md -- Application factory, context stacks (app context, request context, g object), Werkzeug foundation, routing internals, Jinja2 template engine, signal system, extension loading, WSGI interface, async support. Load when: architecture questions, context stack confusion, extension initialization, routing mechanics.references/best-practices.md -- Project structure (functional vs divisional), extensions integration (SQLAlchemy, Migrate, Login, WTF), testing (test_client, pytest fixtures), security (CSRF, sessions, cookies), deployment (Gunicorn, Docker), performance, Flask vs FastAPI decision guide. Load when: "how should I structure", extension setup, testing strategy, security hardening, deployment configuration.tools
kubectl command-line usage and scripting: kubeconfig and context management, output formats (jsonpath, custom-columns, go-template), all major verbs (get, describe, apply, delete, exec, logs, port-forward, rollout, scale, drain), workload resources, config/storage, networking, RBAC, node management, debugging (CrashLoopBackOff, ImagePullBackOff, OOMKilled), and scripting patterns (dry-run, diff, wait, jq, kustomize). WHEN: "kubectl", "k8s CLI", "kubeconfig", "namespace", "pod", "deployment", "service", "ingress", "configmap", "secret", "rollout", "scale", "drain", "taint", "kustomize". Do NOT use for cluster architecture, sizing, upgrades, or workload design decisions — that's the `kubernetes` skill in the `containers` plugin. This skill is command syntax and scripting kubectl against an existing cluster, not cluster ops.
tools
Bash 5.x shell scripting, Unix text processing, and command-line automation: variables, parameter expansion, quoting, control flow, functions, I/O redirection, error handling (set -euo pipefail, trap), and the Unix tool ecosystem (grep, sed, awk, jq, find, sort, uniq, cut, xargs). Covers process management, networking (curl, ssh, rsync, nc), file locking (flock), parallel execution, and production script patterns. WHEN: "Bash", "bash", "shell", "sh", ".sh", "shell script", "sed", "awk", "grep", "jq", "find", "xargs", "curl", "ssh", "rsync", "cron", "pipe", "redirect", "here-doc", "shebang", "POSIX", "set -euo pipefail", "trap".
tools
Azure CLI (az) command syntax and scripting: authentication (interactive, service principal, managed identity, SSO), output formats and JMESPath queries, resource groups, VMs, storage accounts/blobs, networking (VNets, NSGs, load balancers, DNS), Entra ID, AKS, App Service, Functions, databases (SQL, Cosmos DB, MySQL, PostgreSQL), Key Vault, Monitor/alerting, and infrastructure scripting patterns. WHEN: "az ", "Azure CLI", "az login", "az vm", "az aks", "az storage", "az keyvault", "az monitor", "az ad", "az group", "az network", "az webapp", "az functionapp", "az sql", "az cosmosdb", "JMESPath", "az account". Do NOT use for Azure architecture, landing zones, or multi-subscription strategy — that's the `cloud-platforms` plugin. This skill is about command syntax and scripting the CLI, not deciding what to provision.
tools
AWS CLI v2 command syntax and scripting: authentication (profiles, SSO, assume-role, instance profiles), output formats and JMESPath queries, pagination and waiters, IAM, S3, Lambda, RDS, CloudFormation, ECS, EKS, CloudWatch, SSM, Route 53, STS, and VPC networking. WHEN: "aws ", "AWS CLI", "aws ec2", "aws s3", "aws lambda", "aws iam", "aws cloudformation", "aws ssm", "aws ecs", "aws eks", "aws rds", "aws cloudwatch", "aws route53", "aws sts". Do NOT use for AWS architecture, service selection, multi-account strategy, or FinOps — that's the `cloud-platforms` plugin. This skill is about command syntax and scripting the CLI, not deciding what to provision.