Coverage for manage_teams.py: 0.00%

169 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2026-04-15 13:02 +0000

1#!/usr/bin/env python3 

2""" 

3Team Management CLI Tool 

4 

5This tool manages teams via CSV import/export using the backend API. 

6Supports importing teams from CSV and exporting current teams to CSV. 

7 

8Usage: 

9 python manage_teams.py export-teams <csv_file> # Export teams to CSV 

10 python manage_teams.py import-teams <csv_file> # Import teams from CSV 

11 python manage_teams.py list # List all teams 

12""" 

13 

14import csv 

15import os 

16from pathlib import Path 

17from typing import Any 

18 

19import requests 

20import typer 

21from rich import box 

22from rich.console import Console 

23from rich.progress import Progress, SpinnerColumn, TextColumn 

24from rich.table import Table 

25 

26# Initialize Typer app and Rich console 

27app = typer.Typer(help="Team Management CLI Tool") 

28console = Console() 

29 

30# API Configuration 

31API_URL = os.getenv("API_URL", "http://localhost:8000") 

32 

33 

34# ============================================================================ 

35# Authentication & API Helper Functions 

36# ============================================================================ 

37 

38 

39def get_auth_token() -> str: 

40 """Get authentication token for API requests.""" 

41 # For local development, use admin credentials 

42 env = os.getenv("APP_ENV", "local") 

43 

44 # Load environment file from backend directory 

45 backend_dir = Path(__file__).parent 

46 env_file = backend_dir / f".env.{env}" 

47 

48 if env_file.exists(): 

49 # Load environment variables from file 

50 with open(env_file) as f: 

51 for line in f: 

52 if line.strip() and not line.startswith("#"): 

53 key, value = line.strip().split("=", 1) 

54 os.environ[key] = value 

55 

56 # Try to login as admin user 

57 username = "tom" 

58 password = os.getenv("TEST_USER_PASSWORD_TOM", "admin123") 

59 

60 response = requests.post( 

61 f"{API_URL}/api/auth/login", 

62 json={"username": username, "password": password}, 

63 headers={"Content-Type": "application/json"}, 

64 ) 

65 

66 if response.status_code == 200: 

67 data = response.json() 

68 return data["access_token"] 

69 else: 

70 console.print(f"[red]❌ Authentication failed: {response.text}[/red]") 

71 raise typer.Exit(code=1) 

72 

73 

74def api_request(method: str, endpoint: str, token: str, data: dict[str, Any] | None = None) -> requests.Response: 

75 """Make an authenticated API request.""" 

76 headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 

77 

78 url = f"{API_URL}{endpoint}" 

79 

80 if method.upper() == "GET": 

81 response = requests.get(url, headers=headers) 

82 elif method.upper() == "POST": 

83 response = requests.post(url, headers=headers, json=data) 

84 elif method.upper() == "PUT": 

85 response = requests.put(url, headers=headers, json=data) 

86 elif method.upper() == "DELETE": 

87 response = requests.delete(url, headers=headers) 

88 else: 

89 raise ValueError(f"Unsupported HTTP method: {method}") 

90 

91 return response 

92 

93 

94# ============================================================================ 

95# Data Fetching Functions 

96# ============================================================================ 

97 

98 

99def get_all_teams(token: str) -> list[dict]: 

100 """Fetch all teams from the API.""" 

101 response = api_request("GET", "/api/teams", token) 

102 if response.status_code == 200: 

103 return response.json() 

104 else: 

105 console.print(f"[red]❌ Failed to fetch teams: {response.text}[/red]") 

106 return [] 

107 

108 

109def get_all_clubs(token: str) -> list[dict]: 

110 """Fetch all clubs for lookup.""" 

111 response = api_request("GET", "/api/clubs", token) 

112 if response.status_code == 200: 

113 return response.json() 

114 else: 

115 console.print(f"[red]❌ Failed to fetch clubs: {response.text}[/red]") 

116 return [] 

117 

118 

119def get_all_age_groups(token: str) -> list[dict]: 

120 """Fetch all age groups for lookup.""" 

121 response = api_request("GET", "/api/age-groups", token) 

122 if response.status_code == 200: 

123 return response.json() 

124 else: 

125 console.print(f"[red]❌ Failed to fetch age groups: {response.text}[/red]") 

126 return [] 

127 

128 

