"""Lyrics repository — adapter over ``AsyncSession``. One cached row per track (``track_id`` unique). ``upsert`` refreshes the row and bumps ``fetched_at`` so the service's TTL is measured from the last fetch. """ import uuid from sqlalchemy import func, select from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.ext.asyncio import AsyncSession from app.domain.entities.lyrics import Lyrics from app.infrastructure.db.models.lyrics import LyricsModel def _to_entity(row: LyricsModel) -> Lyrics: return Lyrics( track_id=row.track_id, synced=row.synced, plain=row.plain, source=row.source, status=row.status, fetched_at=row.fetched_at, ) class SqlAlchemyLyricsRepository: def __init__(self, session: AsyncSession) -> None: self._session = session async def get(self, track_id: uuid.UUID) -> Lyrics | None: row = await self._session.scalar( select(LyricsModel).where(LyricsModel.track_id == track_id) ) return _to_entity(row) if row is not None else None async def upsert( self, *, track_id: uuid.UUID, synced: str | None, plain: str | None, source: str | None, status: str, ) -> Lyrics: values = { "track_id": track_id, "synced": synced, "plain": plain, "source": source, "status": status, "fetched_at": func.now(), } stmt = ( pg_insert(LyricsModel) .values(**values) .on_conflict_do_update( index_elements=[LyricsModel.track_id], set_={ "synced": synced, "plain": plain, "source": source, "status": status, "fetched_at": func.now(), }, ) .returning(LyricsModel) ) row = (await self._session.scalars(stmt)).one() await self._session.flush() return _to_entity(row)