111 lines
3.0 KiB
Python
111 lines
3.0 KiB
Python
"""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
|