Coverage for src/epl/ports/outbound.py: 100%
14 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-05 10:11 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-05 10:11 +0000
1"""Outbound ports — interfaces the application layer depends on.
3These are the contracts that secondary/driven adapters must satisfy. The
4application layer only imports these protocols; it never references concrete
5implementations. This is what makes the hexagonal boundary testable and
6swap-able (e.g. real HTTP adapter vs. an in-memory stub).
7"""
9from typing import Protocol
11from ..domain.models import Match, MatchDetails, NewsArticle, Player, PulselivePlayer, Standing, Team
14class EPLAPIPort(Protocol):
15 """Contract for the upstream Premier League data source (ESPN API)."""
17 async def get_teams(self) -> list[Team]:
18 """Return all active Premier League teams."""
19 ...
21 async def get_team(self, team_id: str) -> Team:
22 """Return a single team by its ESPN team ID.
24 Raises:
25 EPLNotFoundError: If no team with that ID exists.
26 """
27 ...
29 async def get_scoreboard(self, date: str | None = None, end_date: str | None = None) -> list[Match]:
30 """Return matches on the given date or date range, or today if date is None."""
31 ...
33 async def get_news(self, limit: int) -> list[NewsArticle]:
34 """Return up to ``limit`` recent Premier League news articles."""
35 ...
37 async def get_roster(self, team_id: str) -> list[Player]:
38 """Return the active roster for a team.
40 Raises:
41 EPLNotFoundError: If no team with that ID exists.
42 """
43 ...
45 async def get_match_details(self, match_id: str) -> MatchDetails:
46 """Return detailed information for a single match.
48 Raises:
49 EPLNotFoundError: If no match with that ID exists.
50 """
51 ...
53 async def get_team_schedule(self, team_id: str) -> list[Match]:
54 """Return all scheduled and completed matches for a team in the current season.
56 Raises:
57 EPLNotFoundError: If no team with that ID exists.
58 """
59 ...
61 async def get_standings(self) -> list[Standing]:
62 """Return the current Premier League standings ordered by points descending."""
63 ...
66class PulselivePort(Protocol):
67 """Contract for the Pulselive/Opta-backed second-tier data source.
69 Pulselive powers premierleague.com's front-end. It exposes richer per-
70 player data than ESPN's roster feed — Opta ids, shirt numbers, positional
71 detail, loan flag, DoB — keyed by an integer season id sourced from
72 ``footballapi.pulselive.com/football/competitions/1/compseasons``.
73 """
75 async def get_players(self, season_id: int, page_size: int) -> list[PulselivePlayer]:
76 """Return players registered in ``season_id``, capped at ``page_size``."""
77 ...
79 async def latest_completed_season_id(self) -> int:
80 """Return the id of the most recent Premier League season that has finished.
82 The Pulselive ``/players`` endpoint returns empty pages for in-flight
83 seasons that have not started registering players, so the service
84 layer falls back to the last completed season by default.
85 """
86 ...