# Development Guide ## Document Purpose This document provides guidance for developers working on the Aniworld project. ### What This Document Contains - **Prerequisites**: Required software and tools - **Environment Setup**: Step-by-step local development setup - **Project Structure**: Source code organization explanation - **Development Workflow**: Branch strategy, commit conventions - **Coding Standards**: Style guide, linting, formatting - **Running the Application**: Development server, CLI usage - **Debugging Tips**: Common debugging approaches - **IDE Configuration**: VS Code settings, recommended extensions - **Contributing Guidelines**: How to submit changes - **Code Review Process**: Review checklist and expectations ### What This Document Does NOT Contain - Production deployment (see [DEPLOYMENT.md](DEPLOYMENT.md)) - API reference (see [API.md](API.md)) - Architecture decisions (see [ARCHITECTURE.md](ARCHITECTURE.md)) - Test writing guides (see [TESTING.md](TESTING.md)) - Security guidelines (see [SECURITY.md](SECURITY.md)) ### Target Audience - New Developers joining the project - Contributors (internal and external) - Anyone setting up a development environment --- ## Sections to Document 1. Prerequisites - Python version - Conda environment - Node.js (if applicable) - Git 2. Getting Started - Clone repository - Setup conda environment - Install dependencies - Configuration setup 3. Project Structure Overview 4. Development Server - Starting FastAPI server - Hot reload configuration - Debug mode 5. CLI Development 6. Code Style - PEP 8 compliance - Type hints requirements - Docstring format - Import organization 7. Git Workflow - Branch naming - Commit message format - Pull request process 8. Common Development Tasks ### Adding Queue Deduplication The download queue prevents duplicate entries at two levels: **In-Memory Deduplication** (`src/server/services/download_service.py`): - `_pending_by_episode` dict tracks pending episodes: key = `(serie_id, season, episode)` - `_add_to_pending_queue()` updates the dict when adding items - `add_to_queue()` checks this dict before adding episodes (includes batch-local dedup) - `_remove_from_pending_queue()` cleans up the dict when items are removed **Database Constraint** (`src/server/models.py`): - `DownloadQueueItem` has a unique index on `episode_id` via `__table_args__` - Prevents duplicate queue entries at the database level - Unique constraint: `Index("ix_download_queue_episode_pending", "episode_id", unique=True)` **Scheduler Cooldown** (`src/server/services/scheduler_service.py`): - `_last_auto_download_time` tracks when auto-download last ran - 5-minute cooldown prevents rapid re-triggers - Checked at start of `_auto_download_missing()` ### Mocking the Download Queue When testing components that use the download queue: ```python # Mock repository for unit tests class MockQueueRepository: def __init__(self): self._items: Dict[str, DownloadItem] = {} async def save_item(self, item: DownloadItem) -> DownloadItem: self._items[item.id] = item return item async def get_all_items(self) -> List[DownloadItem]: return list(self._items.values()) # Use in fixture @pytest.fixture def mock_queue_repository(): return MockQueueRepository() @pytest.fixture def download_service(mock_anime_service, mock_queue_repository): return DownloadService( anime_service=mock_anime_service, queue_repository=mock_queue_repository, max_retries=3, ) ``` 9. Troubleshooting Development Issues ### Async Context Managers for aiohttp All `aiohttp.ClientSession` usages must be wrapped in `async with`: ```python # Correct — session properly closed on exit async with TMDBClient(api_key="key") as client: result = await client.search_tv_show("Show") # Wrong — session may leak if exception occurs client = TMDBClient(api_key="key") result = await client.search_tv_show("Show") await client.close() # May not be called if exception raised earlier ``` **Why:** - `aiohttp.ClientSession` holds TCP connections that must be explicitly closed - If exception occurs before `close()`, session leaks - Context manager guarantees `__aexit__` runs even on exceptions **Services that use aiohttp:** - `TMDBClient` — has `__aenter__`/`__aexit__`, use `async with` - `ImageDownloader` — has `__aenter__`/`__aexit__`, use `async with` - `NFOService` — wraps both above, use `async with` **Verification:** - Missing context manager usage triggers `__del__` warning on garbage collection - Integration tests verify no "Unclosed client session" errors in logs ### Scheduler Persistence and Recovery APScheduler stores jobs in `data/scheduler.db` (SQLite) so they survive process restarts: ```python from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore jobstores = { "default": SQLAlchemyJobStore(url="sqlite:///./data/scheduler.db"), } scheduler = AsyncIOScheduler(jobstores=jobstores) ``` **Grace period:** `misfire_grace_time=3600` (1 hour). If server is down at scheduled time and restarts within 1 hour, missed job runs automatically via APScheduler coalesce behavior. **Startup recovery:** On `start()`, scheduler loads persisted jobs from DB. APScheduler handles missed jobs internally when `coalesce=True`. **Health endpoint:** `GET /health` returns `scheduler_next_run` and `scheduler_last_run` for external monitors (Uptime Kuma, Prometheus, etc.). **If server is down >1 hour:** No automatic recovery. Manual trigger via `POST /api/scheduler/trigger-rescan` or wait for next scheduled run. ### Health Check Endpoints The application provides health check endpoints for monitoring and container orchestration: #### `GET /health` Basic health check returning service status and startup health check results. **Response fields:** - `status`: "healthy", "degraded", or "unhealthy" based on startup checks - `timestamp`: ISO timestamp of the check - `series_app_initialized`: Whether the series app is loaded - `anime_directory_configured`: Whether anime_directory is set - `scheduler_next_run` / `scheduler_last_run`: Scheduler times - `checks`: Detailed startup check results (ffmpeg, DNS, anime_directory) #### `GET /health/ready` Readiness check for container orchestrators (Kubernetes, Docker Swarm). **Response when ready:** ```json { "status": "ready", "ready": true, "timestamp": "2024-01-01T00:00:00", "checks": {...} } ``` **Response when not ready (503):** ```json { "status": "not_ready", "ready": false, "timestamp": "2024-01-01T00:00:00", "critical_failures": ["anime_directory: not configured"], "checks": {...} } ``` #### `GET /health/detailed` Comprehensive health check including database, filesystem, and system metrics. #### Startup Health Checks On application startup, the following checks are performed: | Check | Failure Status | Impact | |-------|---------------|--------| | `ffmpeg` | warning | HLS downloads may fail | | `dns_aniworld` | warning | Provider requests may fail | | `dns_tmdb` | warning | TMDB API calls may fail | | `anime_directory` | error | Download service disabled | DNS checks are warnings because failures can be transient. anime_directory errors disable the download service to prevent failures. ### Troubleshooting Development Issues #### Scheduler missed a run 1. Server was down at scheduled time (03:00 UTC by default). 2. Check `data/scheduler.db` exists — if not, jobs are not persisted. 3. If server was down >1 hour, missed job is dropped (misfire window exceeded). 4. Trigger manually: `POST /api/scheduler/trigger-rescan` 5. Monitor next run: `GET /health` → `scheduler_next_run` 6. If problem repeats, increase `misfire_grace_time` in `scheduler_service.py`. #### Startup health check failures If `/health` returns `unhealthy` status: 1. **anime_directory error**: Directory not configured or not writable - Check `ANIME_DIRECTORY` environment variable - Verify directory exists and permissions allow write access - Download service will not initialize until resolved 2. **ffmpeg warning**: ffmpeg not found in PATH - HLS stream downloads will fail - Install ffmpeg: `apt install ffmpeg` or `brew install ffmpeg` 3. **DNS warnings**: Domain resolution failed - Check network connectivity - DNS failures are transient — warnings don't block startup - Retry later to verify: `GET /health`