runnable 0.37.0__py3-none-any.whl → 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.
Potentially problematic release.
This version of runnable might be problematic. Click here for more details.
- runnable/cli.py +156 -0
- runnable/gantt.py +1141 -0
- runnable/graph.py +1 -1
- runnable/parameters.py +106 -49
- runnable-1.0.0.dist-info/METADATA +122 -0
- {runnable-0.37.0.dist-info → runnable-1.0.0.dist-info}/RECORD +9 -8
- runnable-0.37.0.dist-info/METADATA +0 -264
- {runnable-0.37.0.dist-info → runnable-1.0.0.dist-info}/WHEEL +0 -0
- {runnable-0.37.0.dist-info → runnable-1.0.0.dist-info}/entry_points.txt +0 -0
- {runnable-0.37.0.dist-info → runnable-1.0.0.dist-info}/licenses/LICENSE +0 -0
runnable/cli.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
3
5
|
from typing import Annotated
|
|
4
6
|
|
|
5
7
|
import typer
|
|
6
8
|
|
|
7
9
|
from runnable import defaults, entrypoints
|
|
10
|
+
from runnable.gantt import SimpleVisualizer, generate_html_timeline, visualize_simple
|
|
8
11
|
|
|
9
12
|
logger = logging.getLogger(defaults.LOGGER_NAME)
|
|
10
13
|
|
|
@@ -274,5 +277,158 @@ def execute_job(
|
|
|
274
277
|
)
|
|
275
278
|
|
|
276
279
|
|
|
280
|
+
@app.command()
|
|
281
|
+
def timeline(
|
|
282
|
+
run_id_or_path: Annotated[
|
|
283
|
+
str, typer.Argument(help="Run ID to visualize, or path to JSON run log file")
|
|
284
|
+
],
|
|
285
|
+
output: Annotated[
|
|
286
|
+
str,
|
|
287
|
+
typer.Option("--output", "-o", help="Output HTML file path"),
|
|
288
|
+
] = "",
|
|
289
|
+
console: Annotated[
|
|
290
|
+
bool,
|
|
291
|
+
typer.Option(
|
|
292
|
+
"--console/--no-console",
|
|
293
|
+
help="Show console timeline output (default: true)",
|
|
294
|
+
),
|
|
295
|
+
] = True,
|
|
296
|
+
open_browser: Annotated[
|
|
297
|
+
bool,
|
|
298
|
+
typer.Option(
|
|
299
|
+
"--open/--no-open",
|
|
300
|
+
help="Automatically open the generated file in default browser",
|
|
301
|
+
),
|
|
302
|
+
] = True,
|
|
303
|
+
log_level: Annotated[
|
|
304
|
+
LogLevel,
|
|
305
|
+
typer.Option(
|
|
306
|
+
"--log-level",
|
|
307
|
+
help="The log level",
|
|
308
|
+
show_default=True,
|
|
309
|
+
case_sensitive=False,
|
|
310
|
+
),
|
|
311
|
+
] = LogLevel.WARNING,
|
|
312
|
+
):
|
|
313
|
+
"""
|
|
314
|
+
Visualize pipeline execution as an interactive timeline.
|
|
315
|
+
|
|
316
|
+
This command creates lightweight timeline visualizations that effectively
|
|
317
|
+
show composite nodes (parallel, map, conditional) with hierarchical structure,
|
|
318
|
+
timing information, and execution metadata.
|
|
319
|
+
|
|
320
|
+
The new visualization system provides:
|
|
321
|
+
- Clean console output with hierarchical display
|
|
322
|
+
- Interactive HTML with hover tooltips and expandable sections
|
|
323
|
+
- Proper support for all composite pipeline types
|
|
324
|
+
- Rich metadata including commands, parameters, and catalog operations
|
|
325
|
+
|
|
326
|
+
By default, shows console output AND generates HTML file with browser opening.
|
|
327
|
+
|
|
328
|
+
Input Options:
|
|
329
|
+
- Run ID: Looks up JSON file in .run_log_store/ directory
|
|
330
|
+
- JSON Path: Direct path to run log JSON file (flexible for any config)
|
|
331
|
+
|
|
332
|
+
Examples:
|
|
333
|
+
# Using Run ID (looks in .run_log_store/)
|
|
334
|
+
runnable timeline forgiving-joliot-0645 # Console + HTML + browser
|
|
335
|
+
runnable timeline parallel-run --output custom.html # Console + custom HTML + browser
|
|
336
|
+
|
|
337
|
+
# Using JSON file path (any location)
|
|
338
|
+
runnable timeline /path/to/my-run.json # Console + HTML + browser
|
|
339
|
+
runnable timeline ../logs/pipeline-run.json --no-open # Console + HTML, no browser
|
|
340
|
+
runnable timeline ~/experiments/run.json --no-console # HTML + browser only
|
|
341
|
+
|
|
342
|
+
# Other options
|
|
343
|
+
runnable timeline complex-pipeline --no-open # Console + HTML, no browser
|
|
344
|
+
runnable timeline simple-run --no-console --no-open # HTML only, no browser
|
|
345
|
+
"""
|
|
346
|
+
logger.setLevel(log_level.value)
|
|
347
|
+
|
|
348
|
+
# Determine if input is a file path or run ID
|
|
349
|
+
if os.path.exists(run_id_or_path) or run_id_or_path.endswith(".json"):
|
|
350
|
+
# Input is a file path
|
|
351
|
+
json_file_path = Path(run_id_or_path)
|
|
352
|
+
if not json_file_path.exists():
|
|
353
|
+
print(f"❌ JSON file not found: {json_file_path}")
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
# Extract run ID from the file for default naming
|
|
357
|
+
run_id = json_file_path.stem
|
|
358
|
+
mode = "file"
|
|
359
|
+
else:
|
|
360
|
+
# Input is a run ID - use existing behavior
|
|
361
|
+
run_id = run_id_or_path
|
|
362
|
+
json_file_path = None
|
|
363
|
+
mode = "run_id"
|
|
364
|
+
|
|
365
|
+
# Default console behavior: always show console output
|
|
366
|
+
show_console = console if console is not None else True
|
|
367
|
+
|
|
368
|
+
if output:
|
|
369
|
+
# Generate HTML file with console output
|
|
370
|
+
output_file = output
|
|
371
|
+
print(f"🌐 Generating timeline: {output_file}")
|
|
372
|
+
|
|
373
|
+
if show_console:
|
|
374
|
+
# Show console output first, then generate HTML
|
|
375
|
+
if mode == "file":
|
|
376
|
+
_visualize_simple_from_file(json_file_path, show_summary=False)
|
|
377
|
+
else:
|
|
378
|
+
visualize_simple(run_id, show_summary=False)
|
|
379
|
+
print(f"\n🌐 Generating HTML timeline: {output_file}")
|
|
380
|
+
|
|
381
|
+
if mode == "file":
|
|
382
|
+
_generate_html_timeline_from_file(json_file_path, output_file, open_browser)
|
|
383
|
+
else:
|
|
384
|
+
generate_html_timeline(run_id, output_file, open_browser)
|
|
385
|
+
else:
|
|
386
|
+
# Default behavior: show console + generate HTML with browser
|
|
387
|
+
if show_console:
|
|
388
|
+
if mode == "file":
|
|
389
|
+
_visualize_simple_from_file(json_file_path, show_summary=False)
|
|
390
|
+
else:
|
|
391
|
+
visualize_simple(run_id, show_summary=False)
|
|
392
|
+
|
|
393
|
+
# Always generate HTML file and open browser by default
|
|
394
|
+
output_file = f"{run_id}_timeline.html"
|
|
395
|
+
print(f"\n🌐 Generating HTML timeline: {output_file}")
|
|
396
|
+
if mode == "file":
|
|
397
|
+
_generate_html_timeline_from_file(json_file_path, output_file, open_browser)
|
|
398
|
+
else:
|
|
399
|
+
generate_html_timeline(run_id, output_file, open_browser)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _visualize_simple_from_file(json_file_path, show_summary: bool = False) -> None:
|
|
403
|
+
"""Visualize timeline from JSON file path."""
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
viz = SimpleVisualizer(json_file_path)
|
|
407
|
+
viz.print_simple_timeline()
|
|
408
|
+
if show_summary:
|
|
409
|
+
viz.print_execution_summary()
|
|
410
|
+
except Exception as e:
|
|
411
|
+
print(f"❌ Error reading JSON file: {e}")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _generate_html_timeline_from_file(
|
|
415
|
+
json_file_path, output_file: str, open_browser: bool = True
|
|
416
|
+
) -> None:
|
|
417
|
+
"""Generate HTML timeline from JSON file path."""
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
viz = SimpleVisualizer(json_file_path)
|
|
421
|
+
viz.generate_html_timeline(output_file)
|
|
422
|
+
|
|
423
|
+
if open_browser:
|
|
424
|
+
import webbrowser
|
|
425
|
+
|
|
426
|
+
file_path = Path(output_file).absolute()
|
|
427
|
+
print(f"🌐 Opening timeline in browser: {file_path.name}")
|
|
428
|
+
webbrowser.open(file_path.as_uri())
|
|
429
|
+
except Exception as e:
|
|
430
|
+
print(f"❌ Error generating HTML: {e}")
|
|
431
|
+
|
|
432
|
+
|
|
277
433
|
if __name__ == "__main__":
|
|
278
434
|
app()
|