"""Repository 层:封装数据访问。 Repository 只负责查询,不负责事务提交。 事务由 Application Service 通过 UnitOfWork 控制。 """ from __future__ import annotations from sqlalchemy import func, select from sqlalchemy.orm import selectinload from sqlalchemy.ext.asyncio import AsyncSession from src.db.models import League, Match, Prediction, Team class MatchRepository: """比赛数据访问。""" def __init__(self, session: AsyncSession) -> None: self._session = session async def get_by_id(self, match_id: int) -> Match | None: return await self._session.get(Match, match_id) async def get_with_relations(self, match_id: int) -> Match | None: stmt = ( select(Match) .options( selectinload(Match.league), selectinload(Match.home_team), selectinload(Match.away_team), selectinload(Match.stats), ) .where(Match.id == match_id) ) return (await self._session.execute(stmt)).scalar_one_or_none() async def find_by_teams_and_date( self, league_id: int, home_team_id: int, away_team_id: int, date ) -> Match | None: """按联赛+主队+客队+日期查找比赛(天级匹配)。 预加载 stats:调用方(understat 回填)会读取 existing.stats, async session 下惰性加载会抛 MissingGreenlet。 """ if hasattr(date, "date"): date = date.date() stmt = ( select(Match) .options(selectinload(Match.stats)) .where(Match.league_id == league_id) .where(Match.home_team_id == home_team_id) .where(Match.away_team_id == away_team_id) .where(func.date(Match.match_date) == date) ) return (await self._session.execute(stmt)).scalar_one_or_none() async def add(self, match: Match) -> None: self._session.add(match) await self._session.flush() class TeamRepository: """球队数据访问。""" def __init__(self, session: AsyncSession) -> None: self._session = session async def get_by_name(self, name: str) -> Team | None: stmt = select(Team).where(Team.name == name) return (await self._session.execute(stmt)).scalar_one_or_none() async def get_or_create(self, name: str) -> Team: """按名获取球队,不存在则创建。""" team = await self.get_by_name(name) if team is None: team = Team(name=name) self._session.add(team) await self._session.flush() return team async def get_all_by_names(self, names: list[str]) -> dict[str, Team]: """批量获取球队,返回 name → Team 映射。""" if not names: return {} stmt = select(Team).where(Team.name.in_(names)) teams = (await self._session.execute(stmt)).scalars().all() return {t.name: t for t in teams} async def add(self, team: Team) -> None: self._session.add(team) await self._session.flush() class LeagueRepository: """联赛数据访问。""" def __init__(self, session: AsyncSession) -> None: self._session = session async def get_by_code(self, code: str) -> League | None: stmt = select(League).where(League.code == code) return (await self._session.execute(stmt)).scalar_one_or_none() async def get_or_create(self, code: str, name: str, country: str | None = None) -> League: league = await self.get_by_code(code) if league is None: league = League(code=code, name=name, country=country) self._session.add(league) await self._session.flush() return league async def add(self, league: League) -> None: self._session.add(league) await self._session.flush() class PredictionRepository: """预测记录数据访问。""" def __init__(self, session: AsyncSession) -> None: self._session = session async def get_by_id(self, prediction_id: int) -> Prediction | None: return await self._session.get(Prediction, prediction_id) async def add(self, prediction: Prediction) -> None: self._session.add(prediction) await self._session.flush()