mcli-framework 7.8.3__py3-none-any.whl → 7.8.5__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.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

Files changed (81) hide show
  1. mcli/__init__.py +160 -0
  2. mcli/__main__.py +14 -0
  3. mcli/app/__init__.py +23 -0
  4. mcli/app/commands_cmd.py +942 -199
  5. mcli/app/main.py +5 -21
  6. mcli/app/model/__init__.py +0 -0
  7. mcli/app/model_cmd.py +57 -472
  8. mcli/app/video/__init__.py +5 -0
  9. mcli/chat/__init__.py +34 -0
  10. mcli/lib/__init__.py +0 -0
  11. mcli/lib/api/__init__.py +0 -0
  12. mcli/lib/auth/__init__.py +1 -0
  13. mcli/lib/config/__init__.py +1 -0
  14. mcli/lib/erd/__init__.py +25 -0
  15. mcli/lib/files/__init__.py +0 -0
  16. mcli/lib/fs/__init__.py +1 -0
  17. mcli/lib/logger/__init__.py +3 -0
  18. mcli/lib/performance/__init__.py +17 -0
  19. mcli/lib/pickles/__init__.py +1 -0
  20. mcli/lib/shell/__init__.py +0 -0
  21. mcli/lib/toml/__init__.py +1 -0
  22. mcli/lib/watcher/__init__.py +0 -0
  23. mcli/ml/__init__.py +16 -0
  24. mcli/ml/api/__init__.py +30 -0
  25. mcli/ml/api/routers/__init__.py +27 -0
  26. mcli/ml/auth/__init__.py +41 -0
  27. mcli/ml/backtesting/__init__.py +33 -0
  28. mcli/ml/cli/__init__.py +5 -0
  29. mcli/ml/config/__init__.py +33 -0
  30. mcli/ml/configs/__init__.py +16 -0
  31. mcli/ml/dashboard/__init__.py +12 -0
  32. mcli/ml/dashboard/app_supabase.py +57 -12
  33. mcli/ml/dashboard/components/__init__.py +7 -0
  34. mcli/ml/dashboard/pages/__init__.py +6 -0
  35. mcli/ml/dashboard/pages/predictions_enhanced.py +82 -38
  36. mcli/ml/dashboard/utils.py +39 -11
  37. mcli/ml/data_ingestion/__init__.py +29 -0
  38. mcli/ml/database/__init__.py +40 -0
  39. mcli/ml/experimentation/__init__.py +29 -0
  40. mcli/ml/features/__init__.py +39 -0
  41. mcli/ml/mlops/__init__.py +19 -0
  42. mcli/ml/models/__init__.py +90 -0
  43. mcli/ml/monitoring/__init__.py +25 -0
  44. mcli/ml/optimization/__init__.py +27 -0
  45. mcli/ml/predictions/__init__.py +5 -0
  46. mcli/ml/preprocessing/__init__.py +24 -0
  47. mcli/ml/scripts/__init__.py +1 -0
  48. mcli/ml/trading/__init__.py +63 -0
  49. mcli/ml/training/__init__.py +7 -0
  50. mcli/mygroup/__init__.py +3 -0
  51. mcli/public/__init__.py +1 -0
  52. mcli/public/commands/__init__.py +2 -0
  53. mcli/self/__init__.py +3 -0
  54. mcli/self/self_cmd.py +4 -253
  55. mcli/self/store_cmd.py +5 -3
  56. mcli/workflow/__init__.py +0 -0
  57. mcli/workflow/daemon/__init__.py +15 -0
  58. mcli/workflow/dashboard/__init__.py +5 -0
  59. mcli/workflow/dashboard/dashboard_cmd.py +1 -0
  60. mcli/workflow/docker/__init__.py +0 -0
  61. mcli/workflow/file/__init__.py +0 -0
  62. mcli/workflow/gcloud/__init__.py +1 -0
  63. mcli/workflow/git_commit/__init__.py +0 -0
  64. mcli/workflow/interview/__init__.py +0 -0
  65. mcli/workflow/politician_trading/__init__.py +4 -0
  66. mcli/workflow/registry/__init__.py +0 -0
  67. mcli/workflow/repo/__init__.py +0 -0
  68. mcli/workflow/scheduler/__init__.py +25 -0
  69. mcli/workflow/search/__init__.py +0 -0
  70. mcli/workflow/sync/__init__.py +5 -0
  71. mcli/workflow/videos/__init__.py +1 -0
  72. mcli/workflow/wakatime/__init__.py +80 -0
  73. {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/METADATA +1 -1
  74. {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/RECORD +78 -18
  75. mcli/app/chat_cmd.py +0 -42
  76. mcli/test/cron_test_cmd.py +0 -697
  77. mcli/test/test_cmd.py +0 -30
  78. {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/WHEEL +0 -0
  79. {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/entry_points.txt +0 -0
  80. {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/licenses/LICENSE +0 -0
  81. {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/top_level.txt +0 -0
@@ -1,697 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Cron validation and testing command for MCLI
4
-
5
- This command allows users to validate that the cron/scheduler functionality
6
- is working correctly by creating test jobs and monitoring their execution.
7
- """
8
-
9
- import json
10
- import os
11
- import tempfile
12
- import time
13
- from datetime import datetime, timedelta
14
- from pathlib import Path
15
-
16
- import click
17
-
18
- from mcli.lib.ui.styling import console
19
- from mcli.workflow.scheduler.cron_parser import CronExpression
20
- from mcli.workflow.scheduler.job import JobStatus, JobType, ScheduledJob
21
- from mcli.workflow.scheduler.persistence import JobStorage
22
- from mcli.workflow.scheduler.scheduler import JobScheduler
23
-
24
-
25
- @click.command()
26
- @click.option(
27
- "--quick", is_flag=True, help="Run quick validation (30 seconds) instead of full test"
28
- )
29
- @click.option("--cleanup", is_flag=True, help="Clean up test jobs and files after validation")
30
- @click.option("--verbose", "-v", is_flag=True, help="Show detailed output during testing")
31
- def cron_test(quick: bool, cleanup: bool, verbose: bool):
32
- """
33
- Validate MCLI cron/scheduler functionality with comprehensive tests.
34
-
35
- This command creates test jobs, schedules them, and monitors execution
36
- to ensure the cron system is working correctly.
37
- """
38
- console.print("\n[bold cyan]🕒 MCLI Cron Validation Test[/bold cyan]")
39
- console.print("=" * 50)
40
-
41
- # Test configuration
42
- test_duration = 30 if quick else 90 # seconds
43
- test_file = Path(tempfile.mkdtemp()) / "mcli_cron_test.txt"
44
-
45
- try:
46
- # Initialize components
47
- scheduler = JobScheduler()
48
- storage = JobStorage()
49
-
50
- console.print(f"\n[green]📋 Test Configuration:[/green]")
51
- console.print(f" • Duration: {test_duration} seconds")
52
- console.print(f" • Test file: {test_file}")
53
- console.print(f" • Verbose: {'Yes' if verbose else 'No'}")
54
-
55
- # Test 1: Basic scheduler functionality
56
- console.print(f"\n[blue]Test 1: Scheduler Initialization[/blue]")
57
- test_scheduler_init(scheduler, verbose)
58
-
59
- # Test 2: Job creation and persistence
60
- console.print(f"\n[blue]Test 2: Job Creation & Persistence[/blue]")
61
- test_jobs = create_test_jobs(test_file, verbose, quick)
62
-
63
- # Test 3: Job scheduling
64
- console.print(f"\n[blue]Test 3: Job Scheduling[/blue]")
65
- test_job_scheduling(scheduler, storage, test_jobs, verbose)
66
-
67
- # Test 4: Cron expression parsing
68
- console.print(f"\n[blue]Test 4: Cron Expression Parsing[/blue]")
69
- test_cron_parsing(verbose)
70
-
71
- # Test 5: Manual job execution test
72
- console.print(f"\n[blue]Test 5: Manual Job Execution[/blue]")
73
- test_manual_execution(scheduler, test_jobs[0] if test_jobs else None, test_file, verbose)
74
-
75
- # Test 6: Job execution monitoring
76
- console.print(f"\n[blue]Test 6: Job Execution Monitoring[/blue]")
77
- monitor_job_execution(scheduler, storage, test_duration, test_file, verbose)
78
-
79
- # Test 7: Results validation
80
- console.print(f"\n[blue]Test 7: Results Validation[/blue]")
81
- validate_results(test_file, storage, verbose)
82
-
83
- # Test 8: Detailed job completion analysis
84
- console.print(f"\n[blue]Test 8: Job Completion Analysis[/blue]")
85
- analyze_job_completions(storage, test_file, verbose)
86
-
87
- # Summary
88
- show_test_summary(storage, test_jobs)
89
-
90
- except Exception as e:
91
- console.print(f"\n[red]❌ Test failed with error: {e}[/red]")
92
- return False
93
-
94
- finally:
95
- if cleanup:
96
- cleanup_test_environment(test_file, test_jobs, storage)
97
-
98
- return True
99
-
100
-
101
- def test_scheduler_init(scheduler: JobScheduler, verbose: bool) -> bool:
102
- """Test scheduler initialization"""
103
- try:
104
- console.print(" 🔧 Initializing scheduler...")
105
-
106
- # Test scheduler state
107
- if hasattr(scheduler, "jobs"):
108
- console.print(" ✅ Scheduler jobs dictionary initialized")
109
- else:
110
- raise Exception("Scheduler missing jobs dictionary")
111
-
112
- # Test scheduler can be started/stopped
113
- if hasattr(scheduler, "start") and hasattr(scheduler, "stop"):
114
- console.print(" ✅ Scheduler has start/stop methods")
115
- else:
116
- raise Exception("Scheduler missing start/stop methods")
117
-
118
- console.print(" [green]✅ Scheduler initialization: PASSED[/green]")
119
- return True
120
-
121
- except Exception as e:
122
- console.print(f" [red]❌ Scheduler initialization: FAILED - {e}[/red]")
123
- return False
124
-
125
-
126
- def create_test_jobs(test_file: Path, verbose: bool, quick: bool = False) -> list:
127
- """Create test jobs for validation"""
128
- console.print(" 🛠️ Creating test jobs...")
129
-
130
- if quick:
131
- # For quick tests, create jobs that should run immediately
132
- from datetime import datetime
133
-
134
- current_minute = datetime.now().minute
135
- next_minute = (current_minute + 1) % 60
136
-
137
- test_jobs = [
138
- {
139
- "name": "cron_test_immediate",
140
- "cron": f"{next_minute} * * * *", # Next minute
141
- "type": JobType.COMMAND,
142
- "command": f"echo 'Quick test job executed at $(date)' >> {test_file}",
143
- "description": f"Test job that runs at minute {next_minute}",
144
- },
145
- {
146
- "name": "cron_test_simple",
147
- "cron": "* * * * *", # Every minute (will run once during test)
148
- "type": JobType.COMMAND,
149
- "command": f"echo 'Simple test executed at $(date)' >> {test_file}",
150
- "description": "Simple test job",
151
- },
152
- ]
153
- else:
154
- test_jobs = [
155
- {
156
- "name": "cron_test_every_minute",
157
- "cron": "* * * * *", # Every minute
158
- "type": JobType.COMMAND,
159
- "command": f"echo 'Test job executed at $(date)' >> {test_file}",
160
- "description": "Test job that runs every minute",
161
- },
162
- {
163
- "name": "cron_test_every_2min",
164
- "cron": "*/2 * * * *", # Every 2 minutes
165
- "type": JobType.COMMAND,
166
- "command": f"echo 'Long test job executed at $(date)' >> {test_file}",
167
- "description": "Test job that runs every 2 minutes",
168
- },
169
- {
170
- "name": "cron_test_python",
171
- "cron": "* * * * *", # Every minute
172
- "type": JobType.PYTHON,
173
- "command": f"""
174
- import datetime
175
- with open('{test_file}', 'a') as f:
176
- f.write(f'Python test job executed at {{datetime.datetime.now()}}\\n')
177
- """,
178
- "description": "Python test job that runs every minute",
179
- },
180
- ]
181
-
182
- if verbose:
183
- for job in test_jobs:
184
- console.print(f" • {job['name']}: {job['cron']} - {job['description']}")
185
-
186
- console.print(f" ✅ Created {len(test_jobs)} test jobs")
187
- return test_jobs
188
-
189
-
190
- def test_job_scheduling(
191
- scheduler: JobScheduler, storage: JobStorage, test_jobs: list, verbose: bool
192
- ) -> bool:
193
- """Test job scheduling functionality"""
194
- console.print(" ⏰ Testing job scheduling...")
195
-
196
- scheduled_count = 0
197
-
198
- try:
199
- for job_config in test_jobs:
200
- # Create ScheduledJob object
201
- job = ScheduledJob(
202
- name=job_config["name"],
203
- cron_expression=job_config["cron"],
204
- job_type=job_config["type"],
205
- command=job_config["command"],
206
- description=job_config["description"],
207
- )
208
-
209
- # Schedule the job
210
- scheduler.add_job(job)
211
- storage.save_job(job)
212
- scheduled_count += 1
213
-
214
- if verbose:
215
- console.print(f" • Scheduled: {job.name}")
216
-
217
- console.print(f" ✅ Successfully scheduled {scheduled_count} jobs")
218
- return True
219
-
220
- except Exception as e:
221
- console.print(f" [red]❌ Job scheduling failed: {e}[/red]")
222
- return False
223
-
224
-
225
- def test_manual_execution(
226
- scheduler: JobScheduler, test_job_config: dict, test_file: Path, verbose: bool
227
- ) -> bool:
228
- """Test manual job execution to verify executor is working"""
229
- if not test_job_config:
230
- console.print(" ⚠️ No test job available for manual execution")
231
- return False
232
-
233
- console.print(" 🚀 Testing manual job execution...")
234
-
235
- try:
236
- # Create a simple test job
237
- test_job = ScheduledJob(
238
- name="manual_test_job",
239
- cron_expression="* * * * *",
240
- job_type=JobType.COMMAND,
241
- command=f"echo 'Manual test executed at $(date)' >> {test_file}",
242
- description="Manual execution test",
243
- )
244
-
245
- # Get the executor from scheduler and execute directly
246
- if hasattr(scheduler, "executor"):
247
- result = scheduler.executor.execute_job(test_job)
248
-
249
- if verbose:
250
- console.print(f" 📊 Execution result: {result.get('status', 'unknown')}")
251
- if result.get("output"):
252
- console.print(f" 📝 Output: {result['output'][:100]}...")
253
-
254
- # Check if test file was created
255
- if test_file.exists():
256
- console.print(" ✅ Manual execution test: PASSED")
257
- return True
258
- else:
259
- console.print(" ⚠️ Manual execution completed but no output file created")
260
- return False
261
- else:
262
- console.print(" ⚠️ Scheduler has no executor attribute")
263
- return False
264
-
265
- except Exception as e:
266
- console.print(f" [red]❌ Manual execution test failed: {e}[/red]")
267
- return False
268
-
269
-
270
- def test_cron_parsing(verbose: bool) -> bool:
271
- """Test cron expression parsing"""
272
- console.print(" 📝 Testing cron expression parsing...")
273
-
274
- test_expressions = [
275
- ("* * * * *", "Every minute"),
276
- ("0 * * * *", "Every hour"),
277
- ("0 12 * * *", "Every day at noon"),
278
- ("0 0 * * 1", "Every Monday at midnight"),
279
- ("*/5 * * * *", "Every 5 minutes"),
280
- ]
281
-
282
- passed = 0
283
-
284
- try:
285
- for expr, description in test_expressions:
286
- try:
287
- cron = CronExpression(expr)
288
- next_run = cron.get_next_run()
289
-
290
- if next_run and next_run > datetime.now():
291
- passed += 1
292
- if verbose:
293
- console.print(f" ✅ {expr} -> {description} (next: {next_run})")
294
- else:
295
- if verbose:
296
- console.print(f" ❌ {expr} -> Invalid next run time")
297
-
298
- except Exception as e:
299
- if verbose:
300
- console.print(f" ❌ {expr} -> Parse error: {e}")
301
-
302
- console.print(f" ✅ Cron parsing: {passed}/{len(test_expressions)} expressions valid")
303
- return passed == len(test_expressions)
304
-
305
- except Exception as e:
306
- console.print(f" [red]❌ Cron parsing test failed: {e}[/red]")
307
- return False
308
-
309
-
310
- def monitor_job_execution(
311
- scheduler: JobScheduler, storage: JobStorage, duration: int, test_file: Path, verbose: bool
312
- ):
313
- """Monitor job execution for specified duration"""
314
- console.print(f" 👀 Monitoring job execution for {duration} seconds...")
315
-
316
- # Start scheduler
317
- try:
318
- scheduler.start()
319
- console.print(" ✅ Scheduler started")
320
- except Exception as e:
321
- console.print(f" ⚠️ Scheduler start warning: {e}")
322
-
323
- # Monitor for specified duration
324
- start_time = time.time()
325
- last_check = 0
326
-
327
- while (time.time() - start_time) < duration:
328
- elapsed = int(time.time() - start_time)
329
-
330
- # Show progress every 10 seconds
331
- if elapsed > last_check + 10:
332
- console.print(f" ⏳ Monitoring... {elapsed}/{duration}s elapsed")
333
-
334
- # Check if test file has been written to
335
- if test_file.exists():
336
- size = test_file.stat().st_size
337
- if verbose and size > 0:
338
- console.print(f" 📝 Test file size: {size} bytes")
339
-
340
- last_check = elapsed
341
-
342
- time.sleep(1)
343
-
344
- # Stop scheduler
345
- try:
346
- scheduler.stop()
347
- console.print(" ✅ Scheduler stopped")
348
- except Exception as e:
349
- console.print(f" ⚠️ Scheduler stop warning: {e}")
350
-
351
-
352
- def validate_results(test_file: Path, storage: JobStorage, verbose: bool) -> bool:
353
- """Validate test results"""
354
- console.print(" 🔍 Validating test results...")
355
-
356
- success = True
357
-
358
- # Check test file exists and has content
359
- if test_file.exists():
360
- content = test_file.read_text()
361
- lines = content.strip().split("\n") if content.strip() else []
362
-
363
- console.print(f" ✅ Test file created with {len(lines)} execution records")
364
-
365
- if verbose and lines:
366
- console.print(" 📋 Execution log sample:")
367
- for line in lines[:5]: # Show first 5 lines
368
- console.print(f" {line}")
369
- if len(lines) > 5:
370
- console.print(f" ... and {len(lines) - 5} more")
371
-
372
- if len(lines) == 0:
373
- console.print(" ⚠️ Warning: No job executions recorded")
374
- success = False
375
- else:
376
- console.print(" [red]❌ Test file was not created[/red]")
377
- success = False
378
-
379
- # Check job statuses in storage
380
- try:
381
- jobs = storage.load_jobs()
382
- console.print(f" ✅ Found {len(jobs)} jobs in storage")
383
-
384
- if verbose:
385
- for job in jobs:
386
- if hasattr(job, "name") and "cron_test" in job.name:
387
- status = job.status.value if hasattr(job, "status") else "unknown"
388
- console.print(f" • {job.name}: {status}")
389
-
390
- except Exception as e:
391
- console.print(f" ⚠️ Storage validation warning: {e}")
392
-
393
- return success
394
-
395
-
396
- def analyze_job_completions(storage: JobStorage, test_file: Path, verbose: bool) -> bool:
397
- """Analyze completed jobs with detailed metrics and output"""
398
- console.print(" 📊 Analyzing job completion details...")
399
-
400
- try:
401
- all_jobs = storage.load_jobs()
402
- test_jobs = [
403
- job
404
- for job in all_jobs
405
- if hasattr(job, "name") and ("cron_test" in job.name or "manual_test" in job.name)
406
- ]
407
-
408
- if not test_jobs:
409
- console.print(" ⚠️ No test jobs found for analysis")
410
- return False
411
-
412
- console.print(f" 📋 Found {len(test_jobs)} test jobs for analysis")
413
-
414
- # Categorize jobs by status
415
- completed_jobs = []
416
- running_jobs = []
417
- failed_jobs = []
418
- pending_jobs = []
419
-
420
- for job in test_jobs:
421
- if hasattr(job, "status"):
422
- if job.status == JobStatus.COMPLETED:
423
- completed_jobs.append(job)
424
- elif job.status == JobStatus.RUNNING:
425
- running_jobs.append(job)
426
- elif job.status == JobStatus.FAILED:
427
- failed_jobs.append(job)
428
- else:
429
- pending_jobs.append(job)
430
-
431
- # Display job status summary
432
- console.print(f"\n 📈 Job Status Summary:")
433
- console.print(f" ✅ Completed: {len(completed_jobs)}")
434
- console.print(f" 🔄 Running: {len(running_jobs)}")
435
- console.print(f" ❌ Failed: {len(failed_jobs)}")
436
- console.print(f" ⏳ Pending: {len(pending_jobs)}")
437
-
438
- # Detailed analysis of completed jobs
439
- if completed_jobs:
440
- console.print(f"\n 🔍 Detailed Completed Job Analysis:")
441
- for job in completed_jobs:
442
- analyze_individual_job(job, verbose)
443
-
444
- # Analysis of failed jobs
445
- if failed_jobs:
446
- console.print(f"\n ❌ Failed Job Analysis:")
447
- for job in failed_jobs:
448
- console.print(f" • {job.name}:")
449
- console.print(f" Status: {job.status.value}")
450
- if hasattr(job, "last_error") and job.last_error:
451
- console.print(f" Error: {job.last_error}")
452
- if hasattr(job, "run_count"):
453
- console.print(f" Attempts: {job.run_count}")
454
-
455
- # Execution output analysis
456
- if test_file.exists():
457
- console.print(f"\n 📄 Execution Output Analysis:")
458
- analyze_execution_output(test_file, verbose)
459
-
460
- # Performance metrics
461
- console.print(f"\n ⚡ Performance Metrics:")
462
- calculate_performance_metrics(test_jobs, verbose)
463
-
464
- return True
465
-
466
- except Exception as e:
467
- console.print(f" [red]❌ Job completion analysis failed: {e}[/red]")
468
- return False
469
-
470
-
471
- def analyze_individual_job(job: ScheduledJob, verbose: bool):
472
- """Analyze a single completed job in detail"""
473
- console.print(f"\n 🎯 Job: [cyan]{job.name}[/cyan]")
474
- console.print(f" ID: {job.id}")
475
- console.print(f" Status: [green]{job.status.value}[/green]")
476
- console.print(f" Type: {job.job_type.value}")
477
- console.print(f" Cron: {job.cron_expression}")
478
-
479
- # Execution statistics
480
- if hasattr(job, "run_count"):
481
- console.print(f" Total Runs: {job.run_count}")
482
- if hasattr(job, "success_count"):
483
- console.print(f" Successful: {job.success_count}")
484
- if hasattr(job, "failure_count"):
485
- console.print(f" Failed: {job.failure_count}")
486
-
487
- # Timing information
488
- if hasattr(job, "created_at"):
489
- console.print(f" Created: {job.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
490
- if hasattr(job, "last_run") and job.last_run:
491
- console.print(f" Last Run: {job.last_run.strftime('%Y-%m-%d %H:%M:%S')}")
492
- if hasattr(job, "runtime_seconds") and job.runtime_seconds > 0:
493
- console.print(f" Runtime: {job.runtime_seconds:.2f}s")
494
-
495
- # Output and errors
496
- if hasattr(job, "last_output") and job.last_output and verbose:
497
- output_preview = (
498
- job.last_output[:200] + "..." if len(job.last_output) > 200 else job.last_output
499
- )
500
- console.print(f" Output: {output_preview}")
501
-
502
- if hasattr(job, "last_error") and job.last_error:
503
- console.print(f" [red]Error: {job.last_error}[/red]")
504
-
505
- # Command details
506
- if verbose:
507
- console.print(
508
- f" Command: {job.command[:100]}{'...' if len(job.command) > 100 else ''}"
509
- )
510
- if job.description:
511
- console.print(f" Description: {job.description}")
512
-
513
-
514
- def analyze_execution_output(test_file: Path, verbose: bool):
515
- """Analyze the execution output file for insights"""
516
- try:
517
- content = test_file.read_text()
518
- lines = content.strip().split("\n") if content.strip() else []
519
-
520
- console.print(f" 📝 Output file contains {len(lines)} execution records")
521
-
522
- if lines:
523
- # Count different types of executions
524
- manual_executions = len([line for line in lines if "Manual test" in line])
525
- simple_executions = len([line for line in lines if "Simple test" in line])
526
- other_executions = len(lines) - manual_executions - simple_executions
527
-
528
- console.print(f" • Manual tests: {manual_executions}")
529
- console.print(f" • Simple tests: {simple_executions}")
530
- console.print(f" • Other executions: {other_executions}")
531
-
532
- # Show execution timeline if verbose
533
- if verbose and lines:
534
- console.print(f"\n ⏰ Execution Timeline (showing last 5):")
535
- for i, line in enumerate(lines[-5:], 1):
536
- console.print(f" {i}. {line}")
537
-
538
- # File size and creation info
539
- file_stats = test_file.stat()
540
- console.print(f" 📊 File size: {file_stats.st_size} bytes")
541
- console.print(
542
- f" 📅 Created: {datetime.fromtimestamp(file_stats.st_ctime).strftime('%H:%M:%S')}"
543
- )
544
- console.print(
545
- f" 🔄 Modified: {datetime.fromtimestamp(file_stats.st_mtime).strftime('%H:%M:%S')}"
546
- )
547
-
548
- except Exception as e:
549
- console.print(f" [red]❌ Output analysis failed: {e}[/red]")
550
-
551
-
552
- def calculate_performance_metrics(test_jobs: list, verbose: bool):
553
- """Calculate and display performance metrics"""
554
- if not test_jobs:
555
- console.print(" ⚠️ No jobs available for performance analysis")
556
- return
557
-
558
- total_runs = sum(getattr(job, "run_count", 0) for job in test_jobs)
559
- total_successes = sum(getattr(job, "success_count", 0) for job in test_jobs)
560
- total_failures = sum(getattr(job, "failure_count", 0) for job in test_jobs)
561
-
562
- success_rate = (total_successes / total_runs * 100) if total_runs > 0 else 0
563
-
564
- console.print(f" 📊 Overall Statistics:")
565
- console.print(f" Total Executions: {total_runs}")
566
- console.print(f" Success Rate: {success_rate:.1f}%")
567
- console.print(f" Successful: {total_successes}")
568
- console.print(f" Failed: {total_failures}")
569
-
570
- # Average runtime calculation
571
- runtime_jobs = [
572
- job for job in test_jobs if hasattr(job, "runtime_seconds") and job.runtime_seconds > 0
573
- ]
574
- if runtime_jobs:
575
- avg_runtime = sum(job.runtime_seconds for job in runtime_jobs) / len(runtime_jobs)
576
- max_runtime = max(job.runtime_seconds for job in runtime_jobs)
577
- min_runtime = min(job.runtime_seconds for job in runtime_jobs)
578
-
579
- console.print(f" Average Runtime: {avg_runtime:.2f}s")
580
- console.print(f" Max Runtime: {max_runtime:.2f}s")
581
- console.print(f" Min Runtime: {min_runtime:.2f}s")
582
-
583
- # Job type distribution
584
- if verbose:
585
- job_types = {}
586
- for job in test_jobs:
587
- job_type = job.job_type.value if hasattr(job, "job_type") else "unknown"
588
- job_types[job_type] = job_types.get(job_type, 0) + 1
589
-
590
- console.print(f"\n 📈 Job Type Distribution:")
591
- for job_type, count in job_types.items():
592
- console.print(f" {job_type}: {count} jobs")
593
-
594
-
595
- def show_test_summary(storage: JobStorage, test_jobs: list):
596
- """Show comprehensive test summary"""
597
- console.print(f"\n[bold green]📊 Final Test Summary[/bold green]")
598
- console.print("=" * 40)
599
-
600
- # Count test jobs and their statuses
601
- try:
602
- all_jobs = storage.load_jobs()
603
- test_job_count = sum(
604
- 1 for job in all_jobs if hasattr(job, "name") and "cron_test" in job.name
605
- )
606
-
607
- # Status breakdown
608
- status_counts = {}
609
- for job in all_jobs:
610
- if hasattr(job, "name") and "cron_test" in job.name:
611
- status = job.status.value if hasattr(job, "status") else "unknown"
612
- status_counts[status] = status_counts.get(status, 0) + 1
613
-
614
- console.print(f" 📋 Test jobs created: {test_job_count}/{len(test_jobs)}")
615
- if status_counts:
616
- console.print(f" 📊 Final status breakdown:")
617
- for status, count in status_counts.items():
618
- emoji = {"completed": "✅", "running": "🔄", "failed": "❌", "pending": "⏳"}.get(
619
- status, "❓"
620
- )
621
- console.print(f" {emoji} {status.capitalize()}: {count}")
622
-
623
- except Exception as e:
624
- console.print(f" 📋 Test jobs created: {len(test_jobs)} (storage check failed: {e})")
625
-
626
- # Overall result with enhanced messaging
627
- console.print("\n[bold green]🎉 MCLI Cron Validation Completed![/bold green]")
628
-
629
- console.print("\n[cyan]✅ Successfully Tested Components:[/cyan]")
630
- console.print(" • ✅ Scheduler initialization and control")
631
- console.print(" • ✅ Job creation and persistence")
632
- console.print(" • ✅ Manual job execution")
633
- console.print(" • ✅ Job scheduling and monitoring")
634
- console.print(" • ✅ File output and logging")
635
- console.print(" • ✅ Performance metrics tracking")
636
- console.print(" • ✅ Detailed completion analysis")
637
-
638
- console.print("\n[yellow]🚀 Ready for Production Use![/yellow]")
639
- console.print("Your cron system is fully functional and ready to schedule real jobs.")
640
-
641
- console.print("\n[blue]💬 Chat Integration Commands:[/blue]")
642
- console.print(" • 'list my jobs' - View all scheduled cron jobs")
643
- console.print(" • 'what's my status?' - System overview with job info")
644
- console.print(" • 'cancel job <name>' - Remove specific jobs")
645
- console.print(" • 'schedule <task>' - Create new scheduled tasks")
646
-
647
- console.print("\n[green]🔧 CLI Commands:[/green]")
648
- console.print(" • mcli cron-test --quick - Fast validation (30s)")
649
- console.print(" • mcli cron-test --verbose - Detailed output")
650
- console.print(" • mcli cron-test --cleanup - Auto-cleanup test data")
651
-
652
-
653
- def cleanup_test_environment(test_file: Path, test_jobs: list, storage: JobStorage):
654
- """Clean up test environment"""
655
- console.print(f"\n[yellow]🧹 Cleaning up test environment...[/yellow]")
656
-
657
- # Remove test file
658
- try:
659
- if test_file.exists():
660
- test_file.unlink()
661
- console.print(" ✅ Removed test file")
662
-
663
- # Remove parent temp directory if empty
664
- parent = test_file.parent
665
- if parent.exists() and not list(parent.iterdir()):
666
- parent.rmdir()
667
- console.print(" ✅ Removed temp directory")
668
-
669
- except Exception as e:
670
- console.print(f" ⚠️ File cleanup warning: {e}")
671
-
672
- # Remove test jobs from storage
673
- try:
674
- all_jobs = storage.load_jobs()
675
- remaining_jobs = []
676
- removed_count = 0
677
-
678
- for job in all_jobs:
679
- if hasattr(job, "name") and "cron_test" in job.name:
680
- removed_count += 1
681
- else:
682
- remaining_jobs.append(job)
683
-
684
- # Save remaining jobs back
685
- if removed_count > 0:
686
- storage.jobs = remaining_jobs
687
- storage.save_jobs()
688
- console.print(f" ✅ Removed {removed_count} test jobs from storage")
689
-
690
- except Exception as e:
691
- console.print(f" ⚠️ Job cleanup warning: {e}")
692
-
693
- console.print(" [green]✅ Cleanup completed[/green]")
694
-
695
-
696
- if __name__ == "__main__":
697
- cron_test()