"""Abstract storage interface for pytcl data persistence.
This module provides interfaces for storing and retrieving pytcl data
in different formats (HDF5, SQL, etc.).
"""
from abc import ABC, abstractmethod
from types import TracebackType
from typing import Any, Dict, List, Optional, Union
from numpy.typing import ArrayLike, NDArray
[docs]
class StorageBackend(ABC):
"""Abstract base class for storage backends.
Provides a unified interface for storing and retrieving arrays,
metadata, and structured data in various formats.
"""
[docs]
@abstractmethod
def open(self, path: str, mode: str = "r") -> None:
"""Open a storage file or database connection.
Parameters
----------
path : str
Path to storage file or database URI
mode : str, optional
Open mode: 'r' (read), 'w' (write), 'a' (append). Default is 'r'.
"""
pass
[docs]
@abstractmethod
def close(self) -> None:
"""Close the storage connection."""
pass
[docs]
@abstractmethod
def __enter__(self) -> "StorageBackend":
"""Context manager entry."""
return self
[docs]
@abstractmethod
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""Context manager exit."""
pass
[docs]
@abstractmethod
def store_array(
self,
name: str,
data: ArrayLike,
metadata: Optional[Dict[str, Any]] = None,
) -> None:
"""Store a numpy array, replacing any array already under that name.
Parameters
----------
name : str
Dataset name/key for storage
data : ArrayLike
Numpy array to store
metadata : dict, optional
Associated metadata (e.g., units, description). Replaced wholesale
along with the array; metadata from a previous store under the same
name does not survive.
Notes
-----
The replace-on-collision rule is stated here because the backends used
to disagree and neither said so: ``SQLStorage`` replaced, while
``HDF5Storage`` let h5py raise ``ValueError`` on an existing name
(gh-21). Code written against one backend broke on the other, and the
base class -- the only place the contract could live -- was silent.
"""
pass
[docs]
@abstractmethod
def retrieve_array(self, name: str) -> NDArray[Any]:
"""Retrieve a stored numpy array.
Parameters
----------
name : str
Dataset name/key
Returns
-------
ndarray
The stored array
"""
pass
[docs]
@abstractmethod
def store_scalar(
self,
name: str,
value: Union[int, float, str, bool],
metadata: Optional[Dict[str, Any]] = None,
) -> None:
"""Store a scalar value, replacing any scalar already under that name.
Parameters
----------
name : str
Scalar name/key
value : int, float, str, bool
Scalar value to store
metadata : dict, optional
Associated metadata. Replaced wholesale along with the value.
Notes
-----
Same replace-on-collision contract as `store_array`, and stated here
for the same reason: the gh-21 fix that unified the array path was
never applied to the scalar path, so ``SQLStorage`` replaced while
``HDF5Storage`` let h5py raise ``ValueError`` on an existing name --
the identical divergence, one method down.
"""
pass
[docs]
@abstractmethod
def retrieve_scalar(self, name: str) -> Union[int, float, str, bool]:
"""Retrieve a stored scalar value.
Parameters
----------
name : str
Scalar name/key
Returns
-------
Scalar value
"""
pass
[docs]
@abstractmethod
def store_group(self, name: str, metadata: Optional[Dict[str, Any]] = None) -> None:
"""Create a logical group/container for related data.
Parameters
----------
name : str
Group name
metadata : dict, optional
Group-level metadata
"""
pass
[docs]
@abstractmethod
def list_keys(self, group: str = "/") -> List[str]:
"""List all stored keys/datasets in a group.
Parameters
----------
group : str, optional
Group path. Default is root ("/")
Returns
-------
list of str
Keys in the group
"""
pass
[docs]
@abstractmethod
def delete(self, name: str) -> None:
"""Delete a dataset or group.
Parameters
----------
name : str
Dataset/group name to delete
"""
pass
[docs]
@abstractmethod
def flush(self) -> None:
"""Ensure all data is written to disk."""
pass