async chnages to scores and added scores fetching logic to main

This commit is contained in:
Death916 2025-04-24 03:07:02 -07:00
parent 27367934eb
commit 69d546ed6e
2 changed files with 168 additions and 85 deletions

View file

@ -8,7 +8,7 @@ from rxconfig import config
# --- Import your Weather utility --- # --- Import your Weather utility ---
from utils.weather import Weather from utils.weather import Weather
# from utils.scores import NBAScores, mlbScores from utils.scores import NBAScores, mlbScores
from utils.news import News from utils.news import News
# from utils.alarm import Alarm # from utils.alarm import Alarm
import logging import logging
@ -20,29 +20,31 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
class State(rx.State): 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 current_time: str = "" # Note: rx.moment replaces the need for this if used for display
alarm_time: str = "" alarm_time: str = ""
alarms: list = [] alarms: list = []
news: list = [] # Placeholder news: list = [] # Placeholder
nba_scores: str = "" # Placeholder nba_scores: list = [] # Placeholder
mlb_scores: str = "" # Placeholder mlb_scores: list = [] # Placeholder
_news_client: News | None = None # This will be set in the constructor
# --- Weather-specific state variables ---
last_weather_update: str = "Never" last_weather_update: str = "Never"
weather_img: str = WEATHER_IMAGE_PATH weather_img: str = WEATHER_IMAGE_PATH
# Placeholder for the weather client
_weather_client: Weather | None = None # This will be set in the constructor _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 --- # --- Initialize Utility Client ---
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Initialize the weather client # Initialize the weather client
try: try:
self._weather_client = Weather() self._weather_client = Weather()
self._news_client = News() self._news_client = News()
self._mlb_client = mlbScores()
self._nba_client = NBAScores()
logging.info("Weather client initialized successfully.") logging.info("Weather client initialized successfully.")
except Exception as e: except Exception as e:
logging.error(f"Failed to initialize Weather client: {e}", exc_info=True) 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 # Set error state if needed
self.weather_img = "/error_placeholder.png" # Provide a placeholder error image self.weather_img = "/error_placeholder.png" # Provide a placeholder error image
self.last_weather_update = "Client Init Error" self.last_weather_update = "Client Init Error"
self.mlb_scores = ""
self.nba_scores = ""
# --- on_load Handler --- # --- on_load Handler ---
async def start_background_tasks(self): async def start_background_tasks(self):
"""Starts the weather background task when the page loads.""" """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") logging.info("Triggering background tasks: Weather")
# *** FIX: Return a list containing the handler reference *** # *** 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) @rx.event(background=True)
async def fetch_news(self): async def fetch_sports(self):
"""Fetches news periodically."""
# Placeholder for the actual news fetching logic
# Fetches sports scores periodically
while True: while True:
try: try:
logging.info("Fetching news...") logging.info("Fetching sports scores...")
news_items = await self._news_client.get_news() # This now comes from utils/news.py # Fetch MLB and NBA scores
if not news_items: mlb_scores = await self._mlb_client.get_scores()
logging.warning("No news items fetched.") 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: async with self:
self.news = [] self.mlb_scores = []
yield # Update frontend
else:
logging.info(f"Fetched {len(news_items)} news items.")
async with self:
self.news = news_items
yield 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: 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) 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 --- # --- Weather Background Task ---
@rx.event(background=True) @rx.event(background=True)
async def fetch_weather(self): async def fetch_weather(self):
@ -92,7 +138,7 @@ class State(rx.State):
# Check if the client initialized correctly # Check if the client initialized correctly
if not hasattr(self, '_weather_client') or self._weather_client is None: if not hasattr(self, '_weather_client') or self._weather_client is None:
logging.warning("Weather client not initialized. Stopping fetch_weather task.") logging.warning("Weather client not initialized. Stopping fetch_weather task.")
# Optionally update state to reflect this persistent error
async with self: async with self:
self.last_weather_update = "Error: Weather client unavailable" self.last_weather_update = "Error: Weather client unavailable"
yield yield
@ -102,7 +148,7 @@ class State(rx.State):
try: try:
logging.info("Attempting to fetch weather screenshot...") logging.info("Attempting to fetch weather screenshot...")
# Call the method from the initialized client # 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: if img_web_path:
async with self: async with self:
@ -124,8 +170,6 @@ class State(rx.State):
logging.error(f"Error in fetch_weather background task: {e}", exc_info=True) logging.error(f"Error in fetch_weather background task: {e}", exc_info=True)
async with self: # Update state to show error async with self: # Update state to show error
self.last_weather_update = f"Error @ {datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}" 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 yield
await asyncio.sleep(WEATHER_FETCH_INTERVAL) await asyncio.sleep(WEATHER_FETCH_INTERVAL)
@ -238,4 +282,4 @@ app.add_page(
# The original TypeError is resolved by returning [State.fetch_weather] # The original TypeError is resolved by returning [State.fetch_weather]
# from State.start_background_tasks. # from State.start_background_tasks.
# The image display is fixed by binding rx.image src to State.weather_img. # The image display is fixed by binding rx.image src to State.weather_img.

View file

@ -1,27 +1,40 @@
# /home/death916/code/python/deathclock/utils/scores.py
from nba_api.live.nba.endpoints import scoreboard from nba_api.live.nba.endpoints import scoreboard
from datetime import datetime, timedelta from datetime import datetime, timedelta
import statsapi import statsapi
import reflex as rx
import logging # Use logging for consistency
class NBAScores: logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def __init__(self):
self._scores = [] class NBAScores(rx.Base):
# Declare attributes at the class level for rx.Base/Pydantic
def get_scores(self): # 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: try:
# Get scoreboard data # Get scoreboard data
# Consider running blocking IO in a thread pool if it becomes an issue
board = scoreboard.ScoreBoard() board = scoreboard.ScoreBoard()
data = board.get_dict() data = board.get_dict()
# Check if we have a valid scoreboard response # Check if we have a valid scoreboard response
if 'scoreboard' not in data: if 'scoreboard' not in data:
print("No scoreboard data found") logging.warning("No NBA scoreboard data found in response")
return [] return []
games = data['scoreboard'].get('games', []) games = data['scoreboard'].get('games', [])
if not games: if not games:
print("No games found in scoreboard") logging.info("No active NBA games found in scoreboard")
return [] return []
scores_list = [] scores_list = []
for game in games: for game in games:
try: try:
@ -35,62 +48,88 @@ class NBAScores:
} }
scores_list.append(game_data) scores_list.append(game_data)
except KeyError as e: except KeyError as e:
print(f"Error processing game data: {e}") logging.error(f"Error processing NBA game data: {e} for game: {game.get('gameId', 'N/A')}")
continue continue # Skip this game
self._scores = scores_list # No need to store in self._scores if this is just a utility
return self._scores return scores_list
except Exception as e: except Exception as e:
print(f"Error fetching scores: {e}") logging.error(f"Error fetching NBA scores: {e}", exc_info=True)
return [] return [] # Return empty list on error
class mlbScores: class mlbScores(rx.Base):
def __init__(self): # Declare attributes at the class level if needed by rx.Base/Pydantic
self._scores = [] # _scores: list = [] # Not strictly needed for utility methods
self._games = [] # _games: list = [] # Not strictly needed for utility methods
# No __init__ needed
def get_games(self):
async def get_games(self): # Make async
"""Fetches MLB games data."""
try: try:
# Get MLB games data # statsapi might be blocking, consider running in thread executor for async context
games = statsapi.schedule() # For simplicity now, we call it directly.
self._games = games # If statsapi has async methods, use those. Otherwise, wrap sync calls:
return self._games # games = await rx.call_blocking(statsapi.schedule)
games = statsapi.schedule() # Assuming sync for now
return games
except Exception as e: except Exception as e:
print(f"Error fetching MLB games: {e}") logging.error(f"Error fetching MLB games: {e}", exc_info=True)
return [] return []
def get_scores(self): async def get_scores(self): # Make async to match usage in State
games = self.get_games() """Fetches and formats MLB scores."""
games = await self.get_games() # Await the async get_games
scores_list = [] scores_list = []
if not games: if not games:
print("No mlb games found") logging.info("No MLB games found today.")
return [] return []
for game in games: for game in games:
try: try:
# Ensure keys exist, provide defaults if necessary
game_data = { game_data = {
'home_team': game['home_name'], 'home_team': game.get('home_name', 'N/A'),
'home_score': game['home_score'], 'home_score': game.get('home_score', '-'),
'away_team': game['away_name'], 'away_team': game.get('away_name', 'N/A'),
'away_score': game['away_score'], 'away_score': game.get('away_score', '-'),
'status': game['status'] 'status': game.get('status', 'Scheduled') # Provide default status
} }
scores_list.append(game_data) scores_list.append(game_data)
except KeyError as e: except KeyError as e:
print(f"Error processing game data: {e}") # This block might be less necessary with .get() above
continue 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 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__": if __name__ == "__main__":
scores = NBAScores() import asyncio
results = scores.get_scores()
async def test_scores():
print("\nNBA Scores:") nba_client = NBAScores()
if results: nba_results = await nba_client.get_scores() # await async method
for game in results:
print(f"{game['away_team']} {game['away_score']} @ " print("\nNBA Scores:")
f"{game['home_team']} {game['home_score']} " if nba_results:
f"(Status: {game['status']})") for game in nba_results:
else: print(f"{game['away_team']} {game['away_score']} @ "
print("No games available") 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())