This commit is contained in:
Trey t
2026-01-20 12:25:00 -06:00
parent fbfdf136ae
commit c49206bb7c
6 changed files with 359 additions and 17 deletions

View File

@@ -26,6 +26,9 @@ Examples:
sportstime-parser validate nba --season 2025
sportstime-parser upload nba --season 2025
sportstime-parser status
sportstime-parser purge --environment development
sportstime-parser count --environment development
sportstime-parser upload-static --environment development
""",
)
@@ -179,6 +182,53 @@ Examples:
)
clear_parser.set_defaults(func=cmd_clear)
# Purge subcommand
purge_parser = subparsers.add_parser(
"purge",
help="Delete all records from CloudKit (DESTRUCTIVE)",
description="Delete ALL records from CloudKit. This is destructive and cannot be undone.",
)
purge_parser.add_argument(
"--environment", "-e",
choices=["development", "production"],
default=CLOUDKIT_ENVIRONMENT,
help=f"CloudKit environment (default: {CLOUDKIT_ENVIRONMENT})",
)
purge_parser.add_argument(
"--yes", "-y",
action="store_true",
help="Skip confirmation prompt",
)
purge_parser.set_defaults(func=cmd_purge)
# Count subcommand
count_parser = subparsers.add_parser(
"count",
help="Count records in CloudKit by type",
description="Display count of all record types in CloudKit",
)
count_parser.add_argument(
"--environment", "-e",
choices=["development", "production"],
default=CLOUDKIT_ENVIRONMENT,
help=f"CloudKit environment (default: {CLOUDKIT_ENVIRONMENT})",
)
count_parser.set_defaults(func=cmd_count)
# Upload-static subcommand
upload_static_parser = subparsers.add_parser(
"upload-static",
help="Upload static reference data to CloudKit",
description="Upload league structure, team aliases, stadium aliases, and sports to CloudKit",
)
upload_static_parser.add_argument(
"--environment", "-e",
choices=["development", "production"],
default=CLOUDKIT_ENVIRONMENT,
help=f"CloudKit environment (default: {CLOUDKIT_ENVIRONMENT})",
)
upload_static_parser.set_defaults(func=cmd_upload_static)
return parser
@@ -950,6 +1000,285 @@ def cmd_clear(args: argparse.Namespace) -> int:
return 0
def cmd_purge(args: argparse.Namespace) -> int:
"""Execute the purge command to delete all CloudKit records."""
from .uploaders.cloudkit import CloudKitClient, RecordType
logger = get_logger()
# Check CloudKit configuration
client = CloudKitClient(environment=args.environment)
if not client.is_configured:
logger.error("CloudKit not configured. Check CLOUDKIT_KEY_ID and private key.")
return 1
# Confirmation prompt
if not args.yes:
logger.warning(f"[bold red]WARNING: This will delete ALL records from CloudKit ({args.environment})![/bold red]")
logger.warning("This action cannot be undone.")
logger.info("")
response = input(f"Type 'DELETE {args.environment.upper()}' to confirm: ")
if response != f"DELETE {args.environment.upper()}":
logger.info("Aborted.")
return 1
logger.info(f"Purging all records from CloudKit ({args.environment})...")
logger.info("")
record_types = [
RecordType.GAME,
RecordType.TEAM,
RecordType.STADIUM,
RecordType.TEAM_ALIAS,
RecordType.STADIUM_ALIAS,
RecordType.SPORT,
RecordType.LEAGUE_STRUCTURE,
]
total_deleted = 0
total_failed = 0
for record_type in record_types:
logger.info(f"Fetching {record_type.value} records...")
try:
records = client.fetch_all_records(record_type)
except Exception as e:
logger.error(f" Failed to fetch: {e}")
continue
if not records:
logger.info(f" No {record_type.value} records found")
continue
logger.info(f" Deleting {len(records)} {record_type.value} records...")
try:
result = client.delete_records(record_type, records)
total_deleted += result.success_count
total_failed += result.failure_count
logger.info(f" [green]✓[/green] Deleted: {result.success_count}, Failed: {result.failure_count}")
except Exception as e:
logger.error(f" Failed to delete: {e}")
total_failed += len(records)
logger.info("")
logger.info(f"{'='*50}")
logger.info(f"Total deleted: {total_deleted}")
logger.info(f"Total failed: {total_failed}")
return 0 if total_failed == 0 else 1
def cmd_upload_static(args: argparse.Namespace) -> int:
"""Execute the upload-static command to upload reference data to CloudKit."""
import json
from rich.progress import Progress, SpinnerColumn, TextColumn
from .uploaders.cloudkit import CloudKitClient, RecordType
from .uploaders.diff import RecordDiffer
from .models.aliases import TeamAlias, StadiumAlias
from .models.sport import Sport, LeagueStructure, LeagueStructureType
from .config import SCRIPTS_DIR
logger = get_logger()
# Check CloudKit configuration
client = CloudKitClient(environment=args.environment)
if not client.is_configured:
logger.error("CloudKit not configured. Check CLOUDKIT_KEY_ID and private key.")
return 1
logger.info(f"Uploading static reference data to CloudKit ({args.environment})")
logger.info(f"{'='*50}")
differ = RecordDiffer()
total_uploaded = 0
total_failed = 0
# Define sports (hardcoded since there's no sports.json)
sports = [
Sport(id="MLB", abbreviation="MLB", display_name="Major League Baseball",
icon_name="baseball.fill", color_hex="#002D72", season_start_month=3, season_end_month=11),
Sport(id="NBA", abbreviation="NBA", display_name="National Basketball Association",
icon_name="basketball.fill", color_hex="#1D428A", season_start_month=10, season_end_month=6),
Sport(id="NFL", abbreviation="NFL", display_name="National Football League",
icon_name="football.fill", color_hex="#013369", season_start_month=9, season_end_month=2),
Sport(id="NHL", abbreviation="NHL", display_name="National Hockey League",
icon_name="hockey.puck.fill", color_hex="#000000", season_start_month=10, season_end_month=6),
Sport(id="MLS", abbreviation="MLS", display_name="Major League Soccer",
icon_name="soccerball", color_hex="#80A63A", season_start_month=2, season_end_month=11),
Sport(id="WNBA", abbreviation="WNBA", display_name="Women's National Basketball Association",
icon_name="basketball.fill", color_hex="#FF6600", season_start_month=5, season_end_month=10),
Sport(id="NWSL", abbreviation="NWSL", display_name="National Women's Soccer League",
icon_name="soccerball", color_hex="#003087", season_start_month=3, season_end_month=11),
]
# Upload Sports
logger.info("Uploading Sports...")
try:
remote_sports = client.fetch_all_records(RecordType.SPORT)
except Exception:
remote_sports = []
diff_result = differ.diff_sports(sports, remote_sports)
records_to_upload = diff_result.get_records_to_upload()
if records_to_upload:
result = client.save_records(records_to_upload)
total_uploaded += result.success_count
total_failed += result.failure_count
logger.info(f" [green]✓[/green] Sports: {result.success_count} uploaded, {result.failure_count} failed")
else:
logger.info(f" [dim]-[/dim] Sports: No changes")
# Load and upload League Structures
logger.info("Uploading League Structures...")
league_structure_file = SCRIPTS_DIR / "league_structure.json"
if league_structure_file.exists():
with open(league_structure_file, "r") as f:
data = json.load(f)
structures = []
for d in data:
# Handle "type" vs "structure_type" field name
structure_type = d.get("structure_type") or d.get("type")
structures.append(LeagueStructure(
id=d["id"],
sport=d["sport"],
structure_type=LeagueStructureType(structure_type),
name=d["name"],
abbreviation=d.get("abbreviation"),
parent_id=d.get("parent_id"),
display_order=d.get("display_order", 0),
))
try:
remote_structures = client.fetch_all_records(RecordType.LEAGUE_STRUCTURE)
except Exception:
remote_structures = []
diff_result = differ.diff_league_structures(structures, remote_structures)
records_to_upload = diff_result.get_records_to_upload()
if records_to_upload:
result = client.save_records(records_to_upload)
total_uploaded += result.success_count
total_failed += result.failure_count
logger.info(f" [green]✓[/green] League Structures: {result.success_count} uploaded, {result.failure_count} failed")
else:
logger.info(f" [dim]-[/dim] League Structures: No changes ({len(structures)} unchanged)")
else:
logger.warning(f" [yellow]![/yellow] league_structure.json not found")
# Load and upload Team Aliases
logger.info("Uploading Team Aliases...")
team_aliases_file = SCRIPTS_DIR / "team_aliases.json"
if team_aliases_file.exists():
with open(team_aliases_file, "r") as f:
data = json.load(f)
aliases = [TeamAlias.from_dict(d) for d in data]
try:
remote_aliases = client.fetch_all_records(RecordType.TEAM_ALIAS)
except Exception:
remote_aliases = []
diff_result = differ.diff_team_aliases(aliases, remote_aliases)
records_to_upload = diff_result.get_records_to_upload()
if records_to_upload:
result = client.save_records(records_to_upload)
total_uploaded += result.success_count
total_failed += result.failure_count
logger.info(f" [green]✓[/green] Team Aliases: {result.success_count} uploaded, {result.failure_count} failed")
else:
logger.info(f" [dim]-[/dim] Team Aliases: No changes ({len(aliases)} unchanged)")
else:
logger.warning(f" [yellow]![/yellow] team_aliases.json not found")
# Load and upload Stadium Aliases
logger.info("Uploading Stadium Aliases...")
stadium_aliases_file = SCRIPTS_DIR / "stadium_aliases.json"
if stadium_aliases_file.exists():
with open(stadium_aliases_file, "r") as f:
data = json.load(f)
aliases = [StadiumAlias.from_dict(d) for d in data]
try:
remote_aliases = client.fetch_all_records(RecordType.STADIUM_ALIAS)
except Exception:
remote_aliases = []
diff_result = differ.diff_stadium_aliases(aliases, remote_aliases)
records_to_upload = diff_result.get_records_to_upload()
if records_to_upload:
result = client.save_records(records_to_upload)
total_uploaded += result.success_count
total_failed += result.failure_count
logger.info(f" [green]✓[/green] Stadium Aliases: {result.success_count} uploaded, {result.failure_count} failed")
else:
logger.info(f" [dim]-[/dim] Stadium Aliases: No changes ({len(aliases)} unchanged)")
else:
logger.warning(f" [yellow]![/yellow] stadium_aliases.json not found")
logger.info(f"{'='*50}")
logger.info(f"Total uploaded: {total_uploaded}")
logger.info(f"Total failed: {total_failed}")
return 0 if total_failed == 0 else 1
def cmd_count(args: argparse.Namespace) -> int:
"""Execute the count command to show CloudKit record counts."""
from .uploaders.cloudkit import CloudKitClient, RecordType
logger = get_logger()
# Check CloudKit configuration
client = CloudKitClient(environment=args.environment)
if not client.is_configured:
logger.error("CloudKit not configured. Check CLOUDKIT_KEY_ID and private key.")
return 1
logger.info(f"CloudKit record counts ({args.environment})")
logger.info(f"{'='*50}")
record_types = [
RecordType.GAME,
RecordType.TEAM,
RecordType.STADIUM,
RecordType.TEAM_ALIAS,
RecordType.STADIUM_ALIAS,
RecordType.SPORT,
RecordType.LEAGUE_STRUCTURE,
]
total = 0
errors = []
for record_type in record_types:
try:
records = client.fetch_all_records(record_type)
count = len(records)
total += count
logger.info(f" {record_type.value:<20} {count:>6}")
except Exception as e:
logger.error(f" {record_type.value:<20} [red]Not queryable[/red]")
errors.append(record_type.value)
logger.info(f"{'='*50}")
logger.info(f" {'Total':<20} {total:>6}")
if errors:
logger.info("")
logger.warning(f"[yellow]Records not queryable: {', '.join(errors)}[/yellow]")
logger.warning("[yellow]Enable QUERYABLE index in CloudKit Dashboard[/yellow]")
return 0
def run_cli(argv: Optional[list[str]] = None) -> int:
"""Parse arguments and run the appropriate command."""
parser = create_parser()