Add scripts to test models in dev database
This commit is contained in:
parent
9284162682
commit
04a5538447
308
backend/clean_test_data.py
Normal file
308
backend/clean_test_data.py
Normal file
@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database Cleanup Script
|
||||
|
||||
Clear out test data from database tables.
|
||||
Use with caution - this will DELETE data!
|
||||
|
||||
Usage:
|
||||
python clean_test_data.py # Interactive mode
|
||||
python clean_test_data.py --all # Delete all data
|
||||
python clean_test_data.py --game <id> # Delete specific game
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from uuid import UUID
|
||||
from sqlalchemy import select, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database.session import AsyncSessionLocal
|
||||
from app.models.db_models import (
|
||||
Game,
|
||||
Play,
|
||||
Lineup,
|
||||
GameSession,
|
||||
RosterLink,
|
||||
GameCardsetLink
|
||||
)
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatabaseCleaner:
|
||||
"""Clean test data from database."""
|
||||
|
||||
async def count_records(self, session: AsyncSession) -> dict:
|
||||
"""Count records in each table."""
|
||||
counts = {}
|
||||
|
||||
# Count games
|
||||
result = await session.execute(select(Game))
|
||||
counts['games'] = len(list(result.scalars().all()))
|
||||
|
||||
# Count plays
|
||||
result = await session.execute(select(Play))
|
||||
counts['plays'] = len(list(result.scalars().all()))
|
||||
|
||||
# Count lineups
|
||||
result = await session.execute(select(Lineup))
|
||||
counts['lineups'] = len(list(result.scalars().all()))
|
||||
|
||||
# Count roster links
|
||||
result = await session.execute(select(RosterLink))
|
||||
counts['roster_links'] = len(list(result.scalars().all()))
|
||||
|
||||
# Count game sessions
|
||||
result = await session.execute(select(GameSession))
|
||||
counts['game_sessions'] = len(list(result.scalars().all()))
|
||||
|
||||
# Count cardset links
|
||||
result = await session.execute(select(GameCardsetLink))
|
||||
counts['game_cardset_links'] = len(list(result.scalars().all()))
|
||||
|
||||
return counts
|
||||
|
||||
async def delete_game(self, game_id: UUID) -> None:
|
||||
"""
|
||||
Delete a specific game and all related data.
|
||||
|
||||
Cascade deletes will automatically remove:
|
||||
- Plays
|
||||
- Lineups
|
||||
- RosterLinks
|
||||
- GameSession
|
||||
- GameCardsetLinks
|
||||
"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
# Check if game exists
|
||||
result = await session.execute(
|
||||
select(Game).where(Game.id == game_id)
|
||||
)
|
||||
game = result.scalar_one_or_none()
|
||||
|
||||
if not game:
|
||||
logger.warning(f"Game {game_id} not found")
|
||||
return
|
||||
|
||||
logger.info(f"Deleting game {game_id} ({game.league_id})...")
|
||||
|
||||
# Delete game (cascade will handle related records)
|
||||
await session.delete(game)
|
||||
await session.commit()
|
||||
|
||||
logger.info(f"✅ Successfully deleted game {game_id}")
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
logger.error(f"❌ Error deleting game: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
async def delete_all(self, confirm: bool = False) -> None:
|
||||
"""
|
||||
Delete ALL data from all tables.
|
||||
|
||||
WARNING: This is destructive and cannot be undone!
|
||||
"""
|
||||
if not confirm:
|
||||
logger.warning("⚠️ delete_all() requires confirm=True parameter")
|
||||
return
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
logger.info("🗑️ Deleting ALL data from database...")
|
||||
|
||||
# Get counts before deletion
|
||||
before_counts = await self.count_records(session)
|
||||
logger.info(f"Current record counts: {before_counts}")
|
||||
|
||||
# Delete in correct order (children first)
|
||||
logger.info("Deleting plays...")
|
||||
await session.execute(delete(Play))
|
||||
|
||||
logger.info("Deleting lineups...")
|
||||
await session.execute(delete(Lineup))
|
||||
|
||||
logger.info("Deleting roster links...")
|
||||
await session.execute(delete(RosterLink))
|
||||
|
||||
logger.info("Deleting game sessions...")
|
||||
await session.execute(delete(GameSession))
|
||||
|
||||
logger.info("Deleting game cardset links...")
|
||||
await session.execute(delete(GameCardsetLink))
|
||||
|
||||
logger.info("Deleting games...")
|
||||
await session.execute(delete(Game))
|
||||
|
||||
await session.commit()
|
||||
|
||||
# Verify deletion
|
||||
after_counts = await self.count_records(session)
|
||||
logger.info(f"After deletion: {after_counts}")
|
||||
|
||||
logger.info("✅ Successfully deleted all data")
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
logger.error(f"❌ Error deleting data: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
async def list_games(self) -> None:
|
||||
"""List all games in the database."""
|
||||
async with AsyncSessionLocal() as session:
|
||||
result = await session.execute(
|
||||
select(Game).order_by(Game.created_at.desc())
|
||||
)
|
||||
games = result.scalars().all()
|
||||
|
||||
if not games:
|
||||
logger.info("No games found in database")
|
||||
return
|
||||
|
||||
logger.info(f"\n{'='*80}")
|
||||
logger.info(f"Found {len(games)} game(s) in database:")
|
||||
logger.info(f"{'='*80}")
|
||||
|
||||
for game in games:
|
||||
logger.info(f"\nGame ID: {game.id}")
|
||||
logger.info(f" League: {game.league_id}")
|
||||
logger.info(f" Status: {game.status}")
|
||||
logger.info(f" Mode: {game.game_mode}")
|
||||
logger.info(f" Teams: {game.away_team_id} @ {game.home_team_id}")
|
||||
logger.info(f" Score: {game.away_score}-{game.home_score}")
|
||||
if game.current_inning:
|
||||
logger.info(f" State: {game.current_half} {game.current_inning}")
|
||||
logger.info(f" AI: Home={game.home_team_is_ai}, Away={game.away_team_is_ai}")
|
||||
logger.info(f" Created: {game.created_at}")
|
||||
|
||||
async def show_stats(self) -> None:
|
||||
"""Show database statistics."""
|
||||
async with AsyncSessionLocal() as session:
|
||||
counts = await self.count_records(session)
|
||||
|
||||
logger.info(f"\n{'='*80}")
|
||||
logger.info("DATABASE STATISTICS")
|
||||
logger.info(f"{'='*80}")
|
||||
logger.info(f"Games: {counts['games']:>6}")
|
||||
logger.info(f"Plays: {counts['plays']:>6}")
|
||||
logger.info(f"Lineups: {counts['lineups']:>6}")
|
||||
logger.info(f"Roster Links: {counts['roster_links']:>6}")
|
||||
logger.info(f"Game Sessions: {counts['game_sessions']:>6}")
|
||||
logger.info(f"Cardset Links: {counts['game_cardset_links']:>6}")
|
||||
logger.info(f"{'='*80}\n")
|
||||
|
||||
|
||||
async def interactive_menu():
|
||||
"""Interactive menu for cleanup operations."""
|
||||
cleaner = DatabaseCleaner()
|
||||
|
||||
while True:
|
||||
print("\n" + "="*60)
|
||||
print("DATABASE CLEANUP MENU")
|
||||
print("="*60)
|
||||
print("1. Show database statistics")
|
||||
print("2. List all games")
|
||||
print("3. Delete specific game")
|
||||
print("4. Delete ALL data (⚠️ DANGER)")
|
||||
print("5. Exit")
|
||||
print("="*60)
|
||||
|
||||
choice = input("\nEnter your choice (1-5): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
await cleaner.show_stats()
|
||||
|
||||
elif choice == "2":
|
||||
await cleaner.list_games()
|
||||
|
||||
elif choice == "3":
|
||||
game_id_str = input("Enter game UUID: ").strip()
|
||||
try:
|
||||
game_id = UUID(game_id_str)
|
||||
confirm = input(f"Delete game {game_id}? (yes/no): ").strip().lower()
|
||||
if confirm == "yes":
|
||||
await cleaner.delete_game(game_id)
|
||||
else:
|
||||
logger.info("Cancelled")
|
||||
except ValueError:
|
||||
logger.error("Invalid UUID format")
|
||||
|
||||
elif choice == "4":
|
||||
await cleaner.show_stats()
|
||||
print("\n⚠️ WARNING: This will DELETE ALL DATA from the database!")
|
||||
confirm1 = input("Are you absolutely sure? (type 'DELETE ALL'): ").strip()
|
||||
if confirm1 == "DELETE ALL":
|
||||
confirm2 = input("Type 'yes' to confirm: ").strip().lower()
|
||||
if confirm2 == "yes":
|
||||
await cleaner.delete_all(confirm=True)
|
||||
else:
|
||||
logger.info("Cancelled")
|
||||
else:
|
||||
logger.info("Cancelled")
|
||||
|
||||
elif choice == "5":
|
||||
logger.info("Exiting...")
|
||||
break
|
||||
|
||||
else:
|
||||
logger.warning("Invalid choice")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point."""
|
||||
cleaner = DatabaseCleaner()
|
||||
|
||||
# Parse command line arguments
|
||||
if len(sys.argv) == 1:
|
||||
# No arguments - run interactive mode
|
||||
await interactive_menu()
|
||||
|
||||
elif "--help" in sys.argv or "-h" in sys.argv:
|
||||
print(__doc__)
|
||||
|
||||
elif "--stats" in sys.argv:
|
||||
await cleaner.show_stats()
|
||||
|
||||
elif "--list" in sys.argv:
|
||||
await cleaner.list_games()
|
||||
|
||||
elif "--all" in sys.argv:
|
||||
await cleaner.show_stats()
|
||||
print("\n⚠️ WARNING: This will DELETE ALL DATA!")
|
||||
confirm = input("Type 'yes' to confirm: ").strip().lower()
|
||||
if confirm == "yes":
|
||||
await cleaner.delete_all(confirm=True)
|
||||
else:
|
||||
logger.info("Cancelled")
|
||||
|
||||
elif "--game" in sys.argv:
|
||||
idx = sys.argv.index("--game")
|
||||
if idx + 1 < len(sys.argv):
|
||||
try:
|
||||
game_id = UUID(sys.argv[idx + 1])
|
||||
await cleaner.delete_game(game_id)
|
||||
except ValueError:
|
||||
logger.error("Invalid UUID format")
|
||||
else:
|
||||
logger.error("--game requires a UUID argument")
|
||||
|
||||
else:
|
||||
print("Unknown arguments. Use --help for usage information.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("\nInterrupted by user")
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
298
backend/test_db_playground.py
Normal file
298
backend/test_db_playground.py
Normal file
@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Interactive Database Playground
|
||||
|
||||
Test and experiment with database models and operations.
|
||||
Run this script to create test games and explore the database operations.
|
||||
|
||||
Usage:
|
||||
python test_db_playground.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from uuid import uuid4, UUID
|
||||
from typing import Optional
|
||||
|
||||
from app.database.operations import DatabaseOperations
|
||||
from app.models.roster_models import PdRosterLinkData, SbaRosterLinkData
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatabasePlayground:
|
||||
"""Interactive playground for testing database operations."""
|
||||
|
||||
def __init__(self):
|
||||
self.db_ops = DatabaseOperations()
|
||||
self.test_game_id: Optional[UUID] = None
|
||||
|
||||
async def demo_pd_game(self):
|
||||
"""Create and populate a PD league game with roster and lineup."""
|
||||
logger.info("=" * 60)
|
||||
logger.info("DEMO: Creating PD League Game")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Create game
|
||||
self.test_game_id = uuid4()
|
||||
game = await self.db_ops.create_game(
|
||||
game_id=self.test_game_id,
|
||||
league_id="pd",
|
||||
home_team_id=1,
|
||||
away_team_id=2,
|
||||
game_mode="friendly",
|
||||
visibility="public",
|
||||
home_team_is_ai=False,
|
||||
away_team_is_ai=True,
|
||||
ai_difficulty="balanced"
|
||||
)
|
||||
logger.info(f"✅ Created PD game: {game.id}")
|
||||
logger.info(f" Home Team: {game.home_team_id} (Human)")
|
||||
logger.info(f" Away Team: {game.away_team_id} (AI - {game.ai_difficulty})")
|
||||
|
||||
# Add cards to roster
|
||||
logger.info("\n📋 Adding cards to roster...")
|
||||
home_cards = [101, 102, 103, 104, 105, 106, 107, 108, 109]
|
||||
away_cards = [201, 202, 203, 204, 205, 206, 207, 208, 209]
|
||||
|
||||
for card_id in home_cards:
|
||||
roster_link = await self.db_ops.add_pd_roster_card(
|
||||
game_id=self.test_game_id,
|
||||
card_id=card_id,
|
||||
team_id=1
|
||||
)
|
||||
logger.info(f" Added card {card_id} to home roster (link_id={roster_link.id})")
|
||||
|
||||
for card_id in away_cards:
|
||||
roster_link = await self.db_ops.add_pd_roster_card(
|
||||
game_id=self.test_game_id,
|
||||
card_id=card_id,
|
||||
team_id=2
|
||||
)
|
||||
logger.info(f" Added card {card_id} to away roster (link_id={roster_link.id})")
|
||||
|
||||
# Get roster
|
||||
home_roster = await self.db_ops.get_pd_roster(self.test_game_id, team_id=1)
|
||||
away_roster = await self.db_ops.get_pd_roster(self.test_game_id, team_id=2)
|
||||
logger.info(f"\n✅ Home roster: {len(home_roster)} cards")
|
||||
logger.info(f"✅ Away roster: {len(away_roster)} cards")
|
||||
|
||||
# Set lineup
|
||||
logger.info("\n⚾ Setting starting lineup...")
|
||||
positions = ['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF']
|
||||
|
||||
for i, (card_id, position) in enumerate(zip(home_cards, positions), start=1):
|
||||
lineup = await self.db_ops.add_pd_lineup_card(
|
||||
game_id=self.test_game_id,
|
||||
team_id=1,
|
||||
card_id=card_id,
|
||||
position=position,
|
||||
batting_order=i if position != 'P' else None,
|
||||
is_starter=True
|
||||
)
|
||||
logger.info(f" {position}: Card {card_id} (batting {lineup.batting_order or 'N/A'})")
|
||||
|
||||
# Get active lineup
|
||||
home_lineup = await self.db_ops.get_active_lineup(self.test_game_id, team_id=1)
|
||||
logger.info(f"\n✅ Active lineup: {len(home_lineup)} players")
|
||||
|
||||
# Update game state
|
||||
logger.info("\n🎮 Simulating game progress...")
|
||||
await self.db_ops.update_game_state(
|
||||
game_id=self.test_game_id,
|
||||
inning=3,
|
||||
half="bottom",
|
||||
home_score=2,
|
||||
away_score=1,
|
||||
status="active"
|
||||
)
|
||||
logger.info(" Updated to: Bottom 3rd, Home 2 - Away 1")
|
||||
|
||||
# Save a play
|
||||
play_data = {
|
||||
'game_id': self.test_game_id,
|
||||
'play_number': 1,
|
||||
'inning': 3,
|
||||
'half': 'bottom',
|
||||
'outs_before': 1,
|
||||
'batting_order': 3,
|
||||
'away_score': 1,
|
||||
'home_score': 2,
|
||||
'batter_id': home_lineup[2].id,
|
||||
'pitcher_id': home_lineup[0].id,
|
||||
'catcher_id': home_lineup[1].id,
|
||||
'dice_roll': '14+6',
|
||||
'hit_type': 'FB',
|
||||
'result_description': 'Deep fly ball to center field - Home Run!',
|
||||
'outs_recorded': 0,
|
||||
'runs_scored': 1,
|
||||
'pa': 1,
|
||||
'ab': 1,
|
||||
'hit': 1,
|
||||
'homerun': 1,
|
||||
'rbi': 1,
|
||||
'is_go_ahead': False,
|
||||
'complete': True,
|
||||
'locked': False
|
||||
}
|
||||
|
||||
play = await self.db_ops.save_play(play_data)
|
||||
logger.info(f"\n⚾ Saved play #{play.play_number}: {play.result_description}")
|
||||
|
||||
# Load game state
|
||||
logger.info("\n🔄 Loading complete game state...")
|
||||
game_state = await self.db_ops.load_game_state(self.test_game_id)
|
||||
if game_state:
|
||||
logger.info(f"✅ Loaded game state:")
|
||||
logger.info(f" Status: {game_state['game']['status']}")
|
||||
logger.info(f" Score: {game_state['game']['away_score']}-{game_state['game']['home_score']}")
|
||||
logger.info(f" Lineups: {len(game_state['lineups'])} players")
|
||||
logger.info(f" Plays: {len(game_state['plays'])} plays recorded")
|
||||
|
||||
return self.test_game_id
|
||||
|
||||
async def demo_sba_game(self):
|
||||
"""Create and populate an SBA league game with roster and lineup."""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("DEMO: Creating SBA League Game")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Create game
|
||||
game_id = uuid4()
|
||||
game = await self.db_ops.create_game(
|
||||
game_id=game_id,
|
||||
league_id="sba",
|
||||
home_team_id=10,
|
||||
away_team_id=11,
|
||||
game_mode="ranked",
|
||||
visibility="public",
|
||||
home_team_is_ai=False,
|
||||
away_team_is_ai=False
|
||||
)
|
||||
logger.info(f"✅ Created SBA game: {game.id}")
|
||||
logger.info(f" Home Team: {game.home_team_id}")
|
||||
logger.info(f" Away Team: {game.away_team_id}")
|
||||
logger.info(f" Mode: {game.game_mode}")
|
||||
|
||||
# Add players to roster
|
||||
logger.info("\n📋 Adding players to roster...")
|
||||
home_players = [501, 502, 503, 504, 505, 506, 507, 508, 509]
|
||||
away_players = [601, 602, 603, 604, 605, 606, 607, 608, 609]
|
||||
|
||||
for player_id in home_players:
|
||||
roster_link = await self.db_ops.add_sba_roster_player(
|
||||
game_id=game_id,
|
||||
player_id=player_id,
|
||||
team_id=10
|
||||
)
|
||||
logger.info(f" Added player {player_id} to home roster (link_id={roster_link.id})")
|
||||
|
||||
for player_id in away_players:
|
||||
roster_link = await self.db_ops.add_sba_roster_player(
|
||||
game_id=game_id,
|
||||
player_id=player_id,
|
||||
team_id=11
|
||||
)
|
||||
logger.info(f" Added player {player_id} to away roster (link_id={roster_link.id})")
|
||||
|
||||
# Get roster
|
||||
home_roster = await self.db_ops.get_sba_roster(game_id, team_id=10)
|
||||
away_roster = await self.db_ops.get_sba_roster(game_id, team_id=11)
|
||||
logger.info(f"\n✅ Home roster: {len(home_roster)} players")
|
||||
logger.info(f"✅ Away roster: {len(away_roster)} players")
|
||||
|
||||
# Set lineup
|
||||
logger.info("\n⚾ Setting starting lineup...")
|
||||
positions = ['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF']
|
||||
|
||||
for i, (player_id, position) in enumerate(zip(home_players, positions), start=1):
|
||||
lineup = await self.db_ops.add_sba_lineup_player(
|
||||
game_id=game_id,
|
||||
team_id=10,
|
||||
player_id=player_id,
|
||||
position=position,
|
||||
batting_order=i if position != 'P' else None,
|
||||
is_starter=True
|
||||
)
|
||||
logger.info(f" {position}: Player {player_id} (batting {lineup.batting_order or 'N/A'})")
|
||||
|
||||
# Create game session
|
||||
session = await self.db_ops.create_game_session(game_id)
|
||||
logger.info(f"\n✅ Created game session for game: {session.game_id}")
|
||||
|
||||
# Update session snapshot
|
||||
await self.db_ops.update_session_snapshot(
|
||||
game_id=game_id,
|
||||
state_snapshot={'test': 'data', 'connected_users': ['user1', 'user2']}
|
||||
)
|
||||
logger.info("✅ Updated session snapshot")
|
||||
|
||||
return game_id
|
||||
|
||||
async def demo_cleanup(self):
|
||||
"""Demonstrate querying capabilities."""
|
||||
if not self.test_game_id:
|
||||
logger.warning("No test game created yet")
|
||||
return
|
||||
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("DEMO: Query Capabilities")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Get game
|
||||
game = await self.db_ops.get_game(self.test_game_id)
|
||||
if game:
|
||||
logger.info(f"\n📊 Game Details:")
|
||||
logger.info(f" ID: {game.id}")
|
||||
logger.info(f" League: {game.league_id}")
|
||||
logger.info(f" Status: {game.status}")
|
||||
logger.info(f" Inning: {game.current_half} {game.current_inning}")
|
||||
logger.info(f" Score: {game.away_score}-{game.home_score}")
|
||||
|
||||
# Get plays
|
||||
plays = await self.db_ops.get_plays(self.test_game_id)
|
||||
logger.info(f"\n⚾ Plays: {len(plays)} total")
|
||||
for play in plays:
|
||||
logger.info(f" Play #{play.play_number}: {play.result_description}")
|
||||
|
||||
# Get active lineup
|
||||
home_lineup = await self.db_ops.get_active_lineup(self.test_game_id, team_id=1)
|
||||
logger.info(f"\n👥 Active Lineup: {len(home_lineup)} players")
|
||||
for player in sorted(home_lineup, key=lambda x: x.batting_order or 99):
|
||||
logger.info(f" {player.position}: Card {player.card_id} (batting {player.batting_order or 'N/A'})")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run interactive playground demos."""
|
||||
playground = DatabasePlayground()
|
||||
|
||||
try:
|
||||
# Run PD game demo
|
||||
pd_game_id = await playground.demo_pd_game()
|
||||
|
||||
# Run SBA game demo
|
||||
sba_game_id = await playground.demo_sba_game()
|
||||
|
||||
# Show query capabilities
|
||||
await playground.demo_cleanup()
|
||||
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("✅ ALL DEMOS COMPLETED SUCCESSFULLY!")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"\nCreated games:")
|
||||
logger.info(f" PD Game: {pd_game_id}")
|
||||
logger.info(f" SBA Game: {sba_game_id}")
|
||||
logger.info("\nYou can query these games in the database to explore the data!")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"\n❌ Error during demo: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Loading…
Reference in New Issue
Block a user