registry.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. """Provider registry — maps ``source_type`` / URLs to model providers.
  2. The registry is the routing layer a future *shared* import API uses: a pasted
  3. URL goes through :meth:`ModelProviderRegistry.find_for_url`, which asks each
  4. registered provider ``supports_url`` and returns the one that owns it. Today
  5. the route layer still calls the MakerWorld provider directly (endpoints stay
  6. at ``/makerworld/*``), but registering providers here keeps the seam ready.
  7. """
  8. from __future__ import annotations
  9. from typing import TYPE_CHECKING
  10. if TYPE_CHECKING:
  11. from backend.app.services.model_providers.base import ModelProvider
  12. class ModelProviderRegistry:
  13. """Holds the registered :class:`ModelProvider` instances.
  14. Registering is idempotent per provider instance; registering a *different*
  15. provider under an already-taken ``source_type`` is an error.
  16. """
  17. def __init__(self) -> None:
  18. self._providers: dict[str, ModelProvider] = {}
  19. def register(self, provider: ModelProvider) -> None:
  20. existing = self._providers.get(provider.source_type)
  21. if existing is not None and existing is not provider:
  22. raise ValueError(f"A model provider for source_type {provider.source_type!r} is already registered")
  23. self._providers[provider.source_type] = provider
  24. def get(self, source_type: str) -> ModelProvider:
  25. try:
  26. return self._providers[source_type]
  27. except KeyError as exc:
  28. raise KeyError(f"No model provider registered for source_type {source_type!r}") from exc
  29. def all(self) -> tuple[ModelProvider, ...]:
  30. return tuple(self._providers.values())
  31. def find_for_url(self, url: str) -> ModelProvider | None:
  32. """Return the provider that claims ``url``, or ``None`` if none do.
  33. Iterates in registration (dict insertion) order; when more than one
  34. provider ``supports_url`` the *first registered* one wins. Providers
  35. overlap rarely (``host_patterns`` are usually disjoint), so this
  36. tie-break is documented rather than policed — a "generic" provider
  37. must register after the specific ones it might shadow.
  38. """
  39. for provider in self._providers.values():
  40. if provider.supports_url(url):
  41. return provider
  42. return None
  43. # App-wide registry. Providers register themselves on package import (see
  44. # ``backend/app/services/model_providers/__init__.py``).
  45. registry = ModelProviderRegistry()