Skip to content

Storage

Base

pluginlake.core.storage.base

Base class for pluggable storage backends.

StorageBackend

Bases: ABC

Abstract base class for storage backends.

Storage backends define where DuckLake stores its data files and how DuckDB connects to that storage. Implementations handle local filesystems, cloud object stores, and other storage systems.

Each backend provides
  • A base path for DuckLake file storage (DATA_PATH).
  • DuckDB extension configuration for accessing the storage.
Source code in src/pluginlake/core/storage/base.py
class StorageBackend(ABC):
    """Abstract base class for storage backends.

    Storage backends define where DuckLake stores its data files and
    how DuckDB connects to that storage. Implementations handle local
    filesystems, cloud object stores, and other storage systems.

    Each backend provides:
        - A base path for DuckLake file storage (DATA_PATH).
        - DuckDB extension configuration for accessing the storage.
    """

    @abstractmethod
    def get_base_path(self) -> str:
        """Return the root path for file storage.

        For local storage, this is a filesystem path.
        For cloud storage, this is a URI (e.g., ``s3://bucket/prefix/``).
        Used as DuckLake's DATA_PATH.
        """

    @abstractmethod
    def configure_duckdb(self, conn: duckdb.DuckDBPyConnection) -> None:
        """Configure a DuckDB connection to access this storage.

        Install and load any required extensions, set credentials,
        and apply any other necessary configuration.

        Args:
            conn: An open DuckDB connection to configure.
        """

get_base_path() abstractmethod

Return the root path for file storage.

