cosmo/backend/app/services/nasa_cache_service.py

96 lines
3.4 KiB
Python
Raw Normal View History

2026-07-21 12:09:51 +00:00
"""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()