feat: prepare ARR for controlled public deployment
This commit is contained in:
110
arr_storage/remote.py
Normal file
110
arr_storage/remote.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""Narrow adapter seam for an OSS or S3-compatible provider SDK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import BinaryIO, Mapping, Protocol
|
||||
|
||||
from arr_storage.contracts import BackendError, BackendObject, ObjectBackend
|
||||
|
||||
|
||||
class CloudClientError(RuntimeError):
|
||||
"""Provider adapter error classified without retaining a raw provider message."""
|
||||
|
||||
def __init__(self, kind: str):
|
||||
super().__init__(kind)
|
||||
self.kind = kind
|
||||
|
||||
|
||||
class CloudClientPort(Protocol):
|
||||
"""Implement this port with the selected OSS SDK or S3-compatible SDK."""
|
||||
|
||||
def upload_file_if_absent(
|
||||
self,
|
||||
object_key: str,
|
||||
source: str,
|
||||
mime_type: str,
|
||||
metadata: Mapping[str, str],
|
||||
) -> BackendObject:
|
||||
...
|
||||
|
||||
def stat_object(self, object_key: str) -> BackendObject:
|
||||
...
|
||||
|
||||
def stream_object(self, object_key: str) -> BinaryIO:
|
||||
...
|
||||
|
||||
def copy_object_if_absent(
|
||||
self,
|
||||
source_key: str,
|
||||
destination_key: str,
|
||||
metadata: Mapping[str, str],
|
||||
) -> BackendObject:
|
||||
...
|
||||
|
||||
def delete_exact_object(self, object_key: str) -> None:
|
||||
...
|
||||
|
||||
|
||||
class CloudObjectBackend(ObjectBackend):
|
||||
"""Maps a provider-specific client port onto the guarded backend protocol."""
|
||||
|
||||
def __init__(self, client: CloudClientPort) -> None:
|
||||
self._client = client
|
||||
|
||||
def put_file(
|
||||
self,
|
||||
object_key: str,
|
||||
source: str,
|
||||
mime_type: str,
|
||||
metadata: Mapping[str, str],
|
||||
*,
|
||||
if_absent: bool,
|
||||
) -> BackendObject:
|
||||
if not if_absent:
|
||||
raise BackendError("invalid_request")
|
||||
try:
|
||||
return self._client.upload_file_if_absent(
|
||||
object_key,
|
||||
source,
|
||||
mime_type,
|
||||
metadata,
|
||||
)
|
||||
except CloudClientError as error:
|
||||
raise BackendError(error.kind) from None
|
||||
|
||||
def head(self, object_key: str) -> BackendObject:
|
||||
try:
|
||||
return self._client.stat_object(object_key)
|
||||
except CloudClientError as error:
|
||||
raise BackendError(error.kind) from None
|
||||
|
||||
def open_reader(self, object_key: str) -> BinaryIO:
|
||||
try:
|
||||
return self._client.stream_object(object_key)
|
||||
except CloudClientError as error:
|
||||
raise BackendError(error.kind) from None
|
||||
|
||||
def copy_object(
|
||||
self,
|
||||
source_key: str,
|
||||
destination_key: str,
|
||||
metadata: Mapping[str, str],
|
||||
*,
|
||||
if_absent: bool,
|
||||
) -> BackendObject:
|
||||
if not if_absent:
|
||||
raise BackendError("invalid_request")
|
||||
try:
|
||||
return self._client.copy_object_if_absent(
|
||||
source_key,
|
||||
destination_key,
|
||||
metadata,
|
||||
)
|
||||
except CloudClientError as error:
|
||||
raise BackendError(error.kind) from None
|
||||
|
||||
def delete_object(self, object_key: str) -> None:
|
||||
try:
|
||||
self._client.delete_exact_object(object_key)
|
||||
except CloudClientError as error:
|
||||
raise BackendError(error.kind) from None
|
||||
Reference in New Issue
Block a user