96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
"""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()
|