wip
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user