Claude-skill-registry flask-workflow
Flask framework workflow guidelines. Activate when working with Flask projects, flask run, or Flask-specific patterns.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/flask-workflow" ~/.claude/skills/majiayu000-claude-skill-registry-flask-workflow && rm -rf "$T"
skills/data/flask-workflow/SKILL.mdThe key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Flask Workflow
Tool Grid
| Task | Tool | Command |
|---|---|---|
| Run dev | Flask CLI | |
| Test | pytest | |
| Lint | Ruff | |
| Format | Ruff | |
| Migrate | Flask-Migrate | |
| Shell | Flask CLI | |
Application Factory Pattern
All Flask applications MUST use the application factory pattern.
# app/__init__.py from flask import Flask def create_app(config_name: str = "default") -> Flask: """Application factory function.""" app = Flask(__name__) # Load configuration app.config.from_object(config[config_name]) # Initialize extensions db.init_app(app) migrate.init_app(app, db) login_manager.init_app(app) # Register blueprints from app.main import main_bp from app.auth import auth_bp from app.api import api_bp app.register_blueprint(main_bp) app.register_blueprint(auth_bp, url_prefix="/auth") app.register_blueprint(api_bp, url_prefix="/api/v1") # Register error handlers register_error_handlers(app) return app
Blueprint Organization
Routes MUST be organized using Blueprints. NEVER define routes directly on the app object.
Directory Structure
app/ ├── __init__.py # create_app() factory ├── extensions.py # Extension instances ├── models/ # SQLAlchemy models │ ├── __init__.py │ └── user.py ├── main/ # Main blueprint │ ├── __init__.py # Blueprint definition │ ├── routes.py # Route handlers │ └── forms.py # WTForms (if used) ├── auth/ # Auth blueprint │ ├── __init__.py │ ├── routes.py │ └── forms.py └── api/ # API blueprint ├── __init__.py ├── routes.py └── schemas.py # Marshmallow/Pydantic schemas
Blueprint Definition
# app/main/__init__.py from flask import Blueprint main_bp = Blueprint("main", __name__) from app.main import routes # noqa: E402, F401
Configuration
Configuration MUST be loaded from environment variables for secrets. Configuration classes SHOULD be used for different environments.
# config.py import os from pathlib import Path basedir = Path(__file__).parent class Config: """Base configuration.""" SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-key-change-in-prod" SQLALCHEMY_TRACK_MODIFICATIONS = False @staticmethod def init_app(app): """Initialize application-specific config.""" pass class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get("DEV_DATABASE_URL") or \ f"sqlite:///{basedir / 'dev.db'}" class TestingConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" WTF_CSRF_ENABLED = False class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") @staticmethod def init_app(app): # Production-specific initialization pass config = { "development": DevelopmentConfig, "testing": TestingConfig, "production": ProductionConfig, "default": DevelopmentConfig, }
Extensions Pattern
Extensions MUST be instantiated in a separate module without app binding, then initialized in the factory.
# app/extensions.py from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager db = SQLAlchemy() migrate = Migrate() login_manager = LoginManager() login_manager.login_view = "auth.login"
Flask-SQLAlchemy Patterns
Model Definition
# app/models/user.py from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash from flask_login import UserMixin from app.extensions import db, login_manager class User(UserMixin, db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(256), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) def set_password(self, password: str) -> None: self.password_hash = generate_password_hash(password) def check_password(self, password: str) -> bool: return check_password_hash(self.password_hash, password) @login_manager.user_loader def load_user(user_id: str) -> User | None: return db.session.get(User, int(user_id))
Query Patterns
# RECOMMENDED: Use db.session for queries user = db.session.get(User, user_id) # By primary key users = db.session.scalars(db.select(User).filter_by(active=True)).all() # SHOULD avoid deprecated Model.query # user = User.query.get(user_id) # Deprecated
Flask-Migrate
Database migrations MUST use Flask-Migrate. NEVER modify the database schema manually.
# Initialize migrations (once) flask db init # Create migration after model changes flask db migrate -m "Add user table" # Apply migrations flask db upgrade # Rollback flask db downgrade
Flask-Login Authentication
# app/auth/routes.py from flask import render_template, redirect, url_for, flash, request from flask_login import login_user, logout_user, login_required, current_user from app.auth import auth_bp from app.models.user import User @auth_bp.route("/login", methods=["GET", "POST"]) def login(): if current_user.is_authenticated: return redirect(url_for("main.index")) if request.method == "POST": user = db.session.scalar( db.select(User).filter_by(email=request.form["email"]) ) if user and user.check_password(request.form["password"]): login_user(user, remember=request.form.get("remember")) next_page = request.args.get("next") return redirect(next_page or url_for("main.index")) flash("Invalid email or password") return render_template("auth/login.html") @auth_bp.route("/logout") @login_required def logout(): logout_user() return redirect(url_for("main.index"))
Request and Application Context
Application Context
Use
with app.app_context(): when accessing app-bound resources outside request handling.
# CLI commands, background tasks, etc. with app.app_context(): db.create_all() user = db.session.get(User, 1)
Request Context
Request-specific data (g, request, session) MUST only be accessed within request context.
from flask import g, request @app.before_request def before_request(): g.user = current_user g.locale = request.accept_languages.best_match(["en", "es"])
Error Handlers
Error handlers SHOULD be registered in the application factory.
# app/errors.py from flask import render_template, jsonify, request def register_error_handlers(app): @app.errorhandler(404) def not_found_error(error): if request.path.startswith("/api/"): return jsonify({"error": "Not found"}), 404 return render_template("errors/404.html"), 404 @app.errorhandler(500) def internal_error(error): db.session.rollback() if request.path.startswith("/api/"): return jsonify({"error": "Internal server error"}), 500 return render_template("errors/500.html"), 500
Testing with Test Client
Tests MUST use pytest fixtures. Test configuration MUST use TestingConfig.
# tests/conftest.py import pytest from app import create_app from app.extensions import 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() @pytest.fixture def runner(app): return app.test_cli_runner()
# tests/test_auth.py def test_login_page(client): response = client.get("/auth/login") assert response.status_code == 200 assert b"Login" in response.data def test_login_success(client, user): response = client.post("/auth/login", data={ "email": "test@example.com", "password": "password123" }, follow_redirects=True) assert response.status_code == 200
API Routes
API blueprints SHOULD return JSON responses and use proper HTTP status codes.
# app/api/routes.py from flask import jsonify, request from app.api import api_bp from app.models.user import User @api_bp.route("/users/<int:user_id>") def get_user(user_id: int): user = db.session.get(User, user_id) if not user: return jsonify({"error": "User not found"}), 404 return jsonify({ "id": user.id, "email": user.email, "created_at": user.created_at.isoformat() }) @api_bp.route("/users", methods=["POST"]) def create_user(): data = request.get_json() if not data or not data.get("email"): return jsonify({"error": "Email required"}), 400 user = User(email=data["email"]) user.set_password(data["password"]) db.session.add(user) db.session.commit() return jsonify({"id": user.id}), 201
CLI Commands
Custom CLI commands SHOULD be defined using
@app.cli.command() or Click groups.
# app/cli.py import click from app.extensions import db def register_cli(app): @app.cli.command() def initdb(): """Initialize the database.""" db.create_all() click.echo("Database initialized.") @app.cli.command() @click.argument("email") @click.password_option() def create_admin(email, password): """Create an admin user.""" from app.models.user import User user = User(email=email, is_admin=True) user.set_password(password) db.session.add(user) db.session.commit() click.echo(f"Admin {email} created.")
Security Checklist
- SECRET_KEY MUST be set from environment in production
- CSRF protection MUST be enabled for forms
- Passwords MUST be hashed with werkzeug.security
- SQL queries MUST use parameterized statements (SQLAlchemy handles this)
- User input MUST be validated before use
- Debug mode MUST NOT be enabled in production