115 lines
4.4 KiB
Python
115 lines
4.4 KiB
Python
from typing import Any, Dict, Optional, Union
|
|
import aiohttp
|
|
import logging
|
|
import json
|
|
import csv
|
|
import io
|
|
from app.services.service_manager import ServiceManager
|
|
from app.schemas.file import FileInDB
|
|
from sqlalchemy.orm import Session
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class BaseExternalService:
|
|
def __init__(self, base_url: str, api_key: Optional[str] = None):
|
|
self.base_url = base_url
|
|
self.api_key = api_key
|
|
self.session = None
|
|
self.service_manager = ServiceManager()
|
|
self._services = {}
|
|
|
|
async def _get_session(self) -> aiohttp.ClientSession:
|
|
if self.session is None or self.session.closed:
|
|
self.session = aiohttp.ClientSession()
|
|
return self.session
|
|
|
|
async def _make_request(
|
|
self,
|
|
method: str,
|
|
endpoint: str,
|
|
params: Optional[Dict[str, Any]] = None,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
data: Optional[Dict[str, Any]] = None,
|
|
content_type: str = "application/json",
|
|
binary: bool = False
|
|
) -> Union[Dict[str, Any], str, bytes]:
|
|
session = await self._get_session()
|
|
url = f"{self.base_url}{endpoint}"
|
|
|
|
if self.api_key:
|
|
headers = headers or {}
|
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
|
|
try:
|
|
async with session.request(method, url, params=params, headers=headers, json=data) as response:
|
|
response.raise_for_status()
|
|
|
|
# Get the actual content type from the response
|
|
response_content_type = response.headers.get('content-type', '').lower()
|
|
logger.info(f"Making request to {url}")
|
|
|
|
if binary:
|
|
return await response.read()
|
|
|
|
# Get the raw response text first
|
|
raw_response = await response.text()
|
|
|
|
# Only try to parse as JSON if the content type indicates JSON
|
|
if 'application/json' in response_content_type or 'text/json' in response_content_type:
|
|
try:
|
|
# First try to parse the response directly
|
|
return await response.json()
|
|
except Exception as e:
|
|
try:
|
|
# If that fails, try parsing the raw text as JSON (in case it's double-encoded)
|
|
return json.loads(raw_response)
|
|
except Exception as e:
|
|
logger.error(f"Failed to parse JSON response: {e}")
|
|
return raw_response
|
|
return raw_response
|
|
|
|
except aiohttp.ClientError as e:
|
|
logger.error(f"Request failed: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error during API request: {str(e)}")
|
|
raise
|
|
|
|
async def close(self):
|
|
"""Close the aiohttp session if it exists"""
|
|
if self.session and not self.session.closed:
|
|
await self.session.close()
|
|
self.session = None
|
|
logger.info(f"Closed session for {self.__class__.__name__}")
|
|
|
|
def get_service(self, name: str) -> Any:
|
|
"""Get a service by name with lazy loading"""
|
|
if name not in self._services:
|
|
self._services[name] = self.service_manager.get_service(name)
|
|
return self._services[name]
|
|
|
|
@property
|
|
def file_service(self):
|
|
"""Convenience property for file service"""
|
|
return self.get_service('file')
|
|
|
|
async def save_file(self, db: Session, file_data: Union[bytes, list[dict]], file_name: str, subdir: str, file_type: Optional[str] = None) -> FileInDB:
|
|
"""Save a file using the FileService"""
|
|
if isinstance(file_data, list):
|
|
# Convert list of dictionaries to CSV bytes
|
|
output = io.StringIO()
|
|
writer = csv.DictWriter(output, fieldnames=file_data[0].keys())
|
|
writer.writeheader()
|
|
writer.writerows(file_data)
|
|
file_data = output.getvalue().encode('utf-8')
|
|
file_type = file_type or 'text/csv'
|
|
|
|
# Use FileService to save the file
|
|
file_service = self.get_service('file')
|
|
return await file_service.save_file(
|
|
db=db,
|
|
file_data=file_data,
|
|
filename=file_name,
|
|
subdir=subdir,
|
|
file_type=file_type
|
|
) |