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.
- hpde_analytics_cli/__init__.py +4 -0
- hpde_analytics_cli/api/__init__.py +1 -0
- hpde_analytics_cli/api/client.py +325 -0
- hpde_analytics_cli/auth/__init__.py +1 -0
- hpde_analytics_cli/auth/credentials.py +256 -0
- hpde_analytics_cli/auth/oauth.py +529 -0
- hpde_analytics_cli/main.py +360 -0
- hpde_analytics_cli/utils/__init__.py +1 -0
- hpde_analytics_cli/utils/data_export.py +366 -0
- hpde_analytics_cli/utils/field_discovery.py +351 -0
- hpde_analytics_cli/utils/report_generator.py +459 -0
- hpde_analytics_cli-1.0.0.dist-info/METADATA +309 -0
- hpde_analytics_cli-1.0.0.dist-info/RECORD +17 -0
- hpde_analytics_cli-1.0.0.dist-info/WHEEL +5 -0
- hpde_analytics_cli-1.0.0.dist-info/entry_points.txt +2 -0
- hpde_analytics_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- hpde_analytics_cli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -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."""
|