Python + Flask Agent Rules
Project Context
You are building a Flask application using the application factory pattern with Blueprint-based modular routing, SQLAlchemy for ORM, Flask-Migrate for schema management, and Marshmallow or Pydantic for request validation.
Code Style & Structure
- Follow PEP 8. Format with `ruff format`. Lint with `ruff check`. Configure both in `pyproject.toml`.
- Add type hints to all function signatures and return types. Use `from __future__ import annotations`.
- Keep route handlers under 15 lines. Delegate business logic to service functions.
- Use `pathlib.Path` for file system operations.
Project Structure
```
project/
app/
__init__.py # create_app() factory
extensions.py # db, migrate, mail, limiter — init_app() pattern
models/ # SQLAlchemy models, one file per aggregate
api/
auth/
routes.py # Blueprint(prefix='/auth')
services.py # Business logic
schemas.py # Marshmallow/Pydantic schemas
users/
<domain>/
errors.py # Error handlers and custom exceptions
middleware.py # @app.before_request hooks
migrations/
tests/
conftest.py
test_auth/
test_users/
config.py # BaseConfig, DevelopmentConfig, ProductionConfig, TestingConfig
```
Application Factory
- Always use the factory pattern: `def create_app(config_name='development') -> Flask`.
- Register blueprints, error handlers, shell context, and CLI commands inside `create_app`.
- Initialize extensions in `extensions.py` and bind them with `ext.init_app(app)` inside the factory.
- Load configuration with `app.config.from_object(config)`. Never hardcode secrets.
- Use `python-dotenv` to load `.env` files. Use `pydantic-settings` for typed, validated config.
Routes & Blueprints
- One Blueprint per domain area with its own URL prefix: `Blueprint('users', prefix='/api/v1/users')`.
- Use `@bp.get`, `@bp.post`, `@bp.put`, `@bp.delete` (Flask 2.2+) for explicit HTTP method declarations.
- Return `jsonify(data)` with explicit `status` argument: `return jsonify(data), 201`.
- Return a consistent envelope: `{ "data": ..., "meta": ... }` for success; `{ "error": { "code", "message", "details" } }` for failure.
- Use `@bp.before_request` for blueprint-scoped pre-request work (auth verification, request logging).
SQLAlchemy & Migrations
- Use Flask-SQLAlchemy 3.x with explicit `db.Mapped` column annotations (SQLAlchemy 2.0 style).
- Use Flask-Migrate (Alembic) for all schema changes. Review generated migration scripts before applying.
- Write explicit `downgrade()` functions in every migration. Blind migrations break rollbacks.
- Use `uuid` primary keys on public-facing models to prevent sequential ID enumeration.
- Add `index=True` on all columns used in filter conditions. Add multi-column indexes via `__table_args__`.
- Prefer `db.session.execute(select(User).where(...))` over legacy `User.query.filter(...)`.
- Use `joinedload` and `subqueryload` to avoid N+1 queries. Profile with `sqlalchemy-utils` query count tracking.
- Wrap multi-step mutations in `try: db.session.commit() except: db.session.rollback(); raise`.
Request Validation
- Validate all incoming data with Marshmallow schemas or Pydantic v2 models.
- Define separate schema classes: `CreateUserSchema`, `UpdateUserSchema`, `UserResponseSchema`.
- Return `422` with field-level error details on validation failure.
- Use `@validates` in Marshmallow or `@field_validator` in Pydantic for cross-field rules.
- Strip unknown fields: `class Meta: unknown = RAISE` (Marshmallow) or `model_config = ConfigDict(extra='forbid')` (Pydantic).
Authentication
- Use Flask-JWT-Extended for stateless API auth with short access tokens (15 minutes) and rotating refresh tokens.
- Hash passwords with `werkzeug.security.generate_password_hash` using the `scrypt` or `pbkdf2:sha256` method.
- Use `@jwt_required()` decorator. Access the current user with `current_user` after injecting a `user_lookup_callback`.
- Store sensitive cookie flags: `JWT_COOKIE_SECURE = True`, `JWT_COOKIE_SAMESITE = 'Strict'`.
Error Handling
- Register error handlers for `400`, `401`, `403`, `404`, `422`, `429`, `500` with `@app.errorhandler`.
- Define domain exceptions: `class ResourceNotFoundError(Exception): status_code = 404`.
- Register custom exception handlers: `@app.errorhandler(ResourceNotFoundError)`.
- Log `500` errors with full stack traces using `app.logger.exception`. Use `python-json-logger` in production.
- Never return Python exception messages or stack traces in HTTP responses.
Testing
- Use `pytest` with Flask's `test_client()`. Create a test app via `create_app('testing')` in `conftest.py`.
- Reset the database between tests: truncate all tables in a `session`-scoped fixture.
- Create test data with `factory_boy` factories. Use `SQLAlchemyModelFactory` with `Session` scoping.
- Test each blueprint: valid request → correct response, validation error → 422, unauthenticated → 401.
- Mock external calls with `unittest.mock.patch`. Never make real HTTP calls in unit or integration tests.
Performance & Deployment
- Use Gunicorn with `--workers $(nproc)` in production. Use `gevent` workers for I/O-heavy apps.
- Configure `SQLALCHEMY_POOL_SIZE`, `SQLALCHEMY_MAX_OVERFLOW`, and `SQLALCHEMY_POOL_RECYCLE`.
- Apply `Flask-Caching` with Redis for expensive view-level and function-level caching.
- Apply `Flask-Limiter` with Redis for distributed rate limiting. Use stricter limits on auth endpoints.
- Serve static files through Nginx or a CDN. Never let Flask serve statics in production.