copilot-swe-agent[bot]
feat: build complete ReliefLensAI backend
d0f3cbe unverified
Raw
History Blame Contribute Delete
4.4 kB
from __future__ import annotations
import asyncio
import json
import logging
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from functools import lru_cache
from core.config import get_settings
logger = logging.getLogger(__name__)
class StorageService:
def __init__(self) -> None:
self.settings = get_settings()
self._cache: Dict[str, Any] = {}
self._lock = asyncio.Lock()
self.base_path = Path(self.settings.storage_path)
def _ensure_dirs(self) -> None:
for sub in ("sessions", "incidents", "signals", "resources", "dispatch"):
(self.base_path / sub).mkdir(parents=True, exist_ok=True)
def _path(self, collection: str, item_id: str) -> Path:
return self.base_path / collection / f"{item_id}.json"
async def _write(self, collection: str, item_id: str, data: Dict[str, Any]) -> None:
self._ensure_dirs()
cache_key = f"{collection}:{item_id}"
async with self._lock:
self._cache[cache_key] = data
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, self._sync_write, collection, item_id, data)
def _sync_write(self, collection: str, item_id: str, data: Dict[str, Any]) -> None:
path = self._path(collection, item_id)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, default=str), encoding="utf-8")
async def _read(self, collection: str, item_id: str) -> Optional[Dict[str, Any]]:
cache_key = f"{collection}:{item_id}"
if cache_key in self._cache:
return self._cache[cache_key]
path = self._path(collection, item_id)
if not path.exists():
return None
try:
data: Dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
self._cache[cache_key] = data
return data
except Exception as exc:
logger.error("Failed to read %s: %s", path, exc)
return None
async def _list(self, collection: str) -> List[Dict[str, Any]]:
self._ensure_dirs()
folder = self.base_path / collection
items: List[Dict[str, Any]] = []
for fp in folder.glob("*.json"):
try:
items.append(json.loads(fp.read_text(encoding="utf-8")))
except Exception as exc:
logger.warning("Skip corrupt file %s: %s", fp, exc)
return items
async def save_session(self, session_id: str, data: Dict[str, Any]) -> None:
await self._write("sessions", session_id, data)
async def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
return await self._read("sessions", session_id)
async def save_incident(self, incident_id: str, data: Dict[str, Any]) -> None:
await self._write("incidents", incident_id, data)
async def get_incident(self, incident_id: str) -> Optional[Dict[str, Any]]:
return await self._read("incidents", incident_id)
async def list_incidents(self) -> List[Dict[str, Any]]:
return await self._list("incidents")
async def save_signal(self, signal_id: str, data: Dict[str, Any]) -> None:
await self._write("signals", signal_id, data)
async def get_signal(self, signal_id: str) -> Optional[Dict[str, Any]]:
return await self._read("signals", signal_id)
async def list_signals(self) -> List[Dict[str, Any]]:
return await self._list("signals")
async def save_resource(self, resource_id: str, data: Dict[str, Any]) -> None:
await self._write("resources", resource_id, data)
async def list_resources(self) -> List[Dict[str, Any]]:
return await self._list("resources")
async def save_dispatch(self, dispatch_id: str, data: Dict[str, Any]) -> None:
await self._write("dispatch", dispatch_id, data)
async def list_dispatch(self) -> List[Dict[str, Any]]:
return await self._list("dispatch")
async def update_incident(self, incident_id: str, updates: Dict[str, Any]) -> Optional[Dict[str, Any]]:
existing = await self.get_incident(incident_id)
if existing is None:
return None
existing.update(updates)
await self.save_incident(incident_id, existing)
return existing
@lru_cache()
def get_storage() -> StorageService:
return StorageService()