Tighten provider normalization contract and fallback semantics
This commit is contained in:
@ -3,6 +3,15 @@ import logging
|
||||
from django.conf import settings
|
||||
|
||||
from apps.providers.clients import BalldontlieClient
|
||||
from apps.providers.contracts import (
|
||||
CompetitionPayload,
|
||||
NormalizedSyncPayload,
|
||||
PlayerCareerPayload,
|
||||
PlayerPayload,
|
||||
PlayerStatsPayload,
|
||||
SeasonPayload,
|
||||
TeamPayload,
|
||||
)
|
||||
from apps.providers.interfaces import BaseProviderAdapter
|
||||
from apps.providers.services.balldontlie_mappings import (
|
||||
map_competitions,
|
||||
@ -27,7 +36,7 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
def configured_seasons(self) -> list[int]:
|
||||
return settings.PROVIDER_BALLDONTLIE_SEASONS
|
||||
|
||||
def search_players(self, *, query: str = "", limit: int = 50, offset: int = 0) -> list[dict]:
|
||||
def search_players(self, *, query: str = "", limit: int = 50, offset: int = 0) -> list[PlayerPayload]:
|
||||
params = {"search": query} if query else None
|
||||
rows = self.client.list_paginated(
|
||||
"players",
|
||||
@ -38,7 +47,7 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
mapped = map_players(rows)
|
||||
return mapped[offset : offset + limit]
|
||||
|
||||
def fetch_player(self, *, external_player_id: str) -> dict | None:
|
||||
def fetch_player(self, *, external_player_id: str) -> PlayerPayload | None:
|
||||
if not external_player_id.startswith("player-"):
|
||||
return None
|
||||
player_id = external_player_id.replace("player-", "", 1)
|
||||
@ -49,7 +58,7 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
mapped = map_players([data])
|
||||
return mapped[0] if mapped else None
|
||||
|
||||
def fetch_players(self) -> list[dict]:
|
||||
def fetch_players(self) -> list[PlayerPayload]:
|
||||
rows = self.client.list_paginated(
|
||||
"players",
|
||||
per_page=settings.PROVIDER_BALLDONTLIE_PLAYERS_PER_PAGE,
|
||||
@ -57,18 +66,18 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
)
|
||||
return map_players(rows)
|
||||
|
||||
def fetch_competitions(self) -> list[dict]:
|
||||
def fetch_competitions(self) -> list[CompetitionPayload]:
|
||||
return map_competitions()
|
||||
|
||||
def fetch_teams(self) -> list[dict]:
|
||||
def fetch_teams(self) -> list[TeamPayload]:
|
||||
payload = self.client.get_json("teams")
|
||||
rows = payload.get("data") or []
|
||||
return map_teams(rows if isinstance(rows, list) else [])
|
||||
|
||||
def fetch_seasons(self) -> list[dict]:
|
||||
def fetch_seasons(self) -> list[SeasonPayload]:
|
||||
return map_seasons(self.configured_seasons)
|
||||
|
||||
def fetch_player_stats(self) -> list[dict]:
|
||||
def fetch_player_stats(self) -> list[PlayerStatsPayload]:
|
||||
all_rows: list[dict] = []
|
||||
for season in self.configured_seasons:
|
||||
rows = self.client.list_paginated(
|
||||
@ -82,7 +91,7 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
player_stats, _ = map_player_stats(all_rows, allowed_seasons=self.configured_seasons)
|
||||
return player_stats
|
||||
|
||||
def fetch_player_careers(self) -> list[dict]:
|
||||
def fetch_player_careers(self) -> list[PlayerCareerPayload]:
|
||||
all_rows: list[dict] = []
|
||||
for season in self.configured_seasons:
|
||||
rows = self.client.list_paginated(
|
||||
@ -96,7 +105,7 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
_, player_careers = map_player_stats(all_rows, allowed_seasons=self.configured_seasons)
|
||||
return player_careers
|
||||
|
||||
def sync_all(self) -> dict:
|
||||
def sync_all(self) -> NormalizedSyncPayload:
|
||||
logger.info(
|
||||
"provider_sync_start",
|
||||
extra={"provider": self.namespace, "seasons": self.configured_seasons},
|
||||
@ -141,7 +150,7 @@ class BalldontlieProviderAdapter(BaseProviderAdapter):
|
||||
"cursor": None,
|
||||
}
|
||||
|
||||
def sync_incremental(self, *, cursor: str | None = None) -> dict:
|
||||
def sync_incremental(self, *, cursor: str | None = None) -> NormalizedSyncPayload:
|
||||
payload = self.sync_all()
|
||||
payload["cursor"] = cursor
|
||||
return payload
|
||||
|
||||
@ -6,6 +6,15 @@ from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from apps.providers.contracts import (
|
||||
CompetitionPayload,
|
||||
NormalizedSyncPayload,
|
||||
PlayerCareerPayload,
|
||||
PlayerPayload,
|
||||
PlayerStatsPayload,
|
||||
SeasonPayload,
|
||||
TeamPayload,
|
||||
)
|
||||
from apps.providers.exceptions import ProviderRateLimitError, ProviderTransientError
|
||||
from apps.providers.interfaces import BaseProviderAdapter
|
||||
|
||||
@ -50,38 +59,38 @@ class MvpDemoProviderAdapter(BaseProviderAdapter):
|
||||
value = payload.get(key, [])
|
||||
return value if isinstance(value, list) else []
|
||||
|
||||
def search_players(self, *, query: str = "", limit: int = 50, offset: int = 0) -> list[dict]:
|
||||
def search_players(self, *, query: str = "", limit: int = 50, offset: int = 0) -> list[PlayerPayload]:
|
||||
players = self.fetch_players()
|
||||
if query:
|
||||
query_lower = query.lower()
|
||||
players = [p for p in players if query_lower in p.get("full_name", "").lower()]
|
||||
return players[offset : offset + limit]
|
||||
|
||||
def fetch_player(self, *, external_player_id: str) -> dict | None:
|
||||
def fetch_player(self, *, external_player_id: str) -> PlayerPayload | None:
|
||||
for payload in self.fetch_players():
|
||||
if payload.get("external_id") == external_player_id:
|
||||
return payload
|
||||
return None
|
||||
|
||||
def fetch_players(self) -> list[dict]:
|
||||
return self._payload_list("players")
|
||||
def fetch_players(self) -> list[PlayerPayload]:
|
||||
return self._payload_list("players") # type: ignore[return-value]
|
||||
|
||||
def fetch_competitions(self) -> list[dict]:
|
||||
return self._payload_list("competitions")
|
||||
def fetch_competitions(self) -> list[CompetitionPayload]:
|
||||
return self._payload_list("competitions") # type: ignore[return-value]
|
||||
|
||||
def fetch_teams(self) -> list[dict]:
|
||||
return self._payload_list("teams")
|
||||
def fetch_teams(self) -> list[TeamPayload]:
|
||||
return self._payload_list("teams") # type: ignore[return-value]
|
||||
|
||||
def fetch_seasons(self) -> list[dict]:
|
||||
return self._payload_list("seasons")
|
||||
def fetch_seasons(self) -> list[SeasonPayload]:
|
||||
return self._payload_list("seasons") # type: ignore[return-value]
|
||||
|
||||
def fetch_player_stats(self) -> list[dict]:
|
||||
return self._payload_list("player_stats")
|
||||
def fetch_player_stats(self) -> list[PlayerStatsPayload]:
|
||||
return self._payload_list("player_stats") # type: ignore[return-value]
|
||||
|
||||
def fetch_player_careers(self) -> list[dict]:
|
||||
return self._payload_list("player_careers")
|
||||
def fetch_player_careers(self) -> list[PlayerCareerPayload]:
|
||||
return self._payload_list("player_careers") # type: ignore[return-value]
|
||||
|
||||
def sync_all(self) -> dict:
|
||||
def sync_all(self) -> NormalizedSyncPayload:
|
||||
return {
|
||||
"players": self.fetch_players(),
|
||||
"competitions": self.fetch_competitions(),
|
||||
@ -92,7 +101,7 @@ class MvpDemoProviderAdapter(BaseProviderAdapter):
|
||||
"cursor": None,
|
||||
}
|
||||
|
||||
def sync_incremental(self, *, cursor: str | None = None) -> dict:
|
||||
def sync_incremental(self, *, cursor: str | None = None) -> NormalizedSyncPayload:
|
||||
payload = self.sync_all()
|
||||
# MVP source has no change feed yet; returns full snapshot.
|
||||
payload["cursor"] = cursor
|
||||
|
||||
Reference in New Issue
Block a user