# AI Agent Instructions for Quizzical Beats This document provides guidelines for AI coding agents working on the Quizzical Beats repository. ## Project Overview **Quizzical Beats** is a Flask-based web application for creating music quiz rounds for pub quizzes. It integrates with multiple music APIs (Spotify, Deezer, Last.fm) and provides PDF/MP3 export capabilities. ## Technology Stack - **Backend**: Python 3.11+, Flask 3.x - **Database**: SQLAlchemy ORM (SQLite dev, PostgreSQL/MySQL production) - **Frontend**: Jinja2 templates, vanilla JavaScript - **APIs**: Spotify, Deezer, Last.fm, OpenAI, Dropbox - **Authentication**: Flask-Login, Authlib (OAuth) - **Deployment**: Docker, Docker Compose ## Repository Structure ``` musicround/ # Main application package ├── __init__.py # Application factory ├── config.py # Configuration management ├── models.py # Database models (SQLAlchemy) ├── helpers/ # Utility modules ├── routes/ # Flask blueprints (auth, api, core, generate, etc.) ├── static/ # CSS, JavaScript, images └── templates/ # Jinja2 HTML templates tests/ # Test suite docs/ # MkDocs documentation migrations/ # Database migration scripts ``` ## Code Style Guidelines ### Python Style - Follow **PEP 8** with maximum line length of **100 characters** - Use **4 spaces** for indentation (no tabs) - Provide docstrings for all functions and classes (Google style) - Use type hints where beneficial ```python def process_playlist(playlist_id: str, user_id: int) -> dict: """Process a Spotify playlist and import songs. Args: playlist_id: The Spotify playlist ID user_id: The user's database ID Returns: Dictionary containing import results with keys: - success: Boolean indicating success - songs_imported: Number of songs imported - errors: List of error messages (if any) """ pass ``` ### Flask Best Practices - Organize routes using blueprints - Prefer class-based views for complex endpoints - Use Flask-WTF for form handling - Always validate and sanitize user inputs - Use SQLAlchemy ORM (never raw SQL without parameterization) ### Security Requirements - **NEVER** commit API keys, secrets, or passwords - **ALWAYS** use environment variables for sensitive data - Validate all user inputs - Use parameterized queries (SQLAlchemy ORM does this) - Escape all template outputs (Jinja2 auto-escaping) - Check [SECURITY.md](SECURITY.md) before making security-related changes ## Development Workflow ### Before Making Changes 1. **Understand the codebase**: - Read related code in `musicround/routes/` and `musicround/helpers/` - Check existing tests in `tests/` - Review documentation in `docs/` 2. **Check existing issues and roadmap**: - Review [TODO.md](TODO.md) for planned features - Check [ROADMAP.md](ROADMAP.md) for strategic direction - Search GitHub issues for related discussions 3. **Set up development environment**: ```bash python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate pip install -r requirements.txt ``` ### Making Changes 1. **Create minimal, focused changes**: - Make the smallest change that solves the problem - Don't refactor unrelated code - Don't fix unrelated bugs or style issues 2. **Write tests**: - Add tests for new functionality in `tests/` - Update existing tests if behavior changes - Run tests: `pytest tests/ -v` 3. **Update documentation**: - Update docstrings for modified functions - Update `docs/` if user-facing changes - Update `README.md` if installation/setup changes ### Testing ```bash # Run all tests pytest tests/ -v # Run specific test file pytest tests/test_metadata.py -v # Run with coverage pytest --cov=musicround tests/ ``` ### Linting and Code Quality ```bash # Format code (if black is installed) black musicround/ --line-length 100 # Check code style flake8 musicround/ --max-line-length=100 # Type checking (if mypy is installed) mypy musicround/ ``` ## Common Tasks ### Adding a New Route ```python # In musicround/routes/new_feature.py from flask import Blueprint, render_template, request from flask_login import login_required, current_user new_feature_bp = Blueprint('new_feature', __name__) @new_feature_bp.route('/new-feature') @login_required def index(): """Display the new feature page.""" return render_template('new_feature/index.html') ``` Then register in `musicround/__init__.py`: ```python from musicround.routes.new_feature import new_feature_bp app.register_blueprint(new_feature_bp) ``` ### Adding a Database Model ```python # In musicround/models.py class NewModel(db.Model): """Description of what this model represents.""" __tablename__ = 'new_model' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) def __repr__(self): return f'' ``` Then create a migration: ```bash flask db migrate -m "Add NewModel" flask db upgrade ``` ### Adding a Configuration Variable ```python # In musicround/config.py class Config: NEW_SETTING = os.getenv("NEW_SETTING", "default_value") ``` Add to `.env.example`: ```env # Description of what this does NEW_SETTING=default_value ``` ## Database Migrations - Migration files are in `migrations/` - Use `run_migration.py` to run migrations - Always test migrations on a backup database first - Document schema changes in migration message ```bash # Create a new migration python run_migration.py # Or manually flask db migrate -m "Description of change" flask db upgrade ``` ## API Integration Guidelines ### Spotify API - Token management in `musicround/helpers/spotify_helper.py` - Use existing client manager: `SpotifyClientManager` - Handle rate limits gracefully (retry with backoff) - Always refresh expired tokens ### OAuth Integration - OAuth routes in `musicround/routes/auth.py` - Store tokens encrypted in database (User model) - Implement token refresh before expiration - Follow existing patterns for new OAuth providers ## Error Handling ```python # Use Flask error handlers from musicround.errors import APIError @app.errorhandler(APIError) def handle_api_error(error): return render_template('error.html', error=error), error.status_code # In your code if not valid: raise APIError("Invalid input", status_code=400) ``` ## Logging ```python import logging logger = logging.getLogger(__name__) # Use appropriate log levels logger.debug("Detailed debugging information") logger.info("Informational messages") logger.warning("Warning messages") logger.error("Error messages") logger.critical("Critical errors") ``` ## Commit Messages Follow conventional commit format: ``` ():