8.5 KiB
8.5 KiB
Backend Development Rules for AI/LLM
This document provides guidelines and rules that AI/LLM assistants should follow when writing or modifying backend code for the Crafting Shop application.
Architecture Principles
Application Factory Pattern
- ALWAYS use the application factory pattern via
create_app()inbackend/app/__init__.py - NEVER initialize Flask extensions globally - create them at module level, initialize in
create_app() - NEVER create multiple SQLAlchemy instances - use the
dbextension fromapp/__init__.py
# ✅ CORRECT
from app import db
class User(db.Model):
pass
# ❌ WRONG
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Import Patterns
- Import
dband other extensions fromappmodule, notflask_sqlalchemydirectly - Import models from the
app.modelspackage (e.g.,from app.models import User) - All routes should import
dbfromapp
# ✅ CORRECT
from app import db
from app.models import User, Product, Order
# ❌ WRONG
from flask_sqlalchemy import SQLAlchemy
from app.models.user import User # Don't import from individual files
Code Style
Formatting
- Use double quotes for strings and dictionary keys
- Follow PEP 8 style guide
- Use meaningful variable and function names
- Maximum line length: 100 characters
Type Hints
- Add type hints to function signatures where appropriate
- Use Python 3.11+ type hinting features
Model Rules
Database Models
- ALL models must import
dbfromapp:from app import db - ALL models must inherit from
db.Model - NEVER create your own
db = SQLAlchemy()instance - Use
__tablename__explicitly - Use
to_dict()method for serialization - Use
__repr__()for debugging
# ✅ CORRECT
from app import db
from datetime import datetime
class Product(db.Model):
__tablename__ = "products"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {"id": self.id, "name": self.name}
Relationships
- Use
back_populatesfor bidirectional relationships - Use
lazy="dynamic"for one-to-many collections when appropriate - Define foreign keys explicitly with
db.ForeignKey()
Route/API Rules
Blueprint Usage
- ALL routes must be defined in blueprints
- Register blueprints in
create_app()inbackend/app/__init__.py - Group related routes by functionality
Request Handling
- Use
request.get_json()for JSON payloads - Always validate input data
- Return proper HTTP status codes
- Return JSON responses with
jsonify()
# ✅ CORRECT
from flask import Blueprint, request, jsonify
from app import db
api_bp = Blueprint("api", __name__)
@api_bp.route("/products", methods=["POST"])
@jwt_required()
def create_product():
data = request.get_json()
if not data or not data.get("name"):
return jsonify({"error": "Name is required"}), 400
product = Product(name=data["name"])
db.session.add(product)
db.session.commit()
return jsonify(product.to_dict()), 201
Database Operations
- NEVER access
dbthroughcurrent_app.extensions['sqlalchemy'].db - ALWAYS import
dbfromapp - Use
db.session.add()anddb.session.commit()for transactions - Use
db.session.flush()when you need the ID before commit
Error Handling
- Handle common errors (404, 400, 401, 403, 500)
- Return JSON error responses with consistent format
- Use Flask error handlers where appropriate
# ✅ CORRECT
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Not found"}), 404
Authentication
- Use
@jwt_required()decorator for protected routes - Use
get_jwt_identity()to get current user ID - Verify user permissions in routes
# ✅ CORRECT
from flask_jwt_extended import jwt_required, get_jwt_identity
@api_bp.route("/orders", methods=["POST"])
@jwt_required()
def create_order():
user_id = get_jwt_identity()
# ... rest of code
Configuration
Environment Variables
- ALL configuration must use environment variables via
os.environ.get() - Provide sensible defaults in
app/config.py - Use
.env.examplefor documentation
CORS
- Configure CORS in
create_app()using thecorsextension - Use
CORS_ORIGINSenvironment variable
# ✅ CORRECT
cors.init_app(app, resources={r"/api/*": {"origins": app.config.get('CORS_ORIGINS', '*')}})
Service Layer Rules
Business Logic
- Place complex business logic in
backend/app/services/ - Services should be stateless functions or classes
- Services should import
dbfromapp
# ✅ CORRECT
from app import db
from app.models import Product
def create_product_service(data):
product = Product(**data)
db.session.add(product)
db.session.commit()
return product
Testing Rules
Test Structure
- Use pytest framework
- Place tests in
backend/tests/ - Use fixtures for common setup
Database in Tests
- Use in-memory SQLite for tests
- Clean up database between tests
- Use
pytest.fixturefor database setup
Security Rules
Password Handling
- NEVER store plain text passwords
- Use
werkzeug.securityfor password hashing - Use
set_password()andcheck_password()methods
SQL Injection Prevention
- ALWAYS use SQLAlchemy ORM, never raw SQL
- Use parameterized queries if raw SQL is absolutely necessary
Input Validation
- Validate all user inputs
- Sanitize data before database operations
- Use Flask-WTF for form validation
File Structure for New Features
When adding new features, follow this structure:
backend/app/
├── models/
│ └── feature_name.py # Database models
├── routes/
│ └── feature_name.py # API routes
├── services/
│ └── feature_name.py # Business logic
└── utils/
└── feature_name.py # Utility functions
Common Patterns
Creating a New Model
from app import db
from datetime import datetime
class NewModel(db.Model):
__tablename__ = "new_model"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created_at": self.created_at.isoformat() if self.created_at else None
}
Creating New Routes
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from app import db
from app.models import NewModel
new_bp = Blueprint("new", __name__)
@new_bp.route("/resources", methods=["GET"])
def get_resources():
resources = NewModel.query.all()
return jsonify([r.to_dict() for r in resources]), 200
@new_bp.route("/resources", methods=["POST"])
@jwt_required()
def create_resource():
data = request.get_json()
resource = NewModel(**data)
db.session.add(resource)
db.session.commit()
return jsonify(resource.to_dict()), 201
Registering New Blueprints
In backend/app/__init__.py:
# Import models
from app.models.new_model import NewModel
# Register blueprint
from app.routes.feature_name import new_bp
app.register_blueprint(new_bp, url_prefix="/api/new")
DO NOT DO List
❌ NEVER create db = SQLAlchemy() in model files
❌ NEVER access db via current_app.extensions['sqlalchemy'].db
❌ NEVER initialize Flask extensions before create_app() is called
❌ NEVER use raw SQL queries without proper escaping
❌ NEVER store secrets in code (use environment variables)
❌ NEVER return plain Python objects (use jsonify())
❌ NEVER use single quotes for dictionary keys
❌ NEVER commit transactions without proper error handling
❌ NEVER ignore CORS configuration
❌ NEVER skip input validation
Checklist Before Committing
- All models import
dbfromapp - All routes import
dbfromapp - No
db = SQLAlchemy()in model files - All routes are in blueprints
- Proper error handling in place
- Environment variables used for configuration
- Type hints added to functions
- Tests written for new functionality
- Documentation updated
- Code follows PEP 8 style guide