129def get_all_match_types(token: str) -> list[dict]: 

130 """Fetch all match types for lookup.""" 

131 response = api_request("GET", "/api/match-types", token) 

132 if response.status_code == 200: 

133 return response.json() 

134 else: 

135 console.print(f"[red]❌ Failed to fetch match types: {response.text}[/red]") 

136 return [] 

137 

138 

139# ============================================================================ 

140# Export Functions 

141# ============================================================================ 

142 

143 

144def determine_team_type(club_name: str, academy_team: bool) -> str: 

145 """Determine team type based on club affiliation and academy status.""" 

146 if not club_name: 

147 return "Guest Team" 

148 elif academy_team: 

149 return "Tournament Team" # Academy teams are typically tournament-focused 

150 else: 

151 return "League Team" 

152 

153 

154@app.command() 

155def export_teams( 

156 csv_file: str, 

157 club: str | None = typer.Option(None, "--club", help="Filter by club name (case-insensitive)"), 

158): 

159 """ 

160 Export all teams to a CSV file. 

161 

162 The CSV will contain: name,city,club_name,team_type,age_groups,match_types,academy_team 

163 

164 Use --club to filter teams by club name (case-insensitive partial match). 

165 """ 

166 console.print("[bold cyan]📤 Team Export Tool[/bold cyan]") 

167 console.print(f"📁 Exporting to: {csv_file}") 

168 if club: 

169 console.print(f"🏟️ Filtering by club: {club}") 

170 console.print("[bold cyan]📤 Team Export Tool[/bold cyan]") 

171 console.print(f"📁 Exporting to: {csv_file}") 

172 

173 # Authenticate 

174 with console.status("[bold yellow]🔐 Authenticating...", spinner="dots"): 

175 token = get_auth_token() 

176 console.print("✅ Authenticated as admin") 

177 

178 # Fetch all data 

179 with console.status("[bold yellow]📡 Fetching teams and related data...", spinner="dots"): 

180 teams = get_all_teams(token) 

181 clubs = get_all_clubs(token) 

182 age_groups = get_all_age_groups(token) 

183 match_types = get_all_match_types(token) 

184 

185 console.print( 

186 f"✅ Found {len(teams)} teams, {len(clubs)} clubs, {len(age_groups)} age groups, {len(match_types)} match types" 

187 ) 

188 

189 # Create lookup dictionaries 

190 club_lookup = {club["id"]: club["name"] for club in clubs} 

191 {ag["id"]: ag["name"] for ag in age_groups} 

192 {mt["id"]: mt["name"] for mt in match_types} 

193 

194 # Filter teams by club if specified 

195 if club: 

196 original_count = len(teams) 

197 teams = [team for team in teams if club_lookup.get(team.get("club_id"), "").lower().find(club.lower()) != -1] 

198 console.print(f"✅ Filtered to {len(teams)} teams from clubs matching '{club}' (was {original_count})") 

199 

200 if not teams: 

201 filter_msg = f" matching '{club}'" if club else "" 

202 console.print(f"[yellow]⚠️ No teams found{filter_msg} to export[/yellow]") 

203 return 

204 

205 # Prepare CSV data 

206 csv_data = [] 

207 processed_teams = 0 

208 

209 with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), console=console) as progress: 

210 task = progress.add_task("[cyan]Processing teams...", total=len(teams)) 

211 

212 for team in teams: 

213 team["id"] 

214 team_name = team["name"] 

215 city = team.get("city", "") 

216 club_id = team.get("club_id") 

217 academy_team = team.get("academy_team", False) 

218 

219 # Get club name 

220 club_name = club_lookup.get(club_id, "") if club_id else "" 

221 

222 # Get age group names from the team's age_groups array 

223 age_groups_list = team.get("age_groups", []) 

224 age_group_names = [ag["name"] for ag in age_groups_list] 

225 age_groups_str = ",".join(sorted(age_group_names)) 

226 

227 # Determine team type based on club and academy status 

228 team_type = determine_team_type(club_name, academy_team) 

229 

230 # For now, default to "Friendly" for match types 

231 # TODO: Fetch actual match type data from team_match_types table 

232 match_types_str = "Friendly" 

233 

234 # Add to CSV data 

