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.
- mcli/__init__.py +160 -0
- mcli/__main__.py +14 -0
- mcli/app/__init__.py +23 -0
- mcli/app/commands_cmd.py +942 -199
- mcli/app/main.py +5 -21
- mcli/app/model/__init__.py +0 -0
- mcli/app/model_cmd.py +57 -472
- mcli/app/video/__init__.py +5 -0
- mcli/chat/__init__.py +34 -0
- mcli/lib/__init__.py +0 -0
- mcli/lib/api/__init__.py +0 -0
- mcli/lib/auth/__init__.py +1 -0
- mcli/lib/config/__init__.py +1 -0
- mcli/lib/erd/__init__.py +25 -0
- mcli/lib/files/__init__.py +0 -0
- mcli/lib/fs/__init__.py +1 -0
- mcli/lib/logger/__init__.py +3 -0
- mcli/lib/performance/__init__.py +17 -0
- mcli/lib/pickles/__init__.py +1 -0
- mcli/lib/shell/__init__.py +0 -0
- mcli/lib/toml/__init__.py +1 -0
- mcli/lib/watcher/__init__.py +0 -0
- mcli/ml/__init__.py +16 -0
- mcli/ml/api/__init__.py +30 -0
- mcli/ml/api/routers/__init__.py +27 -0
- mcli/ml/auth/__init__.py +41 -0
- mcli/ml/backtesting/__init__.py +33 -0
- mcli/ml/cli/__init__.py +5 -0
- mcli/ml/config/__init__.py +33 -0
- mcli/ml/configs/__init__.py +16 -0
- mcli/ml/dashboard/__init__.py +12 -0
- mcli/ml/dashboard/app_supabase.py +57 -12
- mcli/ml/dashboard/components/__init__.py +7 -0
- mcli/ml/dashboard/pages/__init__.py +6 -0
- mcli/ml/dashboard/pages/predictions_enhanced.py +82 -38
- mcli/ml/dashboard/utils.py +39 -11
- mcli/ml/data_ingestion/__init__.py +29 -0
- mcli/ml/database/__init__.py +40 -0
- mcli/ml/experimentation/__init__.py +29 -0
- mcli/ml/features/__init__.py +39 -0
- mcli/ml/mlops/__init__.py +19 -0
- mcli/ml/models/__init__.py +90 -0
- mcli/ml/monitoring/__init__.py +25 -0
- mcli/ml/optimization/__init__.py +27 -0
- mcli/ml/predictions/__init__.py +5 -0
- mcli/ml/preprocessing/__init__.py +24 -0
- mcli/ml/scripts/__init__.py +1 -0
- mcli/ml/trading/__init__.py +63 -0
- mcli/ml/training/__init__.py +7 -0
- mcli/mygroup/__init__.py +3 -0
- mcli/public/__init__.py +1 -0
- mcli/public/commands/__init__.py +2 -0
- mcli/self/__init__.py +3 -0
- mcli/self/self_cmd.py +4 -253
- mcli/self/store_cmd.py +5 -3
- mcli/workflow/__init__.py +0 -0
- mcli/workflow/daemon/__init__.py +15 -0
- mcli/workflow/dashboard/__init__.py +5 -0
- mcli/workflow/dashboard/dashboard_cmd.py +1 -0
- mcli/workflow/docker/__init__.py +0 -0
- mcli/workflow/file/__init__.py +0 -0
- mcli/workflow/gcloud/__init__.py +1 -0
- mcli/workflow/git_commit/__init__.py +0 -0
- mcli/workflow/interview/__init__.py +0 -0
- mcli/workflow/politician_trading/__init__.py +4 -0
- mcli/workflow/registry/__init__.py +0 -0
- mcli/workflow/repo/__init__.py +0 -0
- mcli/workflow/scheduler/__init__.py +25 -0
- mcli/workflow/search/__init__.py +0 -0
- mcli/workflow/sync/__init__.py +5 -0
- mcli/workflow/videos/__init__.py +1 -0
- mcli/workflow/wakatime/__init__.py +80 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/METADATA +1 -1
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/RECORD +78 -18
- mcli/app/chat_cmd.py +0 -42
- mcli/test/cron_test_cmd.py +0 -697
- mcli/test/test_cmd.py +0 -30
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/WHEEL +0 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/top_level.txt +0 -0
mcli/test/cron_test_cmd.py
DELETED
|
@@ -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()
|