betty.plugin.proxy - Betty documentation

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.plugin.proxy

    """
    Provide tools for proxying plugin management to other tools.
    """

    from collections.abc import AsyncIterator
    from contextlib import suppress
    from typing import Generic, TypeVar, final

    from typing_extensions import override, overload

    from betty.factory import Factory, FactoryError
    from betty.machine_name import MachineName
    from betty.plugin import PluginRepository, Plugin, PluginNotFound

    _T = TypeVar("_T")
    _PluginT = TypeVar("_PluginT", bound=Plugin)

    @final
    class ProxyPluginRepository(PluginRepository[_PluginT], Generic[_PluginT]):
        """
        Expose multiple other plugin repositories as one unified repository.
        """

        def __init__(
            self,
            *upstreams: PluginRepository[_PluginT],
            factory: Factory | None = None,
        ):
            super().__init__(factory=factory)
            self._upstreams = upstreams

        @overload
        async def new_target(self, cls: type[_T]) -> _T:
            pass

        @overload
        async def new_target(self, cls: MachineName) -> _PluginT:
            pass

        @override
        async def new_target(self, cls: type[_T] | MachineName) -> _T | _PluginT:
            with suppress(FactoryError):
                return await super().new_target(cls)
            resolved_cls = await self.get(cls) if isinstance(cls, str) else cls
            for upstream in self._upstreams:
                if issubclass(resolved_cls, Plugin):
                    try:
                        await upstream.get(resolved_cls.plugin_id())
                    except PluginNotFound:
                        continue
                with suppress(PluginNotFound, FactoryError):
                    return await upstream.new_target(resolved_cls)
            raise FactoryError()

        @override
        async def get(self, plugin_id: MachineName) -> type[_PluginT]:
            for upstream in self._upstreams:
                try:
                    return await upstream.get(plugin_id)
                except PluginNotFound:
                    pass
            raise PluginNotFound.new(plugin_id, await self.select()) from None

        @override
        async def __aiter__(self) -> AsyncIterator[type[_PluginT]]:
            seen = set()
            for upstream in self._upstreams:
                async for plugin in upstream:
                    if plugin.plugin_id() not in seen:
                        seen.add(plugin.plugin_id())
                        yield plugin