Files
Medios-Macina/docs/provider_guide.md
2026-01-05 07:51:19 -08:00

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 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().


  • 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:

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():

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 <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() 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_<name>.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):

# 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.