7.7 KiB
7.7 KiB
name, description
| name | description |
|---|---|
| flask-reviewer | WHEN: Flask project review, Blueprint structure, extensions, request handling WHAT: Blueprint organization + Extension patterns + Request/response handling + Configuration + Testing WHEN NOT: FastAPI → fastapi-reviewer, Django → django-reviewer, General Python → python-reviewer |
Flask Reviewer Skill
Purpose
Reviews Flask projects for application structure, extension usage, and best practices.
When to Use
- Flask project code review
- Blueprint structure review
- Extension configuration review
- Request handling patterns
- Flask API design
Project Detection
flaskin requirements.txt/pyproject.tomlfrom flask import Flaskimportsapp.pyor__init__.pywith Flask()blueprints/orroutes/directory
Workflow
Step 1: Analyze Project
**Flask**: 3.0+
**Extensions**: Flask-SQLAlchemy, Flask-Login, Flask-WTF
**API**: Flask-RESTful / Flask-RESTX
**Database**: SQLAlchemy
**Template**: Jinja2
Step 2: Select Review Areas
AskUserQuestion:
"Which areas to review?"
Options:
- Full Flask review (recommended)
- Application structure
- Blueprint organization
- Extension configuration
- Security and validation
multiSelect: true
Detection Rules
Application Factory
| Check | Recommendation | Severity |
|---|---|---|
| Global app instance | Use application factory | HIGH |
| Config in code | Use config classes | MEDIUM |
| No extension init | Use init_app pattern | MEDIUM |
| Circular imports | Use factory + blueprints | HIGH |
# BAD: Global app instance
# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app) # Tight coupling
# GOOD: Application factory
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
from app.routes import main_bp, api_bp
app.register_blueprint(main_bp)
app.register_blueprint(api_bp, url_prefix="/api")
return app
Blueprint Organization
| Check | Recommendation | Severity |
|---|---|---|
| All routes in one file | Split into blueprints | MEDIUM |
| No URL prefix | Add url_prefix to blueprints | LOW |
| Mixed concerns | Separate by domain | MEDIUM |
| No init.py exports | Export blueprint properly | LOW |
# GOOD: Blueprint structure
# app/routes/users.py
from flask import Blueprint, request, jsonify
users_bp = Blueprint("users", __name__, url_prefix="/users")
@users_bp.route("/", methods=["GET"])
def list_users():
return jsonify(users=User.query.all())
@users_bp.route("/<int:user_id>", methods=["GET"])
def get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify(user=user.to_dict())
# app/routes/__init__.py
from app.routes.users import users_bp
from app.routes.products import products_bp
__all__ = ["users_bp", "products_bp"]
Request Handling
| Check | Recommendation | Severity |
|---|---|---|
| No input validation | Use marshmallow/pydantic | HIGH |
| request.json without check | Handle None case | MEDIUM |
| No error handlers | Add @app.errorhandler | MEDIUM |
| Sync blocking calls | Consider async or Celery | MEDIUM |
# BAD: No validation
@app.route("/user", methods=["POST"])
def create_user():
data = request.json # Could be None!
user = User(name=data["name"]) # KeyError risk
return jsonify(user.to_dict())
# GOOD: With validation (marshmallow)
from marshmallow import Schema, fields, validate
class UserSchema(Schema):
name = fields.Str(required=True, validate=validate.Length(min=1, max=100))
email = fields.Email(required=True)
user_schema = UserSchema()
@app.route("/user", methods=["POST"])
def create_user():
data = request.get_json()
if not data:
return jsonify(error="No JSON data"), 400
errors = user_schema.validate(data)
if errors:
return jsonify(errors=errors), 400
user = User(**user_schema.load(data))
db.session.add(user)
db.session.commit()
return jsonify(user=user.to_dict()), 201
Configuration
| Check | Recommendation | Severity |
|---|---|---|
| Secrets in code | Use environment variables | CRITICAL |
| No config classes | Use config hierarchy | MEDIUM |
| DEBUG=True in prod | Environment-based config | CRITICAL |
| No instance folder | Use instance config | LOW |
# config.py
import os
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-key-change-me"
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get("DEV_DATABASE_URL") or \
"sqlite:///dev.db"
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
@classmethod
def init_app(cls, app):
# Production-specific initialization
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler("app.log", maxBytes=10240, backupCount=10)
handler.setLevel(logging.INFO)
app.logger.addHandler(handler)
config = {
"development": DevelopmentConfig,
"production": ProductionConfig,
"default": DevelopmentConfig,
}
Error Handling
| Check | Recommendation | Severity |
|---|---|---|
| No custom error pages | Add error handlers | MEDIUM |
| Exception details in response | Hide in production | HIGH |
| No logging | Add structured logging | MEDIUM |
# app/errors.py
from flask import jsonify, render_template
def register_error_handlers(app):
@app.errorhandler(400)
def bad_request(error):
if request_wants_json():
return jsonify(error="Bad request"), 400
return render_template("errors/400.html"), 400
@app.errorhandler(404)
def not_found(error):
if request_wants_json():
return jsonify(error="Not found"), 404
return render_template("errors/404.html"), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
app.logger.error(f"Internal error: {error}")
if request_wants_json():
return jsonify(error="Internal server error"), 500
return render_template("errors/500.html"), 500
def request_wants_json():
return request.accept_mimetypes.best_match(
["application/json", "text/html"]
) == "application/json"
Response Template
## Flask Code Review Results
**Project**: [name]
**Flask**: 3.0 | **SQLAlchemy**: 2.0 | **Extensions**: Login, WTF
### Application Structure
| Status | File | Issue |
|--------|------|-------|
| HIGH | app.py | Global app instance - use factory |
### Blueprint Organization
| Status | File | Issue |
|--------|------|-------|
| MEDIUM | routes.py | 50+ routes - split into blueprints |
### Request Handling
| Status | File | Issue |
|--------|------|-------|
| HIGH | views.py:34 | No input validation on POST |
### Configuration
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | config.py | SECRET_KEY hardcoded |
### Recommended Actions
1. [ ] Implement application factory pattern
2. [ ] Split routes into domain blueprints
3. [ ] Add marshmallow validation schemas
4. [ ] Move secrets to environment variables
Best Practices
- Factory Pattern: Always use create_app()
- Blueprints: Organize by domain/feature
- Validation: marshmallow or pydantic
- Config: Environment-based hierarchy
- Extensions: Use init_app pattern
Integration
python-reviewer: General Python patternssecurity-scanner: Flask security auditapi-documenter: API documentation