provider.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. """Manyfold model provider (#1471).
  2. Manyfold is a self-hosted library, so the provider has no fixed host and
  3. claims no pasted URLs: models are browsed and searched on the Manyfold tab of
  4. the Model Sources page, not pasted. The descriptor still carries what every
  5. provider declares (identity, permissions, import folder) and builds the
  6. per-request service from the stored connection.
  7. """
  8. from __future__ import annotations
  9. from typing import TYPE_CHECKING
  10. import httpx
  11. from backend.app.core.permissions import Permission
  12. from backend.app.services.model_providers.base import (
  13. ModelProvider,
  14. ProviderAuthConfig,
  15. ProviderAuthType,
  16. ProviderResourceRef,
  17. ProviderService,
  18. )
  19. from backend.app.services.model_providers.manyfold.config import load_config
  20. from backend.app.services.model_providers.manyfold.service import ManyfoldService, ManyfoldUnavailableError
  21. if TYPE_CHECKING:
  22. from sqlalchemy.ext.asyncio import AsyncSession
  23. from backend.app.models.user import User
  24. class ManyfoldProvider(ModelProvider):
  25. source_type = "manyfold"
  26. display_name = "Manyfold"
  27. host_patterns = ()
  28. auth = ProviderAuthConfig(
  29. auth_type=ProviderAuthType.ACCESS_TOKEN,
  30. display_label="Manyfold OAuth application",
  31. description="Bambuddy signs in with the client ID and secret of an OAuth application created in Manyfold.",
  32. credential_fields=("url", "client_id", "client_secret"),
  33. setup_hint="In Manyfold, open Settings -> API and create an application with the 'public read' scopes.",
  34. )
  35. default_folder_name = "Manyfold"
  36. view_permission = Permission.MANYFOLD_VIEW
  37. import_permission = Permission.MANYFOLD_IMPORT
  38. async def build_service(
  39. self,
  40. *,
  41. db: AsyncSession,
  42. user: User | None,
  43. api_key_owner: User | None = None,
  44. client: httpx.AsyncClient | None = None,
  45. ) -> ProviderService:
  46. # One connection for the whole install: the OAuth application's owner
  47. # in Manyfold decides what Bambuddy can see, not the Bambuddy user.
  48. return ManyfoldService(await load_config(db), client=client)
  49. def parse_url(self, url: str) -> ProviderResourceRef:
  50. raise ManyfoldUnavailableError("Manyfold models are browsed, not pasted")
  51. def canonical_url(self, ref: ProviderResourceRef) -> str:
  52. """``manyfold:<model>/<file>`` names one file of one model.
  53. Deliberately not the install's URL: moving Manyfold to another address
  54. must not make every earlier import look new.
  55. """
  56. if ref.sub_id:
  57. return f"manyfold:{ref.external_id}/{ref.sub_id}"
  58. return f"manyfold:{ref.external_id}"
  59. def source_url_filter(self, column, external_id: str):
  60. # Ids may contain "_", which LIKE would read as "any character".
  61. escaped = external_id.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
  62. return column.like(f"manyfold:{escaped}/%", escape="\\")
  63. manyfold_provider = ManyfoldProvider()