Files
ohm_streaming/AGENTS.md
T
root 3dc5dd8fe9
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
feat: fix auth, provider health checks, search, and redesign UI
- Fix register/login: dict-style access on UserTable ORM objects
- Fix HTMX auth: inject JWT token in all HTMX request headers
- Fix FS7 search: use DLE AJAX endpoint /engine/ajax/search.php
- Fix ZT search: use ?p=series&search=QUERY (not DLE format)
- Fix provider health: load hardcoded providers + domain manager
- Add self.id to all anime/series providers
- Redesign homepage: Netflix-style horizontal scroll cards (.hc)
- Redesign search results: grouped by title, poster + synopsis + 3 buttons
- Add Télécharger dropdown: season download + episode picker
- Fix navbar CSS: restore .tabs flex layout, remove orphan rules
- Fix HTMX spinner: remove inline display:none, use CSS indicator
- Add AGENTS.md files across project for developer documentation
2026-03-28 00:14:31 +00:00

7.3 KiB

AGENTS.md — Ohm Stream Downloader

FastAPI anime/series downloader with HTMX+Alpine.js frontend, SQLModel/SQLite DB, 3-tier scraper architecture, JWT auth, Sonarr webhooks, and auto-download scheduler.

COMMANDS

# Dev server
uvicorn main:app --reload --host 0.0.0.0 --port 3000

# --- Tests (pytest, configured in pytest.ini, asyncio_mode=auto) ---

pytest                                          # All tests (coverage + verbose by default)
pytest -m "unit"                                # Fast unit tests only
pytest -m "integration"                         # API integration tests
pytest -m "not slow"                            # CI default — excludes slow tests
pytest -m "network"                             # Tests requiring network access

# Single file / class / test
pytest tests/test_sonarr.py -v
pytest tests/test_sonarr.py::TestSonarrHandler -v
pytest tests/test_sonarr.py::TestSonarrHandler::test_add_mapping -v

# Debug
pytest -s                                       # Show print() output
pytest --cov=app --cov-report=html              # HTML coverage report in htmlcov/

# --- Lint & Format (ruff) ---

ruff check app/                                 # Lint
ruff format --check app/                        # Format check (CI enforces this)
ruff format app/                                # Auto-format

# --- Type Check ---

mypy app/ --ignore-missing-imports              # Type check (CI enforces)

# --- DB Migrations ---

alembic revision --autogenerate -m "description"
alembic upgrade head

# --- Frontend (optional) ---

npm test                                        # Vitest JS tests
npx playwright test                             # E2E browser tests

CODE STYLE

Imports

Three groups separated by blank lines: stdlib → third-party → local (app.*). Always absolute imports (no relative). No wildcard imports (from X import * enforced).

Formatting

PEP 8, 120 chars max line length. Single quotes for strings, double quotes for docstrings. Ruff handles linting and formatting (no local config — CI-only).

Types

Explicit type hints on all function signatures and return types. Use Optional[X] from typing. Use list[str] / dict[str, X] (lowercase generics). Pydantic models for all API schemas. Return type annotations required on public methods.

Naming

  • snake_case for functions, variables, constants
  • PascalCase for classes and enums
  • UPPER_SNAKE_CASE for enum values (DownloadStatus.PENDING)
  • logger = logging.getLogger(__name__) at module level
  • _ prefix for private methods (_fetch_page, _sanitize)
  • get_* for factory functions (get_downloader, get_anime_site)

Error Handling

  • HTTPException for API errors with proper status codes
  • raise ValueError() for business logic validation
  • try/except with logging — never bare except: (known tech debt exists)
  • response.raise_for_status() for HTTP errors
  • Never return None for missing URLs from downloaders — raise an exception

Docstrings

