Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ icalevents
python-socketio
python-engineio
websockets
websocket-client
websocket-client
pydantic>=2.11.0
15 changes: 8 additions & 7 deletions src/base_classes/football.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from PIL import Image, ImageDraw, ImageFont
import time
import pytz
from src.config.config_models import RootConfig
from src.base_classes.sports import SportsCore
from src.base_classes.api_extractors import ESPNFootballExtractor
from src.base_classes.data_sources import ESPNDataSource
Expand All @@ -30,7 +31,7 @@ class Football(SportsCore):
'api_base_url': 'https://site.api.espn.com/apis/site/v2/sports/football'
}

def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
def __init__(self, config: RootConfig, display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
super().__init__(config, display_manager, cache_manager, logger, sport_key)

# Initialize football-specific architecture components
Expand Down Expand Up @@ -167,15 +168,15 @@ def _get_weeks_data(self, league: str) -> Optional[Dict]:
return super()._get_weeks_data("football", league)

class FootballLive(Football):
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
def __init__(self, config: RootConfig, display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
super().__init__(config, display_manager, cache_manager, logger, sport_key)
self.update_interval = self.mode_config.get("live_update_interval", 15)
self.update_interval = self.mode_config.live_update_interval
self.no_data_interval = 300
self.last_update = 0
self.live_games = []
self.current_game_index = 0
self.last_game_switch = 0
self.game_display_duration = self.mode_config.get("live_game_duration", 20)
self.game_display_duration = self.mode_config.live_game_duration
self.last_display_update = 0
self.last_log_time = 0
self.log_interval = 300
Expand Down Expand Up @@ -255,7 +256,7 @@ def update(self):
if details and (details["is_live"] or details["is_halftime"]):
# If show_favorite_teams_only is true, only add if it's a favorite.
# Otherwise, add all games.
if self.mode_config.get("show_favorite_teams_only", False):
if self.mode_config.show_favorite_teams_only:
if details["home_abbr"] in self.favorite_teams or details["away_abbr"] in self.favorite_teams:
if self.show_odds:
self._fetch_game_odds(details)
Expand All @@ -276,12 +277,12 @@ def update(self):

if should_log:
if new_live_games:
filter_text = "favorite teams" if self.mode_config.get("show_favorite_teams_only", False) else "all teams"
filter_text = "favorite teams" if self.mode_config.show_favorite_teams_only else "all teams"
self.logger.info(f"Found {len(new_live_games)} live/halftime games for {filter_text}.")
for game_info in new_live_games: # Renamed game to game_info
self.logger.info(f" - {game_info['away_abbr']}@{game_info['home_abbr']} ({game_info.get('status_text', 'N/A')})")
else:
filter_text = "favorite teams" if self.mode_config.get("show_favorite_teams_only", False) else "criteria"
filter_text = "favorite teams" if self.mode_config.show_favorite_teams_only else "criteria"
self.logger.info(f"No live/halftime games found for {filter_text}.")
self.last_log_time = current_time_for_log

Expand Down
62 changes: 30 additions & 32 deletions src/base_classes/sports.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
from pathlib import Path

# Import new architecture components (individual classes will import what they need)
from .api_extractors import ESPNFootballExtractor, ESPNBaseballExtractor, ESPNHockeyExtractor
from .data_sources import ESPNDataSource, MLBAPIDataSource
from src.base_classes.api_extractors import ESPNFootballExtractor, ESPNBaseballExtractor, ESPNHockeyExtractor
from src.base_classes.data_sources import ESPNDataSource, MLBAPIDataSource
from src.dynamic_team_resolver import DynamicTeamResolver
from src.config.config_models import RootConfig, ScoreboardBaseConfig

class SportsCore:
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
def __init__(self, config: RootConfig, display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
self.logger = logger
self.config = config
self.cache_manager = cache_manager
Expand All @@ -38,20 +39,17 @@ def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cach
self.sport_config = None
self.api_extractor = None
self.data_source = None
self.mode_config = config.get(f"{sport_key}_scoreboard", {}) # Changed config key
self.is_enabled = self.mode_config.get("enabled", False)
self.show_odds = self.mode_config.get("show_odds", False)
self.test_mode = self.mode_config.get("test_mode", False)
self.logo_dir = Path(self.mode_config.get("logo_dir", "assets/sports/ncaa_logos")) # Changed logo dir
self.update_interval = self.mode_config.get(
"update_interval_seconds", 60)
self.show_records = self.mode_config.get('show_records', False)
self.show_ranking = self.mode_config.get('show_ranking', False)
self.mode_config: ScoreboardBaseConfig = config.__getattribute__(f"{sport_key}_scoreboard") # Changed config key
self.is_enabled = self.mode_config.enabled
self.show_odds = self.mode_config.show_odds
self.test_mode = self.mode_config.test_mode
self.logo_dir = Path(self.mode_config.logo_dir) # Changed logo dir
self.update_interval = self.mode_config.update_interval_seconds
self.show_records = self.mode_config.show_records
self.show_ranking = self.mode_config.show_ranking
# Number of games to show (instead of time-based windows)
self.recent_games_to_show = self.mode_config.get(
"recent_games_to_show", 5) # Show last 5 games
self.upcoming_games_to_show = self.mode_config.get(
"upcoming_games_to_show", 10) # Show next 10 games
self.recent_games_to_show = self.mode_config.recent_games_to_show
self.upcoming_games_to_show = self.mode_config.upcoming_games_to_show

self.session = requests.Session()
retry_strategy = Retry(
Expand Down Expand Up @@ -81,7 +79,7 @@ def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cach

# Initialize dynamic team resolver and resolve favorite teams
self.dynamic_resolver = DynamicTeamResolver()
raw_favorite_teams = self.mode_config.get("favorite_teams", [])
raw_favorite_teams = self.mode_config.favorite_teams
self.favorite_teams = self.dynamic_resolver.resolve_teams(raw_favorite_teams, sport_key)

# Log dynamic team resolution
Expand All @@ -98,9 +96,9 @@ def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cach
self._rankings_cache_duration = 3600 # Cache rankings for 1 hour

# Initialize background data service
background_config = self.mode_config.get("background_service", {})
if background_config.get("enabled", True): # Default to enabled
max_workers = background_config.get("max_workers", 3)
background_config = self.mode_config.background_service
if background_config and background_config.enabled: # Default to enabled
max_workers = background_config.max_workers
self.background_service = get_background_service(self.cache_manager, max_workers)
self.background_fetch_requests = {} # Track background fetch requests
self.background_enabled = True
Expand Down Expand Up @@ -412,7 +410,7 @@ def _fetch_game_odds(self, game: Dict) -> None:
return

# Check if we should only fetch for favorite teams
is_favorites_only = self.mode_config.get("show_favorite_teams_only", False)
is_favorites_only = self.mode_config.show_favorite_teams_only
if is_favorites_only:
home_abbr = game.get('home_abbr')
away_abbr = game.get('away_abbr')
Expand Down Expand Up @@ -449,7 +447,7 @@ def _fetch_odds(self, game: Dict, sport: str, league: str) -> None:
return

# Check if we should only fetch for favorite teams
is_favorites_only = self.mode_config.get("show_favorite_teams_only", False)
is_favorites_only = self.mode_config.gshow_favorite_teams_only
if is_favorites_only:
home_abbr = game.get('home_abbr')
away_abbr = game.get('away_abbr')
Expand All @@ -463,8 +461,8 @@ def _fetch_odds(self, game: Dict, sport: str, league: str) -> None:
try:
# Determine update interval based on game state
is_live = game.get('status', '').lower() == 'in'
update_interval = self.mode_config.get("live_odds_update_interval", 60) if is_live \
else self.mode_config.get("odds_update_interval", 3600)
update_interval = self.mode_config.live_odds_update_interval if is_live \
else self.mode_config.odds_update_interval

odds_data = self.odds_manager.get_odds(
sport=sport,
Expand All @@ -485,7 +483,7 @@ def _fetch_odds(self, game: Dict, sport: str, league: str) -> None:

def _get_timezone(self):
try:
timezone_str = self.config.get('timezone', 'UTC')
timezone_str = self.config.timezone
return pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
return pytz.utc
Expand Down Expand Up @@ -559,7 +557,7 @@ def _extract_game_details_common(self, game_event: Dict) -> tuple[Dict | None, D
game_time = local_time.strftime("%I:%M%p").lstrip('0')

# Check date format from config
use_short_date_format = self.config.get('display', {}).get('use_short_date_format', False)
use_short_date_format = self.config.display.use_short_date_format
if use_short_date_format:
game_date = local_time.strftime("%-m/%-d")
else:
Expand Down Expand Up @@ -663,13 +661,13 @@ def _get_weeks_data(self, sport: str, league: str) -> Optional[Dict]:
return None

class SportsUpcoming(SportsCore):
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
def __init__(self, config: RootConfig, display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
super().__init__(config, display_manager, cache_manager, logger, sport_key)
self.upcoming_games = [] # Store all fetched upcoming games initially
self.games_list = [] # Filtered list for display (favorite teams)
self.current_game_index = 0
self.last_update = 0
self.update_interval = self.mode_config.get("upcoming_update_interval", 3600) # Check for recent games every hour
self.update_interval = self.mode_config.upcoming_update_interval # Check for recent games every hour
self.last_log_time = 0
self.log_interval = 300
self.last_warning_time = 0
Expand Down Expand Up @@ -713,7 +711,7 @@ def update(self):
# Filter criteria: must be upcoming ('pre' state)
if game and game['is_upcoming']:
# Only fetch odds for games that will be displayed
if self.mode_config.get("show_favorite_teams_only", False):
if self.mode_config.show_favorite_teams_only:
if not self.favorite_teams:
continue
if game['home_abbr'] not in self.favorite_teams and game['away_abbr'] not in self.favorite_teams:
Expand Down Expand Up @@ -771,7 +769,7 @@ def update(self):
self.logger.info(f"Found {favorite_games_found} favorite team upcoming games")

# Filter for favorite teams only if the config is set
if self.mode_config.get("show_favorite_teams_only", False):
if self.mode_config.show_favorite_teams_only:
# Get all games involving favorite teams
favorite_team_games = [game for game in processed_games
if game['home_abbr'] in self.favorite_teams or
Expand Down Expand Up @@ -1035,13 +1033,13 @@ def display(self, force_clear=False):

class SportsRecent(SportsCore):

def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
def __init__(self, config: RootConfig, display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
super().__init__(config, display_manager, cache_manager, logger, sport_key)
self.recent_games = [] # Store all fetched recent games initially
self.games_list = [] # Filtered list for display (favorite teams)
self.current_game_index = 0
self.last_update = 0
self.update_interval = self.mode_config.get("recent_update_interval", 3600) # Check for recent games every hour
self.update_interval = self.mode_config.recent_update_interval # Check for recent games every hour
self.last_game_switch = 0
self.game_display_duration = 15 # Display each recent game for 15 seconds

Expand Down
22 changes: 8 additions & 14 deletions src/clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,19 @@
from typing import Dict, Any
from src.config_manager import ConfigManager
from src.display_manager import DisplayManager
from src.config.config_models import RootConfig

# Get logger without configuring
logger = logging.getLogger(__name__)

class Clock:
def __init__(self, display_manager: DisplayManager = None, config: Dict[str, Any] = None):
if config is not None:
# Use provided config
self.config = config
self.config_manager = None # Not needed when config is provided
else:
# Fallback: create ConfigManager and load config (for standalone usage)
self.config_manager = ConfigManager()
self.config = self.config_manager.load_config()
def __init__(self, display_manager: DisplayManager, config: RootConfig):
# Use the provided display_manager or create a new one if none provided
self.display_manager = display_manager or DisplayManager(self.config.get('display', {}))
self.config = config
self.display_manager = display_manager
logger.info("Clock initialized with display_manager: %s", id(self.display_manager))
self.location = self.config.get('location', {})
self.clock_config = self.config.get('clock', {})
self.location = self.config.location
self.clock_config = self.config.clock
# Use configured timezone if available, otherwise try to determine it
self.timezone = self._get_timezone()
self.last_time = None
Expand All @@ -37,7 +31,7 @@ def __init__(self, display_manager: DisplayManager = None, config: Dict[str, Any

def _get_timezone(self) -> pytz.timezone:
"""Get timezone from the config file."""
config_timezone = self.config.get('timezone', 'UTC')
config_timezone = self.config.timezone
try:
return pytz.timezone(config_timezone)
except pytz.exceptions.UnknownTimeZoneError:
Expand Down Expand Up @@ -137,7 +131,7 @@ def display_time(self, force_clear: bool = False) -> None:
try:
while True:
clock.display_time()
time.sleep(clock.clock_config.get('update_interval', 1))
time.sleep(clock.clock_config.update_interval)
except KeyboardInterrupt:
print("\nClock stopped by user")
finally:
Expand Down
Loading