REST API for TV show metadata, episodes, air dates, and summaries. Also provides an MCP server for AI assistants.
- Commit Messages: Use descriptive prefixes (
feat:,fix:,refactor:,chore:,docs:). - Branching: Work primarily on
main. Always pull latest before starting. - Pre-commit: Ensure pre-commit hooks pass before pushing.
ALL changes MUST go through git. Manual SSH changes are ephemeral and will be overwritten.
- Commit & Push: All production-ready changes MUST be pushed to the
mainbranch. - Push Verification: Confirm GitHub has the latest hash:
git ls-remote origin main. - Trigger Deploy: Use the
proxmox-managerCLI to deploy:cd ~/code/proxmox-manager ./pm services deploy --service epguides --vmid 122 --yes
- Server Verification: Confirm the container pulled the correct hash:
./pm services run --service epguides --vmid 122 --cmd "cd /opt/epguides && git log -n 1 --oneline" - Force Rebuild (Optional): If changes don't reflect, force a no-cache build:
./pm services run --service epguides --vmid 122 --cmd "cd /opt/epguides && docker compose -f docker-compose.prod.yml build --no-cache backend"
- Framework: FastAPI (async)
- Caching: Redis with TTL-based invalidation
- Data Sources: epguides.com (primary), TVMaze API (fallback)
- Python: 3.12+
- Public API: https://epguides.frecar.no
make help # Show all commands
make up # Start dev environment (Docker + hot reload)
make down # Stop all services
make test # Run tests (100% coverage required)
make fix # Format + lint with ruff
make doctor # Check environment health
make urls # Show service URLs
make clean # Remove cache filesRun single test:
pytest app/tests/test_endpoints.py::test_function -vapp/
├── api/endpoints/ # REST routes
│ ├── shows.py # /shows/* endpoints
│ └── mcp.py # /mcp JSON-RPC endpoint
├── core/
│ ├── cache.py # Redis caching, @cached decorator
│ ├── config.py # Pydantic settings
│ └── constants.py # TTLs, version, URLs
├── models/
│ ├── schemas.py # ShowSchema, EpisodeSchema
│ └── responses.py # PaginatedResponse
├── services/
│ ├── show_service.py # Business logic
│ ├── epguides.py # External API calls
│ └── llm_service.py # Natural language queries
└── tests/ # 100% coverage required
Flow: Endpoints -> Services -> External APIs, with Redis caching at service layer.
@cached("show:{show_id}", ttl=TTL_7_DAYS, model=ShowSchema, key_transform=normalize_show_id)
async def get_show(show_id: str) -> ShowSchema | None:
...TTL constants (app/core/constants.py):
TTL_7_DAYS- Ongoing shows, episodesTTL_30_DAYS- Show list, indexesTTL_1_YEAR- Finished shows
# Use factory function (handles defaults)
show = create_show_schema(epguides_key="test", title="Test")
# Not direct instantiation
show = ShowSchema(...) # Don't do thisresults = await asyncio.gather(
epguides.get_episodes_data(show_id),
epguides.get_maze_id_for_show(show_id),
)100% coverage enforced by pre-commit. Commits blocked if coverage drops.
Mock patterns:
@patch("app.core.cache.cache_get", return_value=None) # Cache miss
@patch("app.services.show_service.get_show") # Service layerPerformance tests: < 50ms hard limit, < 20ms target.
If code can't be tested, remove it.
Runs automatically on commit:
- Trailing whitespace, YAML check, large files, merge conflicts, private keys
- Ruff lint + format
- Version update
- Tests with 100% coverage
Setup: make setup (installs venv + hooks)
- Line length: 120
- Python 3.12+
- All I/O async
- Ruff for linting and formatting