generated from coulomb/repo-seed
content-addressed blob storage: blob_storage.py, memory, local, and S3 adapters
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
"""Stable ports owned by the engine."""
|
||||
|
||||
from .blob_storage import (
|
||||
BlobCleanupResult,
|
||||
BlobRef,
|
||||
BlobStorage,
|
||||
BlobWriteResult,
|
||||
blob_digest,
|
||||
digest_storage_key,
|
||||
)
|
||||
from .ingestion import DirectorySourceConnector, FormatExtractor, SourceConnector
|
||||
from .policy import AllowAllPolicyGateway, PolicyGateway
|
||||
from .repositories import AssetRegistryRepository
|
||||
@@ -7,6 +15,12 @@ from .repositories import AssetRegistryRepository
|
||||
__all__ = [
|
||||
"AllowAllPolicyGateway",
|
||||
"AssetRegistryRepository",
|
||||
"BlobCleanupResult",
|
||||
"BlobRef",
|
||||
"BlobStorage",
|
||||
"BlobWriteResult",
|
||||
"blob_digest",
|
||||
"digest_storage_key",
|
||||
"DirectorySourceConnector",
|
||||
"FormatExtractor",
|
||||
"PolicyGateway",
|
||||
|
||||
95
src/kontextual_engine/ports/blob_storage.py
Normal file
95
src/kontextual_engine/ports/blob_storage.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Blob storage port and content-addressed reference models."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from collections.abc import Iterator
|
||||
from typing import Protocol
|
||||
|
||||
from kontextual_engine.core import content_digest
|
||||
from kontextual_engine.errors import ValidationError
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BlobRef:
|
||||
digest: str
|
||||
size_bytes: int
|
||||
storage_key: str
|
||||
storage_ref: str
|
||||
adapter: str
|
||||
media_type: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, object]:
|
||||
data: dict[str, object] = {
|
||||
"digest": self.digest,
|
||||
"size_bytes": self.size_bytes,
|
||||
"storage_key": self.storage_key,
|
||||
"storage_ref": self.storage_ref,
|
||||
"adapter": self.adapter,
|
||||
}
|
||||
if self.media_type:
|
||||
data["media_type"] = self.media_type
|
||||
return data
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BlobWriteResult:
|
||||
blob: BlobRef
|
||||
created: bool
|
||||
|
||||
def to_dict(self) -> dict[str, object]:
|
||||
return {"blob": self.blob.to_dict(), "created": self.created}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BlobCleanupResult:
|
||||
dry_run: bool
|
||||
deleted_count: int
|
||||
retained_count: int
|
||||
reclaimable_bytes: int
|
||||
deleted_storage_refs: tuple[str, ...] = ()
|
||||
|
||||
def to_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"dry_run": self.dry_run,
|
||||
"deleted_count": self.deleted_count,
|
||||
"retained_count": self.retained_count,
|
||||
"reclaimable_bytes": self.reclaimable_bytes,
|
||||
"deleted_storage_refs": list(self.deleted_storage_refs),
|
||||
}
|
||||
|
||||
|
||||
class BlobStorage(Protocol):
|
||||
adapter_name: str
|
||||
|
||||
def put_bytes(self, content: bytes, *, media_type: str | None = None) -> BlobWriteResult: ...
|
||||
|
||||
def read_bytes(self, storage_ref: str) -> bytes: ...
|
||||
|
||||
def iter_bytes(self, storage_ref: str, *, chunk_size: int = 65536) -> Iterator[bytes]: ...
|
||||
|
||||
def stat(self, storage_ref: str) -> BlobRef: ...
|
||||
|
||||
def exists(self, storage_ref_or_digest: str) -> bool: ...
|
||||
|
||||
def iter_blobs(self) -> list[BlobRef]: ...
|
||||
|
||||
def delete_unreferenced(
|
||||
self,
|
||||
referenced_storage_refs: set[str],
|
||||
*,
|
||||
dry_run: bool = True,
|
||||
) -> BlobCleanupResult: ...
|
||||
|
||||
|
||||
def blob_digest(content: bytes) -> str:
|
||||
return content_digest(content)
|
||||
|
||||
|
||||
def digest_storage_key(digest: str) -> str:
|
||||
if not digest.startswith("sha256:"):
|
||||
raise ValidationError("Unsupported blob digest", details={"digest": digest})
|
||||
value = digest.removeprefix("sha256:")
|
||||
if len(value) < 4:
|
||||
raise ValidationError("Invalid blob digest", details={"digest": digest})
|
||||
return f"sha256/{value[:2]}/{value[2:4]}/{value}"
|
||||
Reference in New Issue
Block a user