crowncode-backend / app /services /external_clients.py
Rthur2003's picture
fix: remove base_url from service_status response for cleaner output
98d16d1
"""
HTTP clients for external analysis services with enhanced validation.
"""
from __future__ import annotations
from dataclasses import dataclass
import os
from pathlib import Path
from typing import Optional
import httpx
from .validation import validate_audio_path, validate_timeout
@dataclass
class ClientResponse:
available: bool
response: Optional[dict]
error: Optional[str]
class MusicAIDetectorClient:
def __init__(self, base_url: Optional[str] = None, timeout_sec: float = 30.0) -> None:
self.base_url = base_url or os.getenv("MUSIC_AI_API_URL")
if not validate_timeout(timeout_sec):
timeout_sec = 30.0
self.timeout_sec = timeout_sec
async def predict(self, audio_path: Path) -> ClientResponse:
if not self.base_url:
return ClientResponse(available=False, response=None, error="music_ai_not_configured")
is_valid, error_msg = validate_audio_path(audio_path)
if not is_valid:
return ClientResponse(available=True, response=None, error=f"music_ai_{error_msg}")
try:
async with httpx.AsyncClient(timeout=self.timeout_sec) as client:
with audio_path.open("rb") as handle:
files = {"file": (audio_path.name, handle, _guess_content_type(audio_path))}
response = await client.post(f"{self.base_url.rstrip('/')}/predict", files=files)
if response.status_code != 200:
return ClientResponse(
available=True,
response=None,
error=f"music_ai_http_{response.status_code}",
)
return ClientResponse(available=True, response=response.json(), error=None)
except httpx.TimeoutException:
return ClientResponse(available=True, response=None, error="music_ai_timeout")
except httpx.NetworkError as exc:
return ClientResponse(available=True, response=None, error=f"music_ai_network_error: {type(exc).__name__}")
except OSError as exc:
return ClientResponse(available=True, response=None, error=f"music_ai_file_error: {type(exc).__name__}")
except Exception as exc:
return ClientResponse(available=True, response=None, error=f"music_ai_error: {type(exc).__name__}")
class SesAnaliziClient:
def __init__(self, base_url: Optional[str] = None, timeout_sec: float = 30.0) -> None:
self.base_url = base_url or os.getenv("SES_ANALIZI_API_URL")
if not validate_timeout(timeout_sec):
timeout_sec = 30.0
self.timeout_sec = timeout_sec
async def analyze(self, audio_path: Path) -> ClientResponse:
if not self.base_url:
return ClientResponse(available=False, response=None, error="ses_analizi_not_configured")
is_valid, error_msg = validate_audio_path(audio_path)
if not is_valid:
return ClientResponse(available=True, response=None, error=f"ses_analizi_{error_msg}")
try:
async with httpx.AsyncClient(timeout=self.timeout_sec) as client:
with audio_path.open("rb") as handle:
files = {"file": (audio_path.name, handle, _guess_content_type(audio_path))}
response = await client.post(f"{self.base_url.rstrip('/')}/analyze", files=files)
if response.status_code != 200:
return ClientResponse(
available=True,
response=None,
error=f"ses_analizi_http_{response.status_code}",
)
return ClientResponse(available=True, response=response.json(), error=None)
except httpx.TimeoutException:
return ClientResponse(available=True, response=None, error="ses_analizi_timeout")
except httpx.NetworkError as exc:
return ClientResponse(available=True, response=None, error=f"ses_analizi_network_error: {type(exc).__name__}")
except OSError as exc:
return ClientResponse(available=True, response=None, error=f"ses_analizi_file_error: {type(exc).__name__}")
except Exception as exc:
return ClientResponse(available=True, response=None, error=f"ses_analizi_error: {type(exc).__name__}")
def service_status() -> dict:
return {
"music_ai": {
"configured": bool(os.getenv("MUSIC_AI_API_URL")),
},
"ses_analizi": {
"configured": bool(os.getenv("SES_ANALIZI_API_URL")),
},
}
def _guess_content_type(path: Path) -> str:
ext = path.suffix.lower()
if ext == ".wav":
return "audio/wav"
if ext in {".mp3", ".m4a"}:
return "audio/mpeg"
if ext == ".flac":
return "audio/flac"
if ext == ".ogg":
return "audio/ogg"
if ext == ".webm":
return "audio/webm"
if ext == ".opus":
return "audio/opus"
return "application/octet-stream"