"""Local filesystem storage adapter.""" import os from pathlib import Path from .base import StorageAdapter, validate_key class LocalFileStorage(StorageAdapter): """Store objects as files under a root directory on the local filesystem.""" def __init__(self, root: str | Path) -> None: """Initialize the adapter with a root directory. Parameters ---------- root: str | Path The root directory under which all objects are stored. """ self._root = Path(root).expanduser().resolve() self._root.mkdir(parents=True, exist_ok=True) def _key_to_path(self, key: str) -> Path: """Resolve a storage key to a path inside the root directory. Raises ------ ValueError If the resolved path would escape the root directory. """ path = (self._root / key).resolve() if self._root not in path.parents and path != self._root: raise ValueError(f"Resolved path {path} escapes root {self._root}") return path def put(self, key: str, data: bytes) -> None: """Store ``data`` under ``key`` atomically. Writes to a temporary file in the target directory and renames it into place. Parent directories are created as needed. """ validate_key(key) path = self._key_to_path(key) path.parent.mkdir(parents=True, exist_ok=True) tmp_path = path.parent / f".{path.name}.tmp-{os.getpid()}" try: tmp_path.write_bytes(data) os.replace(str(tmp_path), str(path)) finally: if tmp_path.exists(): tmp_path.unlink() def get(self, key: str) -> bytes: """Return the bytes stored under ``key``. Raises ------ KeyError If the file does not exist. """ validate_key(key) path = self._key_to_path(key) if not path.is_file(): raise KeyError(key) return path.read_bytes() def list_keys(self, prefix: str = "") -> list[str]: """Return a sorted list of file keys starting with ``prefix``.""" if prefix: validate_key(prefix) keys = [] for item in self._root.rglob("*"): if item.is_file(): rel_key = item.relative_to(self._root).as_posix() if rel_key.startswith(prefix): keys.append(rel_key) return sorted(keys) def delete(self, key: str) -> None: """Delete the file at ``key``. Idempotent: no error if the file is already missing. """ validate_key(key) path = self._key_to_path(key) try: path.unlink() except FileNotFoundError: return def exists(self, key: str) -> bool: """Return whether ``key`` exists as a regular file.""" validate_key(key) path = self._key_to_path(key) return path.is_file()