From f3ebb14e463f4e896dbe5ebbff1b3ff906880f96 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 2 Jun 2026 16:10:43 +0200 Subject: [PATCH] feat(file-storage): add storage backend contract --- .../app/file_storage/backends/__init__.py | 7 +++++ .../app/file_storage/backends/base.py | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 surfsense_backend/app/file_storage/backends/__init__.py create mode 100644 surfsense_backend/app/file_storage/backends/base.py diff --git a/surfsense_backend/app/file_storage/backends/__init__.py b/surfsense_backend/app/file_storage/backends/__init__.py new file mode 100644 index 000000000..9e396f21c --- /dev/null +++ b/surfsense_backend/app/file_storage/backends/__init__.py @@ -0,0 +1,7 @@ +"""Storage backend implementations behind the shared :class:`StorageBackend`.""" + +from __future__ import annotations + +from app.file_storage.backends.base import StorageBackend + +__all__ = ["StorageBackend"] diff --git a/surfsense_backend/app/file_storage/backends/base.py b/surfsense_backend/app/file_storage/backends/base.py new file mode 100644 index 000000000..c65375e9e --- /dev/null +++ b/surfsense_backend/app/file_storage/backends/base.py @@ -0,0 +1,31 @@ +"""The storage backend contract: the minimal object-store surface we depend on.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import AsyncIterator + + +class StorageBackend(ABC): + """Maps an opaque object key to durable bytes.""" + + #: Identifier stored on each row to record which backend holds the bytes. + backend_name: str + + @abstractmethod + async def put( + self, key: str, data: bytes, *, content_type: str | None = None + ) -> None: + """Store ``data`` at ``key``, overwriting any existing object.""" + + @abstractmethod + def open_stream(self, key: str) -> AsyncIterator[bytes]: + """Yield the object's bytes in chunks. Raises if the key is absent.""" + + @abstractmethod + async def delete(self, key: str) -> None: + """Remove the object at ``key``; a missing key is not an error.""" + + @abstractmethod + async def exists(self, key: str) -> bool: + """Return whether an object is stored at ``key``."""