235 csv_data.append( 

236 { 

237 "name": team_name, 

238 "city": city, 

239 "club_name": club_name, 

240 "team_type": team_type, 

241 "age_groups": age_groups_str, 

242 "match_types": match_types_str, 

243 "academy_team": str(academy_team).lower(), 

244 } 

245 ) 

246 

247 processed_teams += 1 

248 progress.update( 

249 task, 

250 advance=1, 

251 description=f"[cyan]Processed {processed_teams}/{len(teams)} teams...", 

252 ) 

253 

254 # Write to CSV 

255 try: 

256 with open(csv_file, "w", newline="", encoding="utf-8") as f: 

257 if csv_data: 

258 fieldnames = [ 

259 "name", 

260 "city", 

261 "club_name", 

262 "team_type", 

263 "age_groups", 

264 "match_types", 

265 "academy_team", 

266 ] 

267 writer = csv.DictWriter(f, fieldnames=fieldnames) 

268 writer.writeheader() 

269 writer.writerows(csv_data) 

270 

271 console.print(f"✅ Successfully exported {len(csv_data)} teams to {csv_file}") 

272 

273 # Show sample of exported data 

274 if csv_data: 

275 console.print("\n[bold]Sample exported data:[/bold]") 

276 table = Table(box=box.ROUNDED) 

277 table.add_column("Name", style="cyan") 

278 table.add_column("City", style="magenta") 

279 table.add_column("Club", style="green") 

280 table.add_column("Team Type", style="yellow") 

281 table.add_column("Age Groups", style="blue") 

282 table.add_column("Match Types", style="red") 

283 

284 for row in csv_data[:5]: # Show first 5 rows 

285 table.add_row( 

286 row["name"][:30] + "..." if len(row["name"]) > 30 else row["name"], 

287 row["city"][:20] + "..." if len(row["city"]) > 20 else row["city"], 

288 row["club_name"][:20] + "..." if len(row["club_name"]) > 20 else row["club_name"], 

289 row["team_type"], 

290 row["age_groups"], 

291 row["match_types"], 

292 ) 

293 console.print(table) 

294 

295 except Exception as e: 

296 console.print(f"[red]❌ Failed to write CSV file: {e}[/red]") 

297 raise typer.Exit(code=1) from e 

298 

299 

300# ============================================================================ 

301# List Command 

302# ============================================================================ 

303 

304 

305@app.command() 

306def list(): 

307 """List all teams in a table format.""" 

308 console.print("[bold cyan]📋 Team List[/bold cyan]") 

309 

310 # Authenticate 

311 with console.status("[bold yellow]🔐 Authenticating...", spinner="dots"): 

312 token = get_auth_token() 

313 console.print("✅ Authenticated as admin") 

314 

315 # Fetch data 

316 with console.status("[bold yellow]📡 Fetching teams...", spinner="dots"): 

317 teams = get_all_teams(token) 

318 clubs = get_all_clubs(token) 

319 

320 console.print(f"✅ Found {len(teams)} teams") 

321 

322 if not teams: 

323 console.print("[yellow]⚠️ No teams found[/yellow]") 

324 return 

325 

326 # Create lookup 

327 club_lookup = {club["id"]: club["name"] for club in clubs} 

328 

329 # Display table 

330 table = Table(box=box.ROUNDED) 

331 table.add_column("ID", style="dim", width=6) 

332 table.add_column("Name", style="cyan", width=25) 

333 table.add_column("City", style="magenta", width=20) 

334 table.add_column("Club", style="green", width=20) 

335 table.add_column("Academy", style="yellow", width=8) 

336 

337 for team in teams[:50]: # Limit to first 50 for readability 

338 club_name = club_lookup.get(team.get("club_id"), "") if team.get("club_id") else "" 

339 academy = "Yes" if team.get("academy_team") else "No" 

340 

341 table.add_row( 

342 str(team["id"]), 

343 team["name"][:24] + "..." if len(team["name"]) > 24 else team["name"], 

344 team.get("city", "")[:19] + "..." if len(team.get("city", "")) > 19 else team.get("city", ""), 

345 club_name[:19] + "..." if len(club_name) > 19 else club_name, 

346 academy, 

347 ) 

348 

349 console.print(table) 

350 

351 if len(teams) > 50: 

352 console.print(f"\n[dim]Showing first 50 of {len(teams)} teams[/dim]") 

353 

354 

355if __name__ == "__main__": 

356 app()