# Provider Development Guide ## ๐ŸŽฏ Purpose This guide describes how to write, test, and register a provider so the application can discover and use it as a pluggable component. > Keep provider code small, focused, and well-tested. Use existing providers as examples. --- ## ๐Ÿ”ง Anatomy of a Provider A provider is a Python class that extends `ProviderCore.base.Provider` and implements a few key methods and attributes. Minimum expectations: - `class MyProvider(Provider):` โ€” subclass the base provider - `URL` / `URL_DOMAINS` or `url_patterns()` โ€” to let the registry route URLs - `validate(self) -> bool` โ€” return True when provider is configured and usable - `search(self, query, limit=50, filters=None, **kwargs)` โ€” return a list of `SearchResult` Optional but common: - `download(self, result: SearchResult, output_dir: Path) -> Optional[Path]` โ€” download a provider result - `selector(self, selected_items, *, ctx, stage_is_last=True, **kwargs) -> bool` โ€” handle `@N` selections - `download_url(self, url, output_dir, progress_cb=None)` โ€” direct URL-handling helper --- ## ๐Ÿงฉ SearchResult Use `ProviderCore.base.SearchResult` to describe results returned by `search()`. Important fields: - `table` (str) โ€” provider table name - `title` (str) โ€” short human title - `path` (str) โ€” canonical URL / link the provider/dl may use - `media_kind` (str) โ€” `file`, `folder`, `book`, etc. - `columns` (list[tuple[str,str]]) โ€” extra key/value pairs to display - `full_metadata` (dict) โ€” provider-specific metadata for downstream stages - `annotations` / `tag` โ€” simple metadata for filtering Return a list of `SearchResult(...)` objects or simple dicts convertible with `.to_dict()`. --- ## โœ… Implementing search() - Parse and sanitize `query` and `filters`. - Return no more than `limit` results. - Use `columns` to provide table columns (TITLE, Seeds, Size, etc.). - Keep `search()` fast and predictable (apply reasonable timeouts). Example: ```python from ProviderCore.base import Provider, SearchResult class HelloProvider(Provider): def search(self, query, limit=50, filters=None, **kwargs): q = (query or "").strip() if not q: return [] results = [] # Build up results results.append( SearchResult( table="hello", title=f"Hit for {q}", path=f"https://example/{q}", columns=[("Info", "example")], full_metadata={"source": "hello"}, ) ) return results[:max(0, int(limit))] ``` --- ## โฌ‡๏ธ Implementing download() and download_url() - Prefer provider `download(self, result, output_dir)` for piped provider items. - For provider-provided URLs, implement `download_url` to allow `download-file` to route downloads through providers. - Use the repo `_download_direct_file` helper for HTTP downloads when possible. Example download(): ```python def download(self, result: SearchResult, output_dir: Path) -> Optional[Path]: # Validate config url = getattr(result, "path", None) if not url or not url.startswith("http"): return None # use existing helpers to fetch the file return _download_direct_file(url, output_dir) ``` --- ## ๐Ÿงญ URL routing Providers can declare: - `URL = ("magnet:",)` or similar prefix list - `URL_DOMAINS = ("example.com",)` to match hosts - Or override `@classmethod def url_patterns(cls):` to combine static and dynamic patterns The registry uses these to match `download-file ` or to pick which provider should handle the URL. --- ## ๐Ÿ›  Selector (handling `@N` picks) - Implement `selector(self, selected_items, *, ctx, stage_is_last=True)` to present a sub-table or to enqueue downloads. - Use `ctx.set_last_result_table()` and `ctx.set_current_stage_table()` to display follow-ups. - Return `True` when you handled the selection and the pipeline should pause or proceed accordingly. --- ## ๐Ÿงช Testing providers - Keep tests small and local. Create `tests/test_provider_.py`. - Test `search()` with mock HTTP responses (use `requests-mock` or similar). - Test `download()` using a temp directory and a small file server or by mocking `_download_direct_file`. - Test `selector()` by constructing a fake result and `ctx` object. Example PowerShell commands to run tests (repo root): ```powershell # Run a single test file pytest tests/test_provider_hello.py -q # Run all tests pytest -q ``` --- ## ๐Ÿ“ฆ Registration & packaging - Add your provider module under `Provider/` and ensure it is imported by module package initialization. Common approach: - Place file `Provider/myprovider.py` - Ensure `Provider/__init__.py` imports the module (or the registry auto-discovers by package import) - If the project has a central provider registry, add lookup helpers there (e.g., `ProviderCore/registry.py`). Usually providers register themselves at import time. --- ## ๐Ÿ’ก Best practices & tips - Use `debug()` / `log()` appropriately; avoid noisy stderr output in normal runs. - Prefer returning `SearchResult` objects to provide consistent UX. - Keep `search()` tolerant (timeouts, malformed responses) and avoid raising for expected network problems. - Use `full_metadata` to pass non-display data to `download()` and `selector()`. - Respect the `limit` parameter in `search()`. --- ## ๐Ÿงพ Example provider checklist - [ ] Implement `search()` and return `SearchResult` items - [ ] Implement `validate()` to check essential config (API keys, credentials) - [ ] Provide `URL` / `URL_DOMAINS` or `url_patterns()` for routing - [ ] Add `download()` or `download_url()` for piped/passed URL downloads - [ ] Add tests under `tests/` - [ ] Add module to `Provider/` package and ensure import/registration --- ## ๐Ÿ”— Further reading - See existing providers in `Provider/` for patterns and edge cases. - Check `API/` helpers for HTTP and debrid clients. --- If you'd like, I can: - Add an example provider file under `Provider/` as a template (see `Provider/hello_provider.py`), and - Create unit tests for it (see `tests/test_provider_hello.py`). I have added a minimal example provider and tests in this repository; use them as a starting point for new providers.