""" SBA API client for fetching player and team data. Integrates with SBA REST API to retrieve player and team information for use in game lineup display and game creation. Author: Claude Date: 2025-01-10 """ import logging from typing import Any import httpx from app.config import get_settings from app.models.player_models import SbaPlayer logger = logging.getLogger(f"{__name__}.SbaApiClient") settings = get_settings() class SbaApiClient: """Client for SBA API player and team data lookups.""" def __init__( self, base_url: str = "https://api.sba.manticorum.com", api_key: str | None = None, ): """ Initialize SBA API client. Args: base_url: Base URL for SBA API (default: production) api_key: Bearer token for API authentication """ self.base_url = base_url self.api_key = api_key or settings.sba_api_key self.timeout = httpx.Timeout(10.0, connect=5.0) def _get_headers(self) -> dict[str, str]: """Get headers with auth token.""" headers = {} if self.api_key: headers["Authorization"] = f"Bearer {self.api_key}" return headers async def get_teams(self, season: int, active_only: bool = True) -> list[dict[str, Any]]: """ Fetch teams from SBA API for a specific season. Args: season: Season number (e.g., 3 for Season 3) active_only: If True, filter out IL teams and teams without divisions Returns: List of team dictionaries with id, name, abbreviation, etc. Example: teams = await client.get_teams(season=3) for team in teams: print(f"{team['abbrev']}: {team['lname']}") """ url = f"{self.base_url}/teams" params = {"season": season} try: async with httpx.AsyncClient(timeout=self.timeout) as client: response = await client.get( url, headers=self._get_headers(), params=params ) response.raise_for_status() data = response.json() teams = data.get("teams", []) if active_only: # Filter out injured list teams (IL suffix) and teams without divisions teams = [ t for t in teams if not t["abbrev"].endswith("IL") and t.get("division") ] logger.info(f"Loaded {len(teams)} teams for season {season}") return teams except httpx.HTTPError as e: logger.error(f"Failed to fetch teams: {e}") raise except Exception as e: logger.error(f"Unexpected error fetching teams: {e}") raise async def get_player(self, player_id: int) -> SbaPlayer: """ Fetch player data from SBA API. Args: player_id: SBA player ID Returns: SbaPlayer instance with all player data Raises: httpx.HTTPError: If API request fails Example: player = await client.get_player(12288) print(f"Name: {player.name}") # "Ronald Acuna Jr" print(f"Positions: {player.get_positions()}") # ['RF'] """ url = f"{self.base_url}/players/{player_id}" try: async with httpx.AsyncClient(timeout=self.timeout) as client: response = await client.get(url, headers=self._get_headers()) response.raise_for_status() data = response.json() player = SbaPlayer.from_api_response(data) logger.info(f"Loaded player {player_id}: {player.name}") return player except httpx.HTTPError as e: logger.error(f"Failed to fetch player {player_id}: {e}") raise except Exception as e: logger.error(f"Unexpected error fetching player {player_id}: {e}") raise async def get_roster(self, team_id: int, season: int) -> list[dict[str, Any]]: """ Fetch roster for a specific team from SBA API. Args: team_id: Team ID season: Season number (e.g., 3 for Season 3) Returns: List of player dictionaries from the SBA API Example: roster = await client.get_roster(team_id=35, season=3) for player in roster: print(f"{player['name']}: {player['position']}") """ url = f"{self.base_url}/players" params = {"season": season, "team_id": team_id} try: async with httpx.AsyncClient(timeout=self.timeout) as client: response = await client.get( url, headers=self._get_headers(), params=params ) response.raise_for_status() data = response.json() players = data.get("players", []) count = data.get("count", len(players)) logger.info(f"Loaded {count} players for team {team_id}, season {season}") return players except httpx.HTTPError as e: logger.error(f"Failed to fetch roster for team {team_id}: {e}") raise except Exception as e: logger.error(f"Unexpected error fetching roster: {e}") raise async def get_players_batch(self, player_ids: list[int]) -> dict[int, SbaPlayer]: """ Fetch multiple players in parallel. Args: player_ids: List of SBA player IDs to fetch Returns: Dictionary mapping player_id to SbaPlayer instance Players that fail to load will be omitted from the result Example: players = await client.get_players_batch([12288, 12289, 12290]) for player_id, player in players.items(): print(f"{player.name}: {player.get_positions()}") """ if not player_ids: return {} results: dict[int, SbaPlayer] = {} async with httpx.AsyncClient(timeout=self.timeout) as client: for player_id in player_ids: try: url = f"{self.base_url}/players/{player_id}" response = await client.get(url) response.raise_for_status() data = response.json() player = SbaPlayer.from_api_response(data) results[player_id] = player except httpx.HTTPError as e: logger.warning(f"Failed to fetch player {player_id}: {e}") # Continue with other players except Exception as e: logger.warning(f"Unexpected error fetching player {player_id}: {e}") # Continue with other players logger.info(f"Loaded {len(results)}/{len(player_ids)} players") return results # Singleton instance sba_api_client = SbaApiClient()