Initial commit
This commit is contained in:
286
skills/flask-reviewer/SKILL.md
Normal file
286
skills/flask-reviewer/SKILL.md
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
name: flask-reviewer
|
||||
description: |
|
||||
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
|
||||
- `flask` in requirements.txt/pyproject.toml
|
||||
- `from flask import Flask` imports
|
||||
- `app.py` or `__init__.py` with Flask()
|
||||
- `blueprints/` or `routes/` 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 |
|
||||
|
||||
```python
|
||||
# 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 |
|
||||
|
||||
```python
|
||||
# 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 |
|
||||
|
||||
```python
|
||||
# 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 |
|
||||
|
||||
```python
|
||||
# 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 |
|
||||
|
||||
```python
|
||||
# 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
|
||||
1. **Factory Pattern**: Always use create_app()
|
||||
2. **Blueprints**: Organize by domain/feature
|
||||
3. **Validation**: marshmallow or pydantic
|
||||
4. **Config**: Environment-based hierarchy
|
||||
5. **Extensions**: Use init_app pattern
|
||||
|
||||
## Integration
|
||||
- `python-reviewer`: General Python patterns
|
||||
- `security-scanner`: Flask security audit
|
||||
- `api-documenter`: API documentation
|
||||
Reference in New Issue
Block a user