Tighten provider normalization contract and fallback semantics

This commit is contained in:
Alfredo Di Stasio
2026-03-10 16:47:39 +01:00
parent 2252821daf
commit e0e75cfb0c
11 changed files with 340 additions and 59 deletions

View File

@ -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