For local storage, this is a filesystem path. For cloud storage, this is a URI (e.g., s3://bucket/prefix/). Used as DuckLake's DATA_PATH.

Source code in src/pluginlake/core/storage/base.py
@abstractmethod
def get_base_path(self) -> str:
    """Return the root path for file storage.

    For local storage, this is a filesystem path.
    For cloud storage, this is a URI (e.g., ``s3://bucket/prefix/``).
    Used as DuckLake's DATA_PATH.
    """

configure_duckdb(conn) abstractmethod

Configure a DuckDB connection to access this storage.

Install and load any required extensions, set credentials, and apply any other necessary configuration.

Parameters:

Name Type Description Default
conn DuckDBPyConnection

An open DuckDB connection to configure.

required
Source code in src/pluginlake/core/storage/base.py
@abstractmethod
def configure_duckdb(self, conn: duckdb.DuckDBPyConnection) -> None:
    """Configure a DuckDB connection to access this storage.

    Install and load any required extensions, set credentials,
    and apply any other necessary configuration.

    Args:
        conn: An open DuckDB connection to configure.
    """

Local Backend

pluginlake.core.storage.local

Local filesystem storage backend.

LocalStorageBackend

Bases: StorageBackend

Storage backend that uses the local filesystem.

Parameters:

Name Type Description Default
base_path str | Path

Root directory for file storage. Created automatically if it does not exist.

required
Source code in src/pluginlake/core/storage/local.py
class LocalStorageBackend(StorageBackend):
    """Storage backend that uses the local filesystem.

    Args:
        base_path: Root directory for file storage.
            Created automatically if it does not exist.
    """

    def __init__(self, base_path: str | Path) -> None:
        """Initialise with the root directory for file storage."""
        self._base_path = Path(base_path).resolve()
        self._base_path.mkdir(parents=True, exist_ok=True)

    def get_base_path(self) -> str:
        """Return the absolute path to the storage root directory."""
        return str(self._base_path)

    def configure_duckdb(self, conn: duckdb.DuckDBPyConnection) -> None:
        """No extra configuration needed for local filesystem access."""

__init__(base_path)

Initialise with the root directory for file storage.

Source code in src/pluginlake/core/storage/local.py
def __init__(self, base_path: str | Path) -> None:
    """Initialise with the root directory for file storage."""
    self._base_path = Path(base_path).resolve()
    self._base_path.mkdir(parents=True, exist_ok=True)

get_base_path()

Return the absolute path to the storage root directory.

Source code in src/pluginlake/core/storage/local.py
def get_base_path(self) -> str:
    """Return the absolute path to the storage root directory."""
    return str(self._base_path)

configure_duckdb(conn)

No extra configuration needed for local filesystem access.

Source code in src/pluginlake/core/storage/local.py
def configure_duckdb(self, conn: duckdb.DuckDBPyConnection) -> None:
    """No extra configuration needed for local filesystem access."""

Layer Manager

pluginlake.core.storage.layers

Storage layer manager for the medallion architecture.

Provides a high-level interface for working with the raw / processed / output storage layers on top of a :class:~pluginlake.core.storage.base.StorageBackend.

StorageLayerManager

Manages the medallion-architecture directory structure.

Wraps a :class:StorageBackend and the corresponding :class:StorageSettings to provide layer-aware path resolution and directory initialisation.

Parameters:

Name Type Description Default
settings StorageSettings | None

Storage configuration with layer paths.

None
backend StorageBackend | None

Optional storage backend override. When None, the backend is resolved from settings.backend.

None

Raises:

Type Description
ValueError

If the configured storage backend is not supported.

Source code in src/pluginlake/core/storage/layers.py
class StorageLayerManager:
    """Manages the medallion-architecture directory structure.

    Wraps a :class:`StorageBackend` and the corresponding
    :class:`StorageSettings` to provide layer-aware path resolution
    and directory initialisation.

    Args:
        settings: Storage configuration with layer paths.
        backend: Optional storage backend override.  When *None*, the
            backend is resolved from ``settings.backend``.

    Raises:
        ValueError: If the configured storage backend is not supported.
    """

    def __init__(
        self,
        settings: StorageSettings | None = None,
        backend: StorageBackend | None = None,
    ) -> None:
        """Initialise with storage settings and an optional backend override."""
        self._settings = settings or StorageSettings()
        self._backend = backend or self._resolve_backend()

    # -- Public API ----------------------------------------------------------

    def initialize(self) -> None:
        """Create **all** storage layer directories.

        Idempotent — safe to call multiple times.
        """
        self._settings.ensure_directories()
        logger.info(
            "Storage layers initialised under %s: %s",
            self._settings.base_dir,
            [layer.value for layer in StorageLayer],
        )

    def get_path(self, layer: StorageLayer, *parts: str) -> Path:
        """Return a path within a specific storage layer.

        Args:
            layer: Target storage layer.
            *parts: Additional path segments appended after the layer dir.

        Returns:
            Absolute path for the requested resource.
        """
        return self._settings.get_layer_path(layer, *parts)

    @property
    def raw_dir(self) -> Path:
        """Shortcut to the raw (bronze) layer directory."""
        return self._settings.raw_dir

    @property
    def processed_dir(self) -> Path:
        """Shortcut to the processed (silver) layer directory."""
        return self._settings.processed_dir

    @property
    def output_dir(self) -> Path:
        """Shortcut to the output (gold) layer directory."""
        return self._settings.output_dir

    @property
    def backend(self) -> StorageBackend:
        """The underlying storage backend."""
        return self._backend

    @property
    def settings(self) -> StorageSettings:
        """The active storage settings."""
        return self._settings

    # -- Internal ------------------------------------------------------------

    def _resolve_backend(self) -> StorageBackend:
        """Instantiate the storage backend from settings."""
        kind = self._settings.backend.lower()
        if kind == "local":
            return LocalStorageBackend(self._settings.base_dir)
        msg = f"Unsupported storage backend: {kind!r}. Supported: 'local'."
        raise ValueError(msg)

raw_dir property

Shortcut to the raw (bronze) layer directory.

processed_dir property

Shortcut to the processed (silver) layer directory.

output_dir property

Shortcut to the output (gold) layer directory.

backend property

The underlying storage backend.

settings property

The active storage settings.

__init__(settings=None, backend=None)

Initialise with storage settings and an optional backend override.

Source code in src/pluginlake/core/storage/layers.py
def __init__(
    self,
    settings: StorageSettings | None = None,
    backend: StorageBackend | None = None,
) -> None:
    """Initialise with storage settings and an optional backend override."""
    self._settings = settings or StorageSettings()
    self._backend = backend or self._resolve_backend()

initialize()

Create all storage layer directories.

Idempotent — safe to call multiple times.

Source code in src/pluginlake/core/storage/layers.py
def initialize(self) -> None:
    """Create **all** storage layer directories.

    Idempotent — safe to call multiple times.
    """
    self._settings.ensure_directories()
    logger.info(
        "Storage layers initialised under %s: %s",
        self._settings.base_dir,
        [layer.value for layer in StorageLayer],
    )

get_path(layer, *parts)

Return a path within a specific storage layer.

Parameters:

Name Type Description Default
layer StorageLayer

Target storage layer.

required
*parts str

Additional path segments appended after the layer dir.

()

Returns:

Type Description
Path

Absolute path for the requested resource.

Source code in src/pluginlake/core/storage/layers.py
def get_path(self, layer: StorageLayer, *parts: str) -> Path:
    """Return a path within a specific storage layer.

    Args:
        layer: Target storage layer.
        *parts: Additional path segments appended after the layer dir.

    Returns:
        Absolute path for the requested resource.
    """
    return self._settings.get_layer_path(layer, *parts)