2482a1fe58
- Add AGENTS.md for agentic coding guidelines - Add Oneupload and Smoothpre video player downloaders - Add MetadataEnrichment service with Kitsu API fallback - Add tests for metadata enrichment and provider detection - Update .gitignore to ignore runtime config files
4.2 KiB
4.2 KiB
AGENTS.md - Agentic Coding Guidelines
This file provides guidance for AI agents working in this repository.
Quick Start
# Setup
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# Run dev server
uvicorn main:app --reload --host 0.0.0.0 --port 3000
Build, Lint & Test Commands
Running Tests
# All tests
pytest
# With coverage
pytest --cov=app --cov-report=html
# Unit only (fast)
pytest -m "unit"
# Exclude slow tests
pytest -m "not slow"
# Verbose with print debugging
pytest -v -s
Running Single Tests
# Specific file
pytest tests/test_sonarr.py -v
# Specific class
pytest tests/test_sonarr.py::TestSonarrHandler -v
# Specific test
pytest tests/test_sonarr.py::TestSonarrHandler::test_add_mapping -v
# Pattern match
pytest -k "test_download" -v
Code Style
Imports (PEP 8 order)
- Standard library (
os,json,asyncio) - Third-party (
httpx,beautifulsoup4,fastapi) - Local app (
app.config,app.utils)
import os
import asyncio
from typing import Optional
import httpx
from fastapi import APIRouter, HTTPException
from app.config import get_settings
from app.models import DownloadTask, DownloadStatus
Formatting
- Line length: 120 chars max
- Indentation: 4 spaces
- Blank lines: 2 between top-level, 1 between inline
Type Annotations
- Use explicit types
- Use
Optional[X]notX | None - Use
list[X],dict[X, Y]
# Good
async def get_download_link(url: str, target_filename: Optional[str] = None) -> tuple[str, str]:
results: list[dict[str, str]] = []
# Avoid
async def get_download_link(url, target_filename=None):
results = []
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Modules | snake_case | download_manager.py |
| Classes | PascalCase | DownloadManager |
| Functions | snake_case | get_download_link() |
| Constants | UPPER_SNAKE | MAX_PARALLEL_DOWNLOADS |
| Variables | snake_case | download_task |
| Enums | PascalCase | DownloadStatus |
| Enum values | UPPER_SNAKE | DownloadStatus.PENDING |
Async/Await
- Always use for I/O operations
- Close clients properly to avoid leaks
async def close(self):
await self.client.aclose()
Error Handling
- Use try/except for recoverable errors
- Raise specific exceptions (
HTTPException,ValueError) - Never use empty except blocks
- Log errors appropriately
try:
result = await client.get(url)
except httpx.TimeoutException:
logger.warning(f"Request timeout for {url}")
raise HTTPException(status_code=504, detail="Request timeout")
File Operations
- Always sanitize filenames:
app.utils.sanitize_filename() - Validate paths:
app.utils.is_safe_filename()
Testing
- Use pytest with pytest-asyncio
- Mark tests:
@pytest.mark.unit,@pytest.mark.integration - Use fixtures from
tests/conftest.py
@pytest.mark.unit
@pytest.mark.asyncio
async def test_download_manager():
manager = DownloadManager(max_parallel=3)
assert manager.max_parallel == 3
Security
- Never hardcode secrets - use environment variables
- Validate all inputs (URLs, filenames)
- Use HMAC for webhook verification when configured
- Limit CORS origins - never use
*in production
Architecture Patterns
Three-Tier Downloader:
app/downloaders/anime_sites/- Anime catalogsapp/downloaders/series_sites/- TV series catalogsapp/downloaders/video_players/- File hosting
Each has base class and factory. When adding providers:
- Inherit from appropriate base class
- Implement required methods
- Register in factory
- Add to providers config in
app/providers.py
URL Convention: Pipe-separated format preserves metadata:
video_url|anime_page_url|episode_title
Key Files
| File | Purpose |
|---|---|
main.py |
FastAPI app, endpoints |
app/config.py |
Pydantic Settings |
app/download_manager.py |
Download queue |
app/utils.py |
sanitize_filename |
app/auth.py |
JWT auth |
app/models/__init__.py |
Pydantic models |
Configuration
- Use
.envfrom.env.example - JWT_SECRET_KEY must change in production