"""Persistence operations for NASA API response caching.""" from datetime import datetime, timedelta from typing import Any, Dict, Optional from sqlalchemy import and_, select from sqlalchemy.dialects.postgresql import insert from sqlalchemy.ext.asyncio import AsyncSession from app.database import AsyncSessionLocal from app.models.db import NasaCache class NasaCacheService: """Read and write cached NASA API responses.""" @staticmethod async def get_cached_response( body_id: str, start_time: Optional[datetime], end_time: Optional[datetime], step: str, session: Optional[AsyncSession] = None, ) -> Optional[Dict[str, Any]]: async def query(active_session: AsyncSession) -> Optional[Dict[str, Any]]: start_naive = start_time.replace(tzinfo=None) if start_time else None end_naive = end_time.replace(tzinfo=None) if end_time else None result = await active_session.execute( select(NasaCache).where( and_( NasaCache.body_id == body_id, NasaCache.start_time == start_naive, NasaCache.end_time == end_naive, NasaCache.step == step, NasaCache.expires_at > datetime.utcnow(), ) ) ) cache = result.scalar_one_or_none() return cache.data if cache else None if session is not None: return await query(session) async with AsyncSessionLocal() as active_session: return await query(active_session) @staticmethod async def save_response( body_id: str, start_time: Optional[datetime], end_time: Optional[datetime], step: str, response_data: Dict[str, Any], ttl_days: int = 7, session: Optional[AsyncSession] = None, ) -> NasaCache: async def save(active_session: AsyncSession) -> NasaCache: start_naive = start_time.replace(tzinfo=None) if start_time else None end_naive = end_time.replace(tzinfo=None) if end_time else None now = datetime.utcnow() start_key = start_time.isoformat() if start_time else "null" end_key = end_time.isoformat() if end_time else "null" cache_key = f"{body_id}:{start_key}:{end_key}:{step}" statement = insert(NasaCache).values( cache_key=cache_key, body_id=body_id, start_time=start_naive, end_time=end_naive, step=step, data=response_data, expires_at=now + timedelta(days=ttl_days), ) statement = statement.on_conflict_do_update( index_elements=["cache_key"], set_={ "data": response_data, "created_at": now, "expires_at": now + timedelta(days=ttl_days), }, ).returning(NasaCache) result = await active_session.execute(statement) cache = result.scalar_one() await active_session.commit() await active_session.refresh(cache) return cache if session is not None: return await save(session) async with AsyncSessionLocal() as active_session: return await save(active_session) nasa_cache_service = NasaCacheService()