From a2b338e05c61e09a785921e5c0ddc0d323534a5b Mon Sep 17 00:00:00 2001 From: Nirvana Date: Sun, 4 Oct 2026 12:25:03 +0200 Subject: [PATCH] Simpli - document provider_name property --- .../providers/_template/README.md | 70 ++++++++++++++++--- .../providers/_template/provider.py | 32 ++++++++- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/lib/streaming_providers/providers/_template/README.md b/lib/streaming_providers/providers/_template/README.md index c09c447..14934ac 100644 --- a/lib/streaming_providers/providers/_template/README.md +++ b/lib/streaming_providers/providers/_template/README.md @@ -16,7 +16,7 @@ and fill in the stubs. Read this file first — it explains the contract. ## What you implement 1. **Provider** — `provider.py`. Required. Declares the provider's class - metadata, wires up whatever managers it has, and exposes the public + members, wires up whatever managers it has, and exposes the public interface. 2. **Auth** — `auth.py`. Optional. Required only if the provider authenticates requests. Free / static-key providers can omit it @@ -31,15 +31,51 @@ and fill in the stubs. Read this file first — it explains the contract. 5. **Models** (optional) — `models.py`. Only if you need a custom Channel or AuthToken subclass. -## Provider class metadata +## Provider class members -Every provider declares these class attributes. They are used by the -registry and the UI before any instance is constructed. +Every provider declares these. Some are abstract (must be implemented by +the concrete class, or the class cannot be instantiated); some are class +attributes with defaults. - PROVIDER_LABEL: ClassVar[str] # display name, e.g. "simpliTV" - PROVIDER_LOGO: ClassVar[str] # logo URL - SUPPORTED_AUTH_TYPES: ClassVar[List[str]] # e.g. ["user_credentials"] - SUPPORTED_COUNTRIES: ClassVar[List[str]] # ALWAYS set this +### Required abstract property + + @property + def provider_name(self) -> str: ... + +**This is abstract on StreamingProvider.** A subclass that does not +override it cannot be instantiated — Python raises +`TypeError: Can't instantiate abstract class ... with abstract method +provider_name` the moment the registry calls `YourProvider(country=...)`. + +The failure is silent if you don't notice it: the registry catches the +exception, logs "Failed to create instance for {name}: ..." at ERROR +level, and moves on. The provider simply does not appear in the UI. If +your provider is registered but never instantiates, this is the first +thing to check. + +The value is the provider's machine identifier — lowercase, no spaces, +used in settings keys, log lines, and the `provider` field on models. +It should match the plugin directory name and the `PROVIDER_NAME` +constant in `constants.py`. + + @property + def provider_name(self) -> str: + return "your_provider_name" + +### Required class attributes + + PROVIDER_LABEL: ClassVar[str] + Display name, e.g. "simpliTV". Used by the registry and the UI. + + PROVIDER_LOGO: ClassVar[str] + Logo URL. + + SUPPORTED_AUTH_TYPES: ClassVar[List[str]] + e.g. ["user_credentials"], ["anonymous"], or ["user_credentials", + "anonymous"] if the provider supports both. + + SUPPORTED_COUNTRIES: ClassVar[List[str]] + ALWAYS set this. See "SUPPORTED_COUNTRIES is not optional" below. ### SUPPORTED_COUNTRIES is not optional @@ -79,6 +115,24 @@ wanting to leave it empty because "I'm not sure yet," declare `["*"]` instead — it's honest about the ambiguity and behaves correctly in both the registry and the runtime. +### Cross-check: what the registry reads + +The registry's `ProviderMetadata._extract_metadata` reads these members +before any instance exists. If any are missing or wrong, the provider +misbehaves in the UI even if the runtime works. + + PROVIDER_LABEL -> metadata.label + PROVIDER_LOGO -> metadata.logo + SUPPORTED_AUTH_TYPES -> metadata.supported_auth_types + SUPPORTED_COUNTRIES -> metadata.supported_countries + class.__name__ -> metadata.plugin_name (derived) + +The plugin name is derived from the class name: +`cls.__name__.lower().replace("provider", "")`. `YourProvider` becomes +`your`. If your class name is non-standard the derived name will be +wrong — pick a class name whose lowercase form (minus the word +"provider") matches your intended plugin name. + ## The manager ABCs There are **seven** manager ABCs. **All seven are optional.** A provider diff --git a/lib/streaming_providers/providers/_template/provider.py b/lib/streaming_providers/providers/_template/provider.py index 939ec2b..578975e 100644 --- a/lib/streaming_providers/providers/_template/provider.py +++ b/lib/streaming_providers/providers/_template/provider.py @@ -37,6 +37,36 @@ from .constants import YourConfig class YourProvider(StreamingProvider): """{TODO: provider name} streaming provider.""" + # ------------------------------------------------------------------ + # provider_name -- ABSTRACT, must be implemented + # ------------------------------------------------------------------ + # + # `provider_name` is declared as an @property @abstractmethod on + # StreamingProvider. If this class does not override it, Python + # raises TypeError at instantiation: + # + # Can't instantiate abstract class YourProvider with abstract + # method provider_name + # + # The registry catches that exception, logs it at ERROR level, and + # skips the provider. The symptom is a provider that is registered + # but never appears in the UI. + # + # The value is the machine identifier: lowercase, no spaces, matching + # the plugin directory name and the PROVIDER_NAME constant in + # constants.py. Used in settings keys, log lines, and the `provider` + # field on models. + # + # Do not delete this property. Override the return value; do not + # replace it with a class attribute. + @property + def provider_name(self) -> str: + return "TODO: provider_name" + + # ------------------------------------------------------------------ + # Class metadata + # ------------------------------------------------------------------ + PROVIDER_LABEL: ClassVar[str] = "TODO: display label" PROVIDER_LOGO: ClassVar[str] = "TODO: logo url" SUPPORTED_AUTH_TYPES: ClassVar[List[str]] = ["user_credentials"] @@ -75,7 +105,7 @@ class YourProvider(StreamingProvider): # 1. HTTP manager. self.http_manager = self._setup_http_manager( - provider_name="TODO: provider_name", + provider_name=self.provider_name, proxy_config=proxy_config, user_agent=self.config.user_agent, timeout=self.config.timeout,