6.1 KiB
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 providerURL/URL_DOMAINSorurl_patterns()— to let the registry route URLsvalidate(self) -> bool— return True when provider is configured and usablesearch(self, query, limit=50, filters=None, **kwargs)— return a list ofSearchResult
Optional but common:
download(self, result: SearchResult, output_dir: Path) -> Optional[Path]— download a provider resultselector(self, selected_items, *, ctx, stage_is_last=True, **kwargs) -> bool— handle@Nselectionsdownload_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 nametitle(str) — short human titlepath(str) — canonical URL / link the provider/dl may usemedia_kind(str) —file,folder,book, etc.columns(list[tuple[str,str]]) — extra key/value pairs to displayfull_metadata(dict) — provider-specific metadata for downstream stagesannotations/tag— simple metadata for filtering
Return a list of SearchResult(...) objects or simple dicts convertible with .to_dict().
✅ Implementing search()
- Parse and sanitize
queryandfilters. - Return no more than
limitresults. - Use
columnsto provide table columns (TITLE, Seeds, Size, etc.). - Keep
search()fast and predictable (apply reasonable timeouts).
Example:
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_urlto allowdownload-fileto route downloads through providers. - Use the repo
_download_direct_filehelper for HTTP downloads when possible.
Example download():
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 listURL_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 <url> 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()andctx.set_current_stage_table()to display follow-ups. - Return
Truewhen you handled the selection and the pipeline should pause or proceed accordingly.
🧪 Testing providers
- Keep tests small and local. Create
tests/test_provider_<name>.py. - Test
search()with mock HTTP responses (userequests-mockor 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 andctxobject.
Example PowerShell commands to run tests (repo root):
# 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__.pyimports the module (or the registry auto-discovers by package import)
- Place file
- 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
SearchResultobjects to provide consistent UX. - Keep
search()tolerant (timeouts, malformed responses) and avoid raising for expected network problems. - Use
full_metadatato pass non-display data todownload()andselector(). - Respect the
limitparameter insearch().
🧾 Example provider checklist
- Implement
search()and returnSearchResultitems - Implement
validate()to check essential config (API keys, credentials) - Provide
URL/URL_DOMAINSorurl_patterns()for routing - Add
download()ordownload_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 (seeProvider/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.