ai-silajuav / app /services.py
github-actions[bot]
Deploy GitHub snapshot to Hugging Face Space
90a0b2c
Raw
History Blame Contribute Delete
4.53 kB
from urllib.parse import quote, urlparse
import httpx
from fastapi import HTTPException, status
from app.config import Settings
from app.models import PipelineRequest, PipelineResponse, SupabaseUpdate
class PipelineClient:
_image_field_names = ("image", "file", "img")
def __init__(self, settings: Settings) -> None:
self._base_url = str(settings.ai_pipeline_url).rstrip("/")
self._timeout = settings.ai_pipeline_timeout_seconds
async def process_url(self, payload: PipelineRequest) -> PipelineResponse:
async with httpx.AsyncClient(timeout=self._timeout) as client:
image = await self._download_image(client, payload.img_location)
try:
response = await self._post_process_form(client, payload, image)
except httpx.HTTPError as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"AI pipeline request failed: {exc}",
) from exc
return PipelineResponse.model_validate(response.json())
async def _download_image(self, client: httpx.AsyncClient, image_url: str) -> tuple[str, bytes, str]:
try:
response = await client.get(image_url, follow_redirects=True)
response.raise_for_status()
except httpx.HTTPError as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"Failed to download Cloudinary image: {exc}",
) from exc
content = response.content
if not content:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail="Downloaded Cloudinary image is empty.",
)
content_type = response.headers.get("content-type", "application/octet-stream").split(";")[0]
filename = _filename_from_url(image_url)
return filename, content, content_type
async def _post_process_form(
self,
client: httpx.AsyncClient,
payload: PipelineRequest,
image: tuple[str, bytes, str],
) -> httpx.Response:
last_response: httpx.Response | None = None
data = {
"lan": str(payload.lan),
"lon": str(payload.lon),
}
for field_name in self._image_field_names:
filename, content, content_type = image
files = {
field_name: (filename, content, content_type),
}
response = await client.post(f"{self._base_url}/process", data=data, files=files)
if response.status_code != 422:
response.raise_for_status()
return response
last_response = response
if last_response is not None:
last_response.raise_for_status()
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail="AI pipeline did not return a response.",
)
def _filename_from_url(image_url: str) -> str:
path = urlparse(image_url).path
filename = path.rsplit("/", 1)[-1].strip()
return filename or "cloudinary-image.jpg"
class SupabaseClient:
def __init__(self, settings: Settings) -> None:
if not settings.supabase_url or not settings.supabase_service_role_key:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Supabase is not configured.",
)
self._base_url = str(settings.supabase_url).rstrip("/")
self._table = settings.supabase_table
self._headers = {
"apikey": settings.supabase_service_role_key,
"Authorization": f"Bearer {settings.supabase_service_role_key}",
"Content-Type": "application/json",
"Prefer": "return=minimal",
}
async def update_report(self, report_id: str, payload: SupabaseUpdate) -> None:
encoded_id = quote(report_id, safe="")
url = f"{self._base_url}/rest/v1/{self._table}?id=eq.{encoded_id}"
async with httpx.AsyncClient(timeout=30) as client:
try:
response = await client.patch(url, headers=self._headers, json=payload.model_dump())
response.raise_for_status()
except httpx.HTTPError as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"Supabase update failed: {exc}",
) from exc