review
This commit is contained in:
@@ -165,17 +165,15 @@ async def create_upload_record(
|
||||
upload = existing
|
||||
else:
|
||||
upload_id = new_ulid()
|
||||
file_name = safe_file_name(payload.file_name)
|
||||
bucket_name = (
|
||||
workspace.artifact_bucket or request.app.state.default_bucket
|
||||
)
|
||||
# Default layout: one top-level folder per workspace inside the
|
||||
# ``workspaces`` bucket. ``bucket_name`` already encodes the workspace
|
||||
# namespace, so the key starts with the workspace id directly.
|
||||
object_key = (
|
||||
f"{payload.workspace_id}/"
|
||||
f"{payload.usage_type}/{upload_id}/{file_name}"
|
||||
)
|
||||
# Object key is a flat two-level path: workspace id + upload id. The
|
||||
# original file name and content type live in the StorageObjects row
|
||||
# (file_name / mime_type / object_key) — they are not part of the
|
||||
# key itself, so the bucket can be reorganised without rewriting
|
||||
# the database.
|
||||
object_key = f"{payload.workspace_id}/{upload_id}"
|
||||
upload = UploadSessions(
|
||||
upload_id=upload_id,
|
||||
workspace_id=payload.workspace_id,
|
||||
@@ -320,7 +318,11 @@ async def complete_upload_record(
|
||||
status.HTTP_409_CONFLICT,
|
||||
"uploaded object hash does not match expected_hash")
|
||||
|
||||
file_name = upload.object_key.rsplit("/", 1)[-1]
|
||||
# The object key is just ``{workspace_id}/{ulid}`` — it does not encode
|
||||
# the file name. Use the original file name from the upload session
|
||||
# (carried via payload.file_name) so the StorageObjects row still
|
||||
# records the user-visible name + extension.
|
||||
file_name = safe_file_name(payload.file_name)
|
||||
item = StorageObjects(
|
||||
storage_object_id=new_ulid(),
|
||||
workspace_id=upload.workspace_id,
|
||||
|
||||
@@ -1,5 +1,66 @@
|
||||
"""Backward-compatible re-export of the shared storage client."""
|
||||
"""Backend-bound storage client.
|
||||
|
||||
from common.storage.client import StorageClient
|
||||
Re-exports :class:`StorageClient` under the same name used by callers in
|
||||
``backend/``. The default client raises :class:`StorageClientError` from
|
||||
``common.storage.client`` so it stays usable from non-FastAPI contexts.
|
||||
Inside FastAPI route handlers we want HTTP-shaped errors, so this module
|
||||
also exposes :class:`BackendStorageClient`, a thin wrapper that translates
|
||||
the framework-agnostic errors into ``HTTPException``.
|
||||
"""
|
||||
|
||||
__all__ = ["StorageClient"]
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from common.storage.client import (
|
||||
StorageClient,
|
||||
StorageClientError,
|
||||
StorageRequestFailed,
|
||||
StorageUnavailable,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["BackendStorageClient", "StorageClient", "StorageClientError"]
|
||||
|
||||
|
||||
def _to_http_exception(exc: StorageClientError) -> HTTPException:
|
||||
if isinstance(exc, StorageUnavailable):
|
||||
return HTTPException(
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
{
|
||||
"code": "STORAGE_UNAVAILABLE",
|
||||
"message": "Storage service temporarily unavailable",
|
||||
"retryable": True,
|
||||
"details": {},
|
||||
},
|
||||
)
|
||||
if isinstance(exc, StorageRequestFailed):
|
||||
return HTTPException(exc.status_code, exc.detail)
|
||||
return HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
"Storage client error",
|
||||
)
|
||||
|
||||
|
||||
class BackendStorageClient(StorageClient):
|
||||
"""Storage client that raises ``HTTPException`` for web callers."""
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
payload: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
return await super()._request(method, path, payload=payload)
|
||||
except StorageClientError as exc:
|
||||
raise _to_http_exception(exc) from exc
|
||||
|
||||
|
||||
# Re-bind the imported symbol so existing backend call sites that import
|
||||
# ``StorageClient`` from this module transparently get the FastAPI-bound
|
||||
# variant without changing every import statement.
|
||||
StorageClient = BackendStorageClient
|
||||
|
||||
Reference in New Issue
Block a user