mirror of
https://github.com/Death916/deathclock.git
synced 2026-04-10 03:04:40 -07:00
async chnages to scores and added scores fetching logic to main
This commit is contained in:
parent
27367934eb
commit
69d546ed6e
2 changed files with 168 additions and 85 deletions
|
|
@ -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,20 +20,20 @@ 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):
|
||||||
|
|
@ -43,6 +43,8 @@ class State(rx.State):
|
||||||
|
|
||||||
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
|
yield
|
||||||
else:
|
nba_scores = await self._nba_client.get_scores()
|
||||||
logging.info(f"Fetched {len(news_items)} news items.")
|
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:
|
async with self:
|
||||||
self.news = news_items
|
self.nba_scores = []
|
||||||
yield
|
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)
|
||||||
|
|
|
||||||
123
utils/scores.py
123
utils/scores.py
|
|
@ -1,25 +1,38 @@
|
||||||
|
# /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 = []
|
|
||||||
|
|
||||||
def get_scores(self):
|
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:
|
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 = []
|
||||||
|
|
@ -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()
|
|
||||||
|
|
||||||
print("\nNBA Scores:")
|
async def test_scores():
|
||||||
if results:
|
nba_client = NBAScores()
|
||||||
for game in results:
|
nba_results = await nba_client.get_scores() # await async method
|
||||||
print(f"{game['away_team']} {game['away_score']} @ "
|
|
||||||
f"{game['home_team']} {game['home_score']} "
|
print("\nNBA Scores:")
|
||||||
f"(Status: {game['status']})")
|
if nba_results:
|
||||||
else:
|
for game in nba_results:
|
||||||
print("No games available")
|
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())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue