$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Flask Projects

Flask is micro by design — AI scatters code without structure. Rules for blueprints, application factory, Flask-SQLAlchemy, WTForms, and proper project organization.

7 min read·February 13, 2025

Flask is micro — AI makes it monolithic with everything in one file

Application factory, blueprints, Flask-SQLAlchemy, WTForms, and config classes

Why Flask Needs Structure Rules

Flask is deliberately minimal — a micro-framework that gives you routing, a request/response cycle, and Jinja2 templates. Everything else is your choice. AI assistants exploit this freedom by putting everything in a single app.py file: routes, models, configuration, templates, database connections, and business logic. The result is a 2,000-line monolith that's impossible to test, maintain, or scale.

The most common AI failures: no application factory (global Flask app prevents testing and multiple instances), no blueprints (all routes in one file), inline SQLAlchemy queries in route functions (no model/service separation), manual form validation instead of WTForms, and hardcoded configuration instead of config classes.

Flask rules must provide the structure that Flask intentionally doesn't. Without them, AI generates Flask apps that work for tutorials but break in production.

Rule 1: Application Factory Pattern

The rule: 'Use the application factory pattern: def create_app(config_name="default"): app = Flask(__name__); app.config.from_object(config[config_name]); register_extensions(app); register_blueprints(app); return app. Never create a global Flask app (app = Flask(__name__) at module level) — it prevents testing with different configs, running multiple instances, and proper initialization ordering.'

For extensions: 'Initialize extensions outside the factory, bind to the app inside: db = SQLAlchemy() at module level, db.init_app(app) inside create_app. This is Flask's standard extension pattern — create globally, initialize with the app. Extensions: Flask-SQLAlchemy, Flask-Migrate, Flask-Login, Flask-WTF, Flask-CORS.'

The application factory is Flask's most important architectural pattern. AI generates global app = Flask(__name__) because every tutorial starts that way. But production Flask apps must use the factory for testing, configuration flexibility, and proper initialization.

⚠️ Never Global app

app = Flask(__name__) at module level prevents testing with different configs and running multiple instances. The application factory (create_app()) is Flask's production pattern — every tutorial's global app is a tutorial shortcut.

Rule 2: Blueprints for Modularity

The rule: 'Organize routes into blueprints: auth = Blueprint("auth", __name__). Register in the factory: app.register_blueprint(auth_bp, url_prefix="/auth"). Each blueprint has its own: routes (views.py), templates (templates/auth/), static files (static/auth/), and forms (forms.py). Never put all routes in one file — blueprints are Flask's module system.'

For project structure: 'app/ as the application package. app/__init__.py contains create_app. app/models/ for SQLAlchemy models. app/auth/ for auth blueprint. app/main/ for main blueprint. app/api/ for API blueprint. config.py for configuration classes. manage.py or wsgi.py as entry point.'

AI generates all routes in a single file because Flask's minimal examples do that. Blueprints scale Flask to production — each feature is isolated, testable, and independently maintainable.

  • Blueprint per feature: auth, main, api, admin — separate directories
  • Each blueprint: views.py, forms.py, templates/<blueprint>/
  • Register in factory: app.register_blueprint(bp, url_prefix='/...')
  • Blueprint-specific error handlers: @bp.errorhandler(404)
  • Blueprint-specific before_request: @bp.before_request
💡 One Blueprint Per Feature

Blueprints are Flask's module system. auth/, main/, api/ — each with views, forms, templates. AI puts 50 routes in one file. Blueprints keep features isolated, testable, and independently maintainable.

Rule 3: Flask-SQLAlchemy and Database Patterns

The rule: 'Use Flask-SQLAlchemy for all database access. Define models in app/models/: class User(db.Model): id = db.Column(db.Integer, primary_key=True). Use Flask-Migrate for schema migrations: flask db migrate, flask db upgrade. Never use raw SQL — Flask-SQLAlchemy's ORM handles parameterization, relationships, and migration tracking.'

For queries: 'Use model query methods: User.query.filter_by(email=email).first(). Use relationship lazy loading configuration: db.relationship("Order", lazy="select") for lazy, lazy="joined" for eager. Use pagination: User.query.paginate(page=page, per_page=20). For complex queries, use db.session.query() with joins and subqueries.'

For session management: 'Flask-SQLAlchemy manages sessions per request — db.session is request-scoped. Commit explicitly: db.session.commit(). Roll back on error: db.session.rollback(). Use try/except around commits. The session is automatically removed at the end of each request.'

Rule 4: WTForms for All Form Handling

The rule: 'Use Flask-WTF (WTForms) for all HTML form handling. Define form classes: class LoginForm(FlaskForm): email = StringField("Email", validators=[DataRequired(), Email()]); password = PasswordField("Password", validators=[DataRequired()]). Validate in views: if form.validate_on_submit(). Never parse request.form manually — WTForms provides CSRF protection, type coercion, and validation for free.'

For API validation: 'For JSON APIs (no HTML forms), use marshmallow or Pydantic instead of WTForms. WTForms is designed for HTML form rendering — marshmallow handles JSON serialization/deserialization with schema validation. Use Flask-Marshmallow for integration: ma = Marshmallow(app).'

AI generates request.form["email"] without any validation — trusting user input directly. WTForms validates, coerces types, provides CSRF protection, and renders form HTML. One FlaskForm class replaces 20 lines of manual validation.

ℹ️ WTForms = Free CSRF

WTForms provides CSRF protection, type coercion, and validation from one form class. AI parses request.form manually — no CSRF token, no validation, no type safety. FlaskForm replaces 20 lines of manual code.

Rule 5: Configuration Management

The rule: 'Define configuration as classes in config.py: class Config: SECRET_KEY = os.environ.get("SECRET_KEY"); SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL"). Extend for environments: class DevelopmentConfig(Config): DEBUG = True. class ProductionConfig(Config): DEBUG = False. Load in the factory: app.config.from_object(config[config_name]). Never hardcode secrets — always from environment variables.'

For secrets: 'Use python-dotenv for local development: load_dotenv() at the top of config.py reads .env files. In production, set environment variables through your deployment platform. Never commit .env files — .env is in .gitignore, .env.example is committed with placeholder values.'

For testing: 'Create a TestingConfig: class TestingConfig(Config): TESTING = True; SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"; WTF_CSRF_ENABLED = False. The factory creates a test app: app = create_app("testing"). This isolates tests from development and production configurations.'

Complete Flask Rules Template

Consolidated rules for Flask projects.

  • Application factory: create_app() — never global app = Flask(__name__)
  • Blueprints per feature: auth, main, api — separate directories with views, forms, templates
  • Flask-SQLAlchemy for DB — Flask-Migrate for migrations — never raw SQL
  • WTForms for HTML forms — marshmallow for JSON APIs — never raw request.form
  • Config classes: Config, DevelopmentConfig, ProductionConfig, TestingConfig
  • python-dotenv for local .env — env vars for production — never hardcoded secrets
  • Flask-Login for auth — Flask-CORS for API — Flask-Limiter for rate limiting
  • pytest + Flask test client — factory creates test app with TestingConfig