Source code for model_repository.storage

"""Read-only storage implementations for simulation model files."""

import io
from pathlib import Path, PurePosixPath
from typing import BinaryIO, Protocol

from simtools.model_repository.git_backend import Pygit2ObjectStore


[docs] class ModelStorage(Protocol): """Common read-only interface for model repository storage.""" name: str config: dict
[docs] def read_bytes(self, path: str) -> bytes: """Read a repository-relative file as bytes.""" ...
[docs] def open(self, path: str) -> BinaryIO: """Open a repository-relative file as a binary stream.""" ...
[docs] def iter_files(self, prefix: str) -> list[str]: """List repository-relative files below a directory prefix.""" ...
[docs] def exists(self, path: str) -> bool: """Return whether a repository-relative file exists.""" ...
def _relative_path(path: str) -> PurePosixPath: """Validate and convert a repository-relative POSIX path.""" relative = PurePosixPath(path) if relative.is_absolute() or ".." in relative.parts: raise ValueError(f"Model storage path must be repository-relative: {path!r}") return relative
[docs] class DirectoryStorage: """Read model files from a directory tree.""" def __init__(self, root): """Create storage rooted at a model checkout directory.""" self.root = Path(root).expanduser().resolve() self.name = str(self.root) self.config = {"type": "filesystem", "path": str(self.root)} def _path(self, path): return self.root.joinpath(*_relative_path(path).parts)
[docs] def read_bytes(self, path): """Read a repository-relative file as bytes.""" return self._path(path).read_bytes()
[docs] def open(self, path): """Open a repository-relative file as a binary stream.""" return self._path(path).open("rb")
[docs] def iter_files(self, prefix): """List repository-relative files below a directory prefix.""" directory = self._path(prefix) return sorted( path.relative_to(self.root).as_posix() for path in directory.rglob("*") if path.is_file() )
[docs] def exists(self, path): """Return whether a repository-relative file exists.""" return self._path(path).is_file()
[docs] class GitStorage: """Read model files from a resolved commit in a local Git repository.""" def __init__(self, repository, revision, object_store=None): """Create storage for one revision of a local repository.""" self.repository = Path(repository).expanduser().resolve() self.object_store = object_store or Pygit2ObjectStore(self.repository) self.revision = self.object_store.resolve_revision(revision) self.name = f"{self.repository}@{self.revision}" self.config = { "type": "git", "repository": str(self.repository), "commit": self.revision, }
[docs] def read_bytes(self, path): """Read a repository-relative file as bytes.""" return self.object_store.read_blob(self.revision, _relative_path(path).as_posix())
[docs] def open(self, path): """Open a repository-relative file as a binary stream.""" return self.object_store.open_blob(self.revision, _relative_path(path).as_posix())
[docs] def iter_files(self, prefix): """List repository-relative files below a directory prefix.""" return self.object_store.iter_files(self.revision, _relative_path(prefix).as_posix())
[docs] def exists(self, path): """Return whether a repository-relative file exists (directories are not files).""" relative = _relative_path(path).as_posix() try: self.object_store.read_blob(self.revision, relative) except FileNotFoundError: return False except ValueError: # the path names a tree (directory), not a blob return False return True
[docs] class MemoryStorage: """Read model files from an in-memory mapping of paths to bytes.""" def __init__(self, files: dict[str, bytes]): """Create storage from repository-relative paths and byte contents.""" self.files = {_relative_path(path).as_posix(): data for path, data in files.items()} self.name = "memory" self.config = {"type": "memory", "files": self.files}
[docs] def read_bytes(self, path): """Read a repository-relative file as bytes.""" relative = _relative_path(path).as_posix() try: return self.files[relative] except KeyError as exc: raise FileNotFoundError(f"Model file not found: {relative}") from exc
[docs] def open(self, path): """Open a repository-relative file as a binary stream.""" return io.BytesIO(self.read_bytes(path))
[docs] def iter_files(self, prefix): """List repository-relative files below a directory prefix.""" directory = _relative_path(prefix).as_posix() if directory == ".": return sorted(self.files) return sorted(path for path in self.files if path.startswith(directory.rstrip("/") + "/"))
[docs] def exists(self, path): """Return whether a repository-relative file exists.""" return _relative_path(path).as_posix() in self.files