from __future__ import annotations import httpx import structlog from app.core.config import settings from app.core.exceptions import FileServiceError from app.models.schemas import UploadedFileInfo logger = structlog.get_logger(__name__) async def upload_file_to_service( file_bytes: bytes, filename: str, content_type: str = "application/octet-stream", ) -> UploadedFileInfo: timeout = httpx.Timeout(settings.FILE_SERVICE_TIMEOUT, connect=settings.FILE_SERVICE_CONNECT_TIMEOUT) headers = { "X-API-Key": settings.FILE_SERVICE_API_KEY, "Authorization": f"Bearer {settings.FILE_SERVICE_BEARER_TOKEN}", } async with httpx.AsyncClient(timeout=timeout) as client: try: response = await client.post( settings.FILE_SERVICE_URL, headers=headers, files={"files": (filename, file_bytes, content_type)}, ) response.raise_for_status() except httpx.HTTPStatusError as exc: logger.error( "file_service_http_error", status_code=exc.response.status_code, body=exc.response.text, ) raise FileServiceError( f"File service returned HTTP {exc.response.status_code}" ) from exc except httpx.RequestError as exc: logger.error("file_service_request_error", error=str(exc)) raise FileServiceError(f"File service unreachable: {exc}") from exc uploaded = _extract_uploaded_file(response.json(), filename, len(file_bytes)) logger.info("file_uploaded", file_id=uploaded.file_id, filename=filename) return uploaded def _extract_uploaded_file( payload: object, filename: str, size_bytes: int, ) -> UploadedFileInfo: """Pick the first file entry out of the (variably-shaped) service response.""" if isinstance(payload, list): if not payload or not isinstance(payload[0], dict): raise FileServiceError("File service returned an empty or malformed list") entry = payload[0].get("data") or payload[0] return _entry_to_info(entry, filename, size_bytes) if isinstance(payload, dict): nested = payload.get("files") or payload.get("data") if isinstance(nested, list) and nested and isinstance(nested[0], dict): entry = nested[0].get("data") or nested[0] return _entry_to_info(entry, filename, size_bytes) return _entry_to_info(payload, filename, size_bytes) raise FileServiceError(f"Unrecognised file service response type: {type(payload)}") def _entry_to_info(entry: dict, filename: str, size_bytes: int) -> UploadedFileInfo: file_id = str( entry.get("fileid") or entry.get("file_id") or entry.get("id") or entry.get("_id") or "" ) file_url = str( entry.get("file_url") or entry.get("url") or entry.get("path") or "" ) resolved_filename = str(entry.get("filename") or entry.get("name") or filename) resolved_size = int(entry.get("size") or entry.get("size_bytes") or size_bytes) return UploadedFileInfo( file_id=file_id, file_url=file_url, filename=resolved_filename, size_bytes=resolved_size, )