betty.assets - 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.assets

    """
    The Assets API.
    """

    from __future__ import annotations

    from asyncio import to_thread
    from os import walk
    from pathlib import Path
    from typing import Sequence, TYPE_CHECKING

    from betty.concurrent import AsynchronizedLock

    if TYPE_CHECKING:
        from collections.abc import Mapping, AsyncIterator

    class AssetRepository:
        """
        Manages a set of assets.

        This repository unifies several directory paths on disk, overlaying them on
        each other. Paths added later act as fallbacks, e.g. earlier paths have priority.
        """

        def __init__(self, *assets_directory_paths: Path):
            """
            :param assets_directory_paths: Earlier paths have priority over later paths.
            """
            self._assets_directory_paths = assets_directory_paths
            self.__assets: Mapping[Path, Path] | None = None
            self._lock = AsynchronizedLock.threading()

        async def _assets(self) -> Mapping[Path, Path]:
            if self.__assets is None:
                async with self._lock:
                    self.__assets = await to_thread(self._init_assets)
            return self.__assets

        def _init_assets(self) -> Mapping[Path, Path]:
            return {
                (Path(directory_path) / file_name).relative_to(assets_directory_path): Path(
                    directory_path
                )
                / file_name
                for assets_directory_path in reversed(self._assets_directory_paths)
                for directory_path, _, file_names in walk(assets_directory_path)
                for file_name in file_names
            }

        @property
        def assets_directory_paths(self) -> Sequence[Path]:
            """
            The paths to the individual virtual layers.
            """
            return self._assets_directory_paths

        async def walk(
            self, asset_directory_path: Path | None = None
        ) -> AsyncIterator[Path]:
            """
            Get virtual paths to available assets.

            :param asset_directory_path: If given, only asses under the directory are returned.
            """
            asset_directory_path_str = str(asset_directory_path)
            for asset_path in await self._assets():
                if asset_directory_path is None or str(asset_path).startswith(
                    asset_directory_path_str
                ):
                    yield asset_path

        async def get(self, path: Path) -> Path:
            """
            Get the path to a single asset file.

            :param path: The virtual asset path.
            :return: The path to the actual file on disk.
            """
            return (await self._assets())[path]