Flask Cursor Rules: Python Microframework Guide

Cursor rules for Flask covering Blueprints, app factory, SQLAlchemy, route decorators, Marshmallow schemas, error handling, and pytest testing for clean Python APIs.

June 8, 2025by PromptGenius Team
flaskpythoncursor-rulesbackendwsgisqlalchemy
Flask Cursor Rules: Python Microframework Guide

Overview

Flask is the unopinionated Python microframework that gives you full control over your backend architecture. These cursor rules enforce Blueprint-based modularity, the app factory pattern, SQLAlchemy ORM conventions, Marshmallow validation, structured error handling, and pytest testing to help AI assistants build clean, testable Flask applications.

Note:

Enforces app factory creation, Blueprint routing, SQLAlchemy models and session management, Marshmallow/Pydantic validation, custom error handlers, and pytest fixture patterns.

Rules Configuration

---
description: Enforces idiomatic Flask patterns including Blueprint modularity, app factory, SQLAlchemy ORM, Marshmallow validation, structured error handling, and pytest testing. Provides guidelines for clean, testable Python APIs.
globs: **/*.py
---
# Flask Best Practices

You are an expert in Flask, Python backend development, and REST API design.
You understand WSGI applications, SQLAlchemy ORM, and production deployment patterns.

### Project Structure
- /app — application package with __init__.py containing create_app()
- /app/blueprints — Blueprint-based route modules organized by domain
- /app/models — SQLAlchemy model classes with relationships and methods
- /app/schemas — Marshmallow or Pydantic schemas for validation/serialization
- /app/services — business logic layer, independent of HTTP concerns
- /app/extensions.py — centralized Flask extension instances (db, migrate, ma)
- /config.py — configuration classes (DevelopmentConfig, ProductionConfig, etc.)
- /tests — pytest test files mirroring app structure

### Application Factory
- Use create_app() pattern in app/__init__.py to enable multiple app instances
- Accept config_name parameter to select environment configuration
- Register Blueprints and extensions inside create_app()
- Never instantiate the app at module level (avoid circular imports)

### Blueprints & Routing
- Register Blueprints under URL prefixes: app.register_blueprint(auth_bp, url_prefix='/api/v1/auth')
- Separate API routes under their own Blueprint modules
- Use method_decorators for auth: @auth_bp.before_request for global auth checks
- Document routes with descriptive docstrings
- Return JSON with jsonify(); set appropriate status codes

### SQLAlchemy & Database
- Define models inheriting from db.Model with __tablename__
- Use db.Column with explicit types, nullable, and default values
- Manage database state in migrations with Flask-Migrate/Alembic
- Use db.session in a context manager or commit/rollback explicitly
- Query with db.session: db.session.execute(db.select(Post).filter_by(category='tech')).scalars().all()
- Close sessions properly with teardown_appcontext or dispose on app shutdown

### Validation & Serialization
- Use Marshmallow schemas with fields, validate, and post_load decorators
- Return field-level error messages on validation failure
- Strip unknown fields with Meta.unknown = RAISE or EXCLUDE
- Use Pydantic models as an alternative for type-safe validation
- Validate at the route boundary before passing data to services

### Error Handling
- Register custom error handlers with @app.errorhandler(status_code)
- Return structured JSON: { error: { message, type, details? } }
- Create custom exception classes inheriting from Exception
- Use try/except in service layer; let routes catch and format errors
- Log exceptions with app.logger; never expose stack traces in production

### Security
- Use Flask-CORS with origin whitelists, not wildcards
- Hash passwords with werkzeug.security.generate_password_hash
- Use flask-talisman or set Content-Security-Policy headers
- Validate and sanitize all user input at the route boundary
- Store secrets in environment variables; never hardcode

### Testing
- Use pytest with pytest-flask for app fixtures
- Create a test app fixture with TestingConfig (in-memory SQLite)
- Use test_client() for integration-style request tests
- Factory functions (builders) for test model instances
- Mock external services with responses or pytest-mock

Installation

Create flask.mdc in your project's .cursor/rules/ directory and paste the configuration above. Cursor and Windsurf both read .cursor/rules/ — Copilot users place it in .github/copilot-instructions.md instead.

Examples

# app/blueprints/posts.py — Blueprint with validation and service layer
from flask import Blueprint, request, jsonify
from marshmallow import Schema, fields, validates, ValidationError
from app.services.post_service import PostService
from app.extensions import db

posts_bp = Blueprint('posts', __name__, url_prefix='/api/v1/posts')


class PostSchema(Schema):
    title = fields.String(required=True, validate=lambda s: 1 <= len(s) <= 200)
    body = fields.String(required=True)
    published = fields.Boolean(load_default=False)

    @validates('title')
    def validate_title(self, value):
        if not value.strip():
            raise ValidationError('Title cannot be blank')


@posts_bp.route('/', methods=['GET'])
def list_posts():
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 20, type=int)

    posts, total = PostService.get_published(page, per_page)
    return jsonify({
        'data': [p.to_dict() for p in posts],
        'meta': {'page': page, 'total': total, 'per_page': per_page}
    }), 200


@posts_bp.route('/', methods=['POST'])
def create_post():
    schema = PostSchema()
    try:
        data = schema.load(request.json)
    except ValidationError as err:
        return jsonify({'errors': err.messages}), 422

    post = PostService.create(data['title'], data['body'], data['published'])

    db.session.commit()
    return jsonify({'data': post.to_dict()}), 201
# app/__init__.py — App factory with extensions
from flask import Flask
from flask_cors import CORS
from flask_migrate import Migrate
from app.extensions import db, ma
from config import config_by_name


migrate = Migrate()


def create_app(config_name='development'):
    app = Flask(__name__)
    app.config.from_object(config_by_name[config_name])

    db.init_app(app)
    ma.init_app(app)
    migrate.init_app(app, db)
    CORS(app, origins=app.config['ALLOWED_ORIGINS'])

    from app.blueprints.posts import posts_bp
    from app.blueprints.auth import auth_bp

    app.register_blueprint(posts_bp)
    app.register_blueprint(auth_bp)

    @app.errorhandler(404)
    def not_found(error):
        return jsonify({'error': {'message': 'Resource not found', 'type': 'NOT_FOUND'}}), 404

    @app.errorhandler(500)
    def server_error(error):
        db.session.rollback()
        return jsonify({'error': {'message': 'Internal server error', 'type': 'SERVER_ERROR'}}), 500

    return app
# tests/conftest.py — Pytest fixtures for Flask
import pytest
from app import create_app
from app.extensions import db as _db


class TestingConfig:
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    ALLOWED_ORIGINS = ['*']


@pytest.fixture
def app():
    app = create_app('testing')
    app.config.from_object(TestingConfig)

    with app.app_context():
        _db.create_all()
        yield app
        _db.session.remove()
        _db.drop_all()


@pytest.fixture
def client(app):
    return app.test_client()


@pytest.fixture
def db(app):
    with app.app_context():
        yield _db