hpde-analytics-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ """
2
+ HPDE Analytics CLI: MotorsportsReg API integration for HPDE and Time Trials programs.
3
+
4
+ Entry point for the application.
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ import sys
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+ from dotenv import load_dotenv
15
+
16
+ from hpde_analytics_cli.api.client import create_client_from_oauth
17
+ from hpde_analytics_cli.auth.credentials import CredentialManager
18
+ from hpde_analytics_cli.auth.oauth import MSROAuth, create_oauth_from_env
19
+ from hpde_analytics_cli.utils.data_export import DataExporter
20
+ from hpde_analytics_cli.utils.field_discovery import run_field_discovery
21
+ from hpde_analytics_cli.utils.report_generator import generate_report
22
+
23
+
24
+ def print_profile(profile: Dict[str, Any]) -> None:
25
+ """Print user profile information in a formatted way."""
26
+ print("\n" + "-" * 40)
27
+ print("User Profile")
28
+ print("-" * 40)
29
+
30
+ if "firstName" in profile or "lastName" in profile:
31
+ name = f"{profile.get('firstName', '')} {profile.get('lastName', '')}".strip()
32
+ print(f" Name: {name}")
33
+
34
+ if "email" in profile:
35
+ print(f" Email: {profile['email']}")
36
+
37
+ if "id" in profile:
38
+ print(f" Profile ID: {profile['id']}")
39
+
40
+ orgs = profile.get("organizations", [])
41
+ if orgs:
42
+ print(f"\n Organizations ({len(orgs)}):")
43
+ for org in orgs:
44
+ org_name = org.get("name", "Unknown")
45
+ org_id = org.get("id", "N/A")
46
+ print(f" - {org_name} (ID: {org_id})")
47
+ else:
48
+ print("\n No organizations found")
49
+
50
+ print("-" * 40)
51
+
52
+
53
+ def run_authentication(oauth: MSROAuth, verbose: bool = False) -> Dict[str, Any]:
54
+ """Run the authentication flow and return profile data."""
55
+ profile = oauth.run_auth_flow()
56
+
57
+ if verbose:
58
+ print("\nFull profile response:")
59
+ print(json.dumps(profile, indent=2))
60
+
61
+ print_profile(profile)
62
+ return profile
63
+
64
+
65
+ def fetch_api_data(
66
+ client: Any, event_id: Optional[str] = None, verbose: bool = False
67
+ ) -> Dict[str, Any]:
68
+ """Fetch data from all API endpoints."""
69
+ print("\n" + "=" * 60)
70
+ print("Fetching API Data")
71
+ print("=" * 60)
72
+
73
+ results = client.get_all_endpoint_data(event_id=event_id)
74
+
75
+ if verbose:
76
+ print("\n" + "-" * 40)
77
+ print("Raw API Responses")
78
+ print("-" * 40)
79
+ for endpoint, data in results.items():
80
+ print(f"\n[{endpoint}]")
81
+ print(json.dumps(data, indent=2, default=str)[:2000])
82
+ if len(json.dumps(data, default=str)) > 2000:
83
+ print("... (truncated)")
84
+
85
+ return results
86
+
87
+
88
+ def handle_discover(oauth, args) -> None:
89
+ """Handle field discovery command."""
90
+ print("Running field discovery...")
91
+ if not oauth.has_valid_tokens():
92
+ print("Error: No valid tokens found. Run with --auth first.")
93
+ sys.exit(1)
94
+
95
+ profile = oauth.validate_connection()
96
+ print_profile(profile)
97
+
98
+ client = create_client_from_oauth(oauth, organization_id=args.org_id)
99
+ api_data = fetch_api_data(client, event_id=args.event_id, verbose=args.verbose)
100
+
101
+ output_path = Path(__file__).parent.parent / args.output
102
+ run_field_discovery(api_data, output_path=str(output_path), verbose=args.verbose)
103
+
104
+
105
+ def handle_report(args) -> None:
106
+ """Handle report generation command."""
107
+ print("Generating Time Trials report...")
108
+
109
+ if not args.export_dir:
110
+ print("Error: --export-dir is required for report generation.")
111
+ print("Usage: python -m src.main --report --export-dir <EXPORT_DIR>")
112
+ sys.exit(1)
113
+
114
+ export_dir = Path(args.export_dir)
115
+ if not export_dir.exists():
116
+ print(f"Error: Export directory not found: {export_dir}")
117
+ sys.exit(1)
118
+
119
+ print("\n" + "=" * 60)
120
+ print("Generating Report")
121
+ print("=" * 60)
122
+
123
+ report_output = args.report_file
124
+ if not report_output and args.name:
125
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
126
+ report_output = str(export_dir / f"{args.name}_{timestamp}.xlsx")
127
+
128
+ report_path = generate_report(
129
+ export_dir=str(export_dir),
130
+ output_path=report_output,
131
+ verbose=True,
132
+ )
133
+
134
+ print("\n" + "=" * 60)
135
+ print("Report Complete")
136
+ print("=" * 60)
137
+ print(f"\nReport saved to: {report_path}")
138
+
139
+
140
+ def handle_export(oauth, args) -> None:
141
+ """Handle data export command."""
142
+ print("Exporting event data...")
143
+ if not oauth.has_valid_tokens():
144
+ print("Error: No valid tokens found. Run with --auth first.")
145
+ sys.exit(1)
146
+
147
+ if not args.event_id:
148
+ print("Error: --event-id is required for export.")
149
+ print("Usage: python -m src.main --export --event-id <EVENT_ID>")
150
+ sys.exit(1)
151
+
152
+ profile = oauth.validate_connection()
153
+ print_profile(profile)
154
+
155
+ client = create_client_from_oauth(oauth, organization_id=args.org_id)
156
+
157
+ print("\n" + "=" * 60)
158
+ print("Exporting Data")
159
+ print("=" * 60)
160
+
161
+ if args.output_dir:
162
+ output_dir = Path(args.output_dir)
163
+ else:
164
+ output_dir = Path(__file__).parent.parent / "output"
165
+
166
+ exporter = DataExporter(output_dir=str(output_dir), name=args.name)
167
+ exported_files = exporter.export_all_data(
168
+ client,
169
+ event_id=args.event_id,
170
+ verbose=True,
171
+ )
172
+
173
+ print("\n" + "=" * 60)
174
+ print("Export Complete")
175
+ print("=" * 60)
176
+ if args.name:
177
+ folder_name = f"{args.name}_{exporter.export_timestamp}"
178
+ else:
179
+ folder_name = f"export_{exporter.export_timestamp}"
180
+ print(f"\nFiles exported to: {output_dir / folder_name}")
181
+ print("\nExported files:")
182
+ for file_name, path in exported_files.items():
183
+ print(f" - {file_name}: {path}")
184
+
185
+
186
+ def handle_auth(oauth, args) -> None:
187
+ """Handle authentication-only command."""
188
+ print("Running authentication flow...")
189
+ run_authentication(oauth, verbose=args.verbose)
190
+
191
+
192
+ def handle_full_flow(oauth, args) -> None:
193
+ """Handle full flow (auth + discovery)."""
194
+ print("Running full flow (authentication + field discovery)...")
195
+ run_authentication(oauth, verbose=args.verbose)
196
+
197
+ client = create_client_from_oauth(oauth, organization_id=args.org_id)
198
+ api_data = fetch_api_data(client, event_id=args.event_id, verbose=args.verbose)
199
+
200
+ output_path = Path(__file__).parent.parent / args.output
201
+ run_field_discovery(api_data, output_path=str(output_path), verbose=args.verbose)
202
+
203
+
204
+ def load_environment(verbose: bool = False) -> None:
205
+ """Load environment variables from .env file if it exists."""
206
+ env_path = Path(__file__).parent.parent / ".env"
207
+ if env_path.exists():
208
+ load_dotenv(env_path)
209
+ if verbose:
210
+ print(f"Loaded environment from {env_path}")
211
+
212
+
213
+ def handle_credential_commands(args) -> bool:
214
+ """Handle credential management commands. Returns True if handled."""
215
+ credential_manager = CredentialManager()
216
+
217
+ if args.configure:
218
+ success = credential_manager.configure_interactive()
219
+ sys.exit(0 if success else 1)
220
+
221
+ if args.credential_status:
222
+ credential_manager.show_status()
223
+ sys.exit(0)
224
+
225
+ return False
226
+
227
+
228
+ def create_parser() -> argparse.ArgumentParser:
229
+ """Create and configure the argument parser."""
230
+ parser = argparse.ArgumentParser(
231
+ prog="hpde-analytics-cli",
232
+ description="HPDE Analytics - MotorsportsReg API integration for HPDE and Time Trials programs",
233
+ formatter_class=argparse.RawDescriptionHelpFormatter,
234
+ epilog="""
235
+ Examples:
236
+ hpde-analytics-cli --configure # Set up API credentials securely
237
+ hpde-analytics-cli --auth # Authentication only
238
+ hpde-analytics-cli --export # Export all data (JSON + CSV)
239
+ hpde-analytics-cli --report # Generate TT report from exported data
240
+ hpde-analytics-cli --verbose # Enable verbose output
241
+ """,
242
+ )
243
+
244
+ parser.add_argument(
245
+ "--configure",
246
+ action="store_true",
247
+ help="Configure API credentials (stores securely in system keyring)",
248
+ )
249
+ parser.add_argument(
250
+ "--credential-status",
251
+ action="store_true",
252
+ help="Show current credential configuration status",
253
+ )
254
+ parser.add_argument(
255
+ "--auth",
256
+ action="store_true",
257
+ help="Run authentication flow only",
258
+ )
259
+ parser.add_argument(
260
+ "--discover",
261
+ action="store_true",
262
+ help="Run field discovery only (requires existing auth)",
263
+ )
264
+ parser.add_argument(
265
+ "--export",
266
+ action="store_true",
267
+ help="Export all event data to JSON and CSV files",
268
+ )
269
+ parser.add_argument(
270
+ "--report",
271
+ action="store_true",
272
+ help="Generate Time Trials report from exported data (requires --export-dir)",
273
+ )
274
+ parser.add_argument(
275
+ "--export-dir",
276
+ type=str,
277
+ help="Directory containing exported CSV files (for --report)",
278
+ )
279
+ parser.add_argument(
280
+ "--report-file",
281
+ type=str,
282
+ help="Output path for the report file (for --report, default: saves to export-dir)",
283
+ )
284
+ parser.add_argument(
285
+ "--event-id",
286
+ type=str,
287
+ help="Specific event ID to query for field discovery",
288
+ )
289
+ parser.add_argument(
290
+ "--org-id",
291
+ type=str,
292
+ help="Organization ID to use for API requests (overrides default)",
293
+ )
294
+ parser.add_argument(
295
+ "--output",
296
+ type=str,
297
+ default="output/field_inventory.json",
298
+ help="Output file for field inventory (default: output/field_inventory.json)",
299
+ )
300
+ parser.add_argument(
301
+ "--output-dir",
302
+ type=str,
303
+ help="Directory for export output files (default: output/ in project dir)",
304
+ )
305
+ parser.add_argument(
306
+ "--name",
307
+ type=str,
308
+ help="Custom name for export folder or report file (e.g., 'HPDE_TT_1_2025')",
309
+ )
310
+ parser.add_argument(
311
+ "--verbose",
312
+ "-v",
313
+ action="store_true",
314
+ help="Enable verbose output",
315
+ )
316
+
317
+ return parser
318
+
319
+
320
+ def main():
321
+ """Main entry point."""
322
+ parser = create_parser()
323
+ args = parser.parse_args()
324
+
325
+ load_environment(verbose=args.verbose)
326
+ handle_credential_commands(args)
327
+
328
+ try:
329
+ oauth = create_oauth_from_env()
330
+
331
+ if args.auth:
332
+ handle_auth(oauth, args)
333
+ elif args.discover:
334
+ handle_discover(oauth, args)
335
+ elif args.report:
336
+ handle_report(args)
337
+ elif args.export:
338
+ handle_export(oauth, args)
339
+ else:
340
+ handle_full_flow(oauth, args)
341
+
342
+ print("\nDone!")
343
+
344
+ except ValueError as e:
345
+ print(f"Configuration error: {e}")
346
+ sys.exit(1)
347
+ except KeyboardInterrupt:
348
+ print("\n\nOperation cancelled by user")
349
+ sys.exit(130)
350
+ except Exception as e:
351
+ print(f"Error: {e}")
352
+ if args.verbose:
353
+ import traceback
354
+
355
+ traceback.print_exc()
356
+ sys.exit(1)
357
+
358
+
359
+ if __name__ == "__main__":
360
+ main()
@@ -0,0 +1 @@
1
+ """Utility modules."""