From 69d546ed6e9fe76535a07ef10233b67a8fb424bf Mon Sep 17 00:00:00 2001 From: Death916 Date: Thu, 24 Apr 2025 03:07:02 -0700 Subject: [PATCH] async chnages to scores and added scores fetching logic to main --- deathclock/deathclock.py | 112 +++++++++++++++++++++---------- utils/scores.py | 141 +++++++++++++++++++++++++-------------- 2 files changed, 168 insertions(+), 85 deletions(-) diff --git a/deathclock/deathclock.py b/deathclock/deathclock.py index 875a31b..721354b 100644 --- a/deathclock/deathclock.py +++ b/deathclock/deathclock.py @@ -8,7 +8,7 @@ from rxconfig import config # --- Import your Weather utility --- from utils.weather import Weather -# from utils.scores import NBAScores, mlbScores +from utils.scores import NBAScores, mlbScores from utils.news import News # from utils.alarm import Alarm import logging @@ -20,29 +20,31 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %( class State(rx.State): - # --- Original state variables (kept as requested) --- + # --- State Variables --- + current_time: str = "" # Note: rx.moment replaces the need for this if used for display alarm_time: str = "" alarms: list = [] news: list = [] # Placeholder - nba_scores: str = "" # Placeholder - mlb_scores: str = "" # Placeholder - - # --- Weather-specific state variables --- + nba_scores: list = [] # Placeholder + mlb_scores: list = [] # Placeholder + _news_client: News | None = None # This will be set in the constructor last_weather_update: str = "Never" - weather_img: str = WEATHER_IMAGE_PATH - # Placeholder for the weather client _weather_client: Weather | None = None # This will be set in the constructor + _mlb_client: list | None = None # This will be set in the constructor + _nba_client: list | None = None # This will be set in the constructor # --- Initialize Utility Client --- def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Initialize the weather client try: - + self._weather_client = Weather() self._news_client = News() + self._mlb_client = mlbScores() + self._nba_client = NBAScores() logging.info("Weather client initialized successfully.") except Exception as e: logging.error(f"Failed to initialize Weather client: {e}", exc_info=True) @@ -50,41 +52,85 @@ class State(rx.State): # Set error state if needed self.weather_img = "/error_placeholder.png" # Provide a placeholder error image self.last_weather_update = "Client Init Error" + self.mlb_scores = "" + self.nba_scores = "" # --- on_load Handler --- async def start_background_tasks(self): """Starts the weather background task when the page loads.""" - rx.remove_local_storage("chakra-ui-color-mode") + rx.remove_local_storage("chakra-ui-color-mode") #trying to test themes remove after logging.info("Triggering background tasks: Weather") # *** FIX: Return a list containing the handler reference *** - return [State.fetch_weather] - + return [State.fetch_weather, State.fetch_sports] + + + # --- Sports Background Task --- + @rx.event(background=True) - async def fetch_news(self): - """Fetches news periodically.""" - # Placeholder for the actual news fetching logic + async def fetch_sports(self): + + + # Fetches sports scores periodically while True: try: - logging.info("Fetching news...") - news_items = await self._news_client.get_news() # This now comes from utils/news.py - if not news_items: - logging.warning("No news items fetched.") + logging.info("Fetching sports scores...") + # Fetch MLB and NBA scores + mlb_scores = await self._mlb_client.get_scores() + logging.info(f"MLB Scores: {mlb_scores}") + # Check if MLB scores are empty + if not mlb_scores: + logging.warning("No MLB scores fetched.") async with self: - self.news = [] - yield # Update frontend - else: - logging.info(f"Fetched {len(news_items)} news items.") - async with self: - self.news = news_items + self.mlb_scores = [] yield - - + nba_scores = await self._nba_client.get_scores() + logging.info(f"NBA Scores: {nba_scores}") + # Check if NBA scores are empty + if not nba_scores: + logging.warning("No NBA scores fetched.") + async with self: + self.nba_scores = [] + yield + + # Update state with fetched scores + async with self: + self.mlb_scores = mlb_scores + self.nba_scores = nba_scores + logging.info(f"Fetched {len(mlb_scores)} MLB scores and {len(nba_scores)} NBA scores.") + yield # Update frontend + except Exception as e: - logging.error(f"Error in fetch_news background task: {e}", exc_info=True) + logging.error(f"Error in fetch_sports background task: {e}", exc_info=True) await asyncio.sleep(500) - + # format sports scores for display + """ + @rx.event(background=True) + async def fetch_news(self): + #Fetches news periodically + # Placeholder for the actual news fetching logic + while True: + try: + logging.info("Fetching news...") + news_items = await self._news_client.get_news() + if not news_items: + logging.warning("No news items fetched.") + async with self: + self.news = [] + yield # Update frontend + else: + logging.info(f"Fetched {len(news_items)} news items.") + async with self: + self.news = news_items + yield + + + except Exception as e: + logging.error(f"Error in fetch_news background task: {e}", exc_info=True) + await asyncio.sleep(500) + + """ # --- Weather Background Task --- @rx.event(background=True) async def fetch_weather(self): @@ -92,7 +138,7 @@ class State(rx.State): # Check if the client initialized correctly if not hasattr(self, '_weather_client') or self._weather_client is None: logging.warning("Weather client not initialized. Stopping fetch_weather task.") - # Optionally update state to reflect this persistent error + async with self: self.last_weather_update = "Error: Weather client unavailable" yield @@ -102,7 +148,7 @@ class State(rx.State): try: logging.info("Attempting to fetch weather screenshot...") # Call the method from the initialized client - img_web_path = self._weather_client.get_weather_screenshot() # This now comes from utils/weather.py + img_web_path = self._weather_client.get_weather_screenshot() if img_web_path: async with self: @@ -124,8 +170,6 @@ class State(rx.State): logging.error(f"Error in fetch_weather background task: {e}", exc_info=True) async with self: # Update state to show error self.last_weather_update = f"Error @ {datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}" - # Optionally reset image to placeholder on error - # self.weather_img = WEATHER_IMAGE_PATH yield await asyncio.sleep(WEATHER_FETCH_INTERVAL) @@ -238,4 +282,4 @@ app.add_page( # The original TypeError is resolved by returning [State.fetch_weather] # from State.start_background_tasks. -# The image display is fixed by binding rx.image src to State.weather_img. \ No newline at end of file +# The image display is fixed by binding rx.image src to State.weather_img. diff --git a/utils/scores.py b/utils/scores.py index 24c5311..64f4947 100644 --- a/utils/scores.py +++ b/utils/scores.py @@ -1,27 +1,40 @@ +# /home/death916/code/python/deathclock/utils/scores.py from nba_api.live.nba.endpoints import scoreboard from datetime import datetime, timedelta import statsapi +import reflex as rx +import logging # Use logging for consistency -class NBAScores: - def __init__(self): - self._scores = [] - - def get_scores(self): +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +class NBAScores(rx.Base): + # Declare attributes at the class level for rx.Base/Pydantic + # These aren't strictly necessary if the class is just a utility + # providing methods, but it aligns with rx.Base inheritance. + # If they were meant to hold state managed by Reflex, they'd be crucial. + # For now, we can remove them or keep them empty if needed later. + # Let's remove __init__ as it's not needed for this structure. + + # No __init__ needed + + async def get_scores(self): # Make async to match usage in State + """Fetches NBA scores and returns them as a list of dicts.""" try: # Get scoreboard data + # Consider running blocking IO in a thread pool if it becomes an issue board = scoreboard.ScoreBoard() data = board.get_dict() - + # Check if we have a valid scoreboard response if 'scoreboard' not in data: - print("No scoreboard data found") + logging.warning("No NBA scoreboard data found in response") return [] - + games = data['scoreboard'].get('games', []) if not games: - print("No games found in scoreboard") + logging.info("No active NBA games found in scoreboard") return [] - + scores_list = [] for game in games: try: @@ -35,62 +48,88 @@ class NBAScores: } scores_list.append(game_data) except KeyError as e: - print(f"Error processing game data: {e}") - continue - - self._scores = scores_list - return self._scores - + logging.error(f"Error processing NBA game data: {e} for game: {game.get('gameId', 'N/A')}") + continue # Skip this game + + # No need to store in self._scores if this is just a utility + return scores_list + except Exception as e: - print(f"Error fetching scores: {e}") - return [] + logging.error(f"Error fetching NBA scores: {e}", exc_info=True) + return [] # Return empty list on error -class mlbScores: - def __init__(self): - self._scores = [] - self._games = [] +class mlbScores(rx.Base): + # Declare attributes at the class level if needed by rx.Base/Pydantic + # _scores: list = [] # Not strictly needed for utility methods + # _games: list = [] # Not strictly needed for utility methods - - def get_games(self): + # No __init__ needed + + async def get_games(self): # Make async + """Fetches MLB games data.""" try: - # Get MLB games data - games = statsapi.schedule() - self._games = games - return self._games + # statsapi might be blocking, consider running in thread executor for async context + # For simplicity now, we call it directly. + # If statsapi has async methods, use those. Otherwise, wrap sync calls: + # games = await rx.call_blocking(statsapi.schedule) + games = statsapi.schedule() # Assuming sync for now + return games except Exception as e: - print(f"Error fetching MLB games: {e}") + logging.error(f"Error fetching MLB games: {e}", exc_info=True) return [] - - def get_scores(self): - games = self.get_games() + + async def get_scores(self): # Make async to match usage in State + """Fetches and formats MLB scores.""" + games = await self.get_games() # Await the async get_games scores_list = [] if not games: - print("No mlb games found") + logging.info("No MLB games found today.") return [] for game in games: try: + # Ensure keys exist, provide defaults if necessary game_data = { - 'home_team': game['home_name'], - 'home_score': game['home_score'], - 'away_team': game['away_name'], - 'away_score': game['away_score'], - 'status': game['status'] + 'home_team': game.get('home_name', 'N/A'), + 'home_score': game.get('home_score', '-'), + 'away_team': game.get('away_name', 'N/A'), + 'away_score': game.get('away_score', '-'), + 'status': game.get('status', 'Scheduled') # Provide default status } scores_list.append(game_data) except KeyError as e: - print(f"Error processing game data: {e}") - continue + # This block might be less necessary with .get() above + logging.error(f"Error processing MLB game data: {e} for game: {game.get('game_id', 'N/A')}") + continue # Skip this game return scores_list # RETURN THE LIST +# Keep the __main__ block for testing if desired, but update calls to be async if needed +# (e.g., using asyncio.run()) if __name__ == "__main__": - scores = NBAScores() - results = scores.get_scores() - - print("\nNBA Scores:") - if results: - for game in results: - print(f"{game['away_team']} {game['away_score']} @ " - f"{game['home_team']} {game['home_score']} " - f"(Status: {game['status']})") - else: - print("No games available") \ No newline at end of file + import asyncio + + async def test_scores(): + nba_client = NBAScores() + nba_results = await nba_client.get_scores() # await async method + + print("\nNBA Scores:") + if nba_results: + for game in nba_results: + print(f"{game['away_team']} {game['away_score']} @ " + f"{game['home_team']} {game['home_score']} " + f"(Status: {game['status']})") + else: + print("No NBA games/scores available") + + mlb_client = mlbScores() + mlb_results = await mlb_client.get_scores() # await async method + + print("\nMLB Scores:") + if mlb_results: + for game in mlb_results: + print(f"{game['away_team']} {game['away_score']} @ " + f"{game['home_team']} {game['home_score']} " + f"(Status: {game['status']})") + else: + print("No MLB games/scores available") + + asyncio.run(test_scores())