Triple double quotes """. Module docstrings describe purpose. Class docstrings describe responsibility. Complex methods use Args/Returns sections. Not all methods require docstrings.

ARCHITECTURE

main.py                    # App entry, middleware, startup, router registration
app/
├── routers/               # 11 APIRouter modules (one per feature domain)
├── downloaders/           # 3-tier: anime_sites/ → series_sites/ → video_players/
├── models/                # Pydantic/SQLModel (Base → Table → Schema pattern)
├── config.py              # Pydantic Settings from .env
├── database.py            # SQLModel engine (created at import time)
├── download_manager.py    # Async queue, semaphore-based parallelism
├── auth.py                # JWT + bcrypt, SQLModel user storage
├── providers.py           # ANIME_PROVIDERS, SERIES_PROVIDERS, FILE_HOSTS registries
└── utils.py               # sanitize_filename(), is_safe_filename()
templates/                 # Jinja2 + HTMX + Alpine.js
static/js/                 # Vanilla ES modules (no build step)
tests/                     # pytest suite (conftest.py has shared fixtures)
config/                    # Runtime JSON files (users, watchlist, sonarr)
alembic/                   # DB migrations

KEY CONVENTIONS

  • URL pipe format: video_url|anime_page_url|episode_title — metadata preserved through download pipeline
  • Module-level init: database.py creates engine on import; main.py creates download_manager on import
  • Async everywhere: Always async/await for I/O — use httpx.AsyncClient, never requests
  • Factory pattern: get_downloader(url) routes anime → series → video player → generic
  • Router deps: Depends(lambda: download_manager), Depends(get_current_user_from_token), Depends(lambda: templates)
  • Dual storage: Some features use JSON files (legacy) + SQLModel tables (newer)
  • Frontend: No JS build step. HTMX for server interactions, Alpine.js for client state, Plyr.io for video
  • Circular import avoidance: episode_checker.py uses lazy init (set_download_manager())

ANTI-PATTERNS (DO NOT)

  • Use sync requests — always httpx.AsyncClient
  • Return None for missing URLs from downloaders — raise an exception
  • Skip sanitize_filename() on extracted filenames — path traversal risk
  • Forget await self.close() in downloaders — resource leak
  • Hardcode User-Agent in individual players — use base class headers
  • Use from X import * — always explicit imports
  • Import download_manager from main.py in app/ modules — causes circular imports
  • Store secrets in config/*.json — use .env
  • Use as any, @ts-ignore to suppress type errors (if adding TS)

TEST CONVENTIONS

  • tests/ directory with conftest.py for shared fixtures
  • Markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.network, @pytest.mark.slow
  • asyncio_mode = auto — async test functions run without explicit marker
  • Test naming: test_<verb>_<noun> in Test* classes
  • 300s timeout configured in pytest.ini; testpaths = tests
  • Legacy test files at project root: test_watchlist_simple.py, test_watchlist_e2e.py

ADDING NEW PROVIDERS

Video player: Create in app/downloaders/video_players/, inherit BaseVideoPlayer, implement can_handle() + get_download_link(url, target_filename=None), register in __init__.py, add to FILE_HOSTS in providers.py.

Anime/series site: Create in app/downloaders/anime_sites/ or series_sites/, inherit base class, implement search_anime() + get_episodes() + get_anime_metadata() + get_download_link(), register in __init__.py, add to providers.py.

NOTES

  • Python 3.11+, CI tests on 3.11 and 3.12
  • No pyproject.toml — uses requirements.txt with exact version pinning
  • GEMINI.md and CLAUDE.md exist with tool-specific instructions (merge if conflicting)
  • French-language project (animes, séries, VOSTFR) but all code and comments in English
  • ~20 empty except: blocks in downloaders/tests — known tech debt
  • JWT_SECRET_KEY min 32 chars, default rejected at startup; generate via Settings.generate_secret()
  • Sub-AGENTS.md files exist in app/, app/routers/, app/downloaders/, app/models/, app/downloaders/anime_sites/, app/downloaders/video_players/ for deeper context