orchestra-cli 0.5.0__tar.gz → 0.5.1__tar.gz

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.
Files changed (61) hide show
  1. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/PKG-INFO +1 -1
  2. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/run_pipeline.py +29 -4
  3. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_run_pipeline.py +94 -0
  4. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/PKG-INFO +1 -1
  5. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/pyproject.toml +1 -1
  6. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/README.md +0 -0
  7. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/__init__.py +0 -0
  8. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/build_pipeline.py +0 -0
  9. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/cli.py +0 -0
  10. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/create_pipeline.py +0 -0
  11. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/delete_pipeline.py +0 -0
  12. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/fetch_pipelines.py +0 -0
  13. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/get_pipeline.py +0 -0
  14. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/import_pipeline.py +0 -0
  15. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/list_accounts.py +0 -0
  16. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/login.py +0 -0
  17. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/login_page.html +0 -0
  18. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/migrate_pipeline.py +0 -0
  19. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/pipeline_upsert.py +0 -0
  20. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/task_logs.py +0 -0
  21. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/update_pipeline.py +0 -0
  22. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/use_account.py +0 -0
  23. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/src/validate_pipeline.py +0 -0
  24. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/__init__.py +0 -0
  25. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/conftest.py +0 -0
  26. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_build_pipeline.py +0 -0
  27. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_cli.py +0 -0
  28. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_create_pipeline.py +0 -0
  29. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_delete_pipeline.py +0 -0
  30. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_fetch_pipelines.py +0 -0
  31. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_get_pipeline.py +0 -0
  32. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_import_pipeline.py +0 -0
  33. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_list_accounts.py +0 -0
  34. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_login.py +0 -0
  35. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_migrate_pipeline.py +0 -0
  36. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_pipeline_selector.py +0 -0
  37. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_task_logs.py +0 -0
  38. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_update_pipeline.py +0 -0
  39. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_use_account.py +0 -0
  40. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_validate_pipeline.py +0 -0
  41. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/__init__.py +0 -0
  42. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/test_api.py +0 -0
  43. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/test_git.py +0 -0
  44. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/test_picker.py +0 -0
  45. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/accounts.py +0 -0
  46. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/api.py +0 -0
  47. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/constants.py +0 -0
  48. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/credentials.py +0 -0
  49. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/git.py +0 -0
  50. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/picker.py +0 -0
  51. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/pipeline_lookup.py +0 -0
  52. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/pipeline_selector.py +0 -0
  53. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/pipeline_update.py +0 -0
  54. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/styling.py +0 -0
  55. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/yaml_loader.py +0 -0
  56. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/SOURCES.txt +0 -0
  57. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/dependency_links.txt +0 -0
  58. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/entry_points.txt +0 -0
  59. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/requires.txt +0 -0
  60. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/top_level.txt +0 -0
  61. {orchestra_cli-0.5.0 → orchestra_cli-0.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestra-cli
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Orchestra CLI. Perform operations with Orchestra locally.
5
5
  Author-email: Orchestra Technologies <support@getorchestra.io>
6
6
  Requires-Python: >=3.10
@@ -37,13 +37,14 @@ STATUS_STYLES = {
37
37
  "CREATED": "cyan",
38
38
  "QUEUED": "yellow",
39
39
  "RUNNING": "blue",
40
+ "CANCELLING": "red",
40
41
  "SUCCEEDED": "green",
41
42
  "WARNING": "yellow",
42
43
  "SKIPPED": "yellow",
43
44
  "FAILED": "red",
44
45
  "CANCELLED": "red",
45
46
  }
46
- IN_PROGRESS_RUN_STATUSES = {"RUNNING", "QUEUED", "CREATED"}
47
+ IN_PROGRESS_RUN_STATUSES = {"RUNNING", "QUEUED", "CREATED", "CANCELLING"}
47
48
  TERMINAL_RUN_STATUSES = {"SUCCEEDED", "WARNING", "SKIPPED", "FAILED", "CANCELLED"}
48
49
 
49
50
 
@@ -307,13 +308,23 @@ def _format_pipeline_duration(created_at_utc: datetime, now_utc: datetime | None
307
308
  return f"{seconds} {unit}"
308
309
 
309
310
 
311
+ def _run_message(status_body: dict[str, object]) -> str | None:
312
+ message = status_body.get("message")
313
+ if isinstance(message, str) and message.strip():
314
+ return message.strip()
315
+ return None
316
+
317
+
310
318
  def _build_poll_status_text(
311
319
  status_value: str,
312
320
  created_at_utc: datetime | None = None,
313
321
  now_utc: datetime | None = None,
322
+ status_message: str | None = None,
314
323
  ) -> Text:
315
324
  text = Text("Pipeline status: ")
316
325
  text.append(status_value, style=f"bold {STATUS_STYLES.get(status_value, 'white')}")
326
+ if status_message is not None:
327
+ text.append(f" - {status_message}")
317
328
  if created_at_utc is not None:
318
329
  text.append(f" ({_format_pipeline_duration(created_at_utc, now_utc)})", style="dim")
319
330
  return text
@@ -419,10 +430,11 @@ def _build_poll_display(
419
430
  task_runs: list[dict[str, object]],
420
431
  can_fetch_task_runs: bool,
421
432
  now_utc: datetime | None = None,
433
+ status_message: str | None = None,
422
434
  ) -> Group:
423
435
  return Group(
424
436
  _build_live_status_text(seconds_since_check),
425
- _build_poll_status_text(status_value, created_at_utc, now_utc),
437
+ _build_poll_status_text(status_value, created_at_utc, now_utc, status_message),
426
438
  _build_task_runs_table(
427
439
  task_runs,
428
440
  can_fetch_task_runs=can_fetch_task_runs,
@@ -452,6 +464,7 @@ def _sleep_with_status_updates(
452
464
  created_at_utc: datetime | None,
453
465
  task_runs: list[dict[str, object]],
454
466
  can_fetch_task_runs: bool,
467
+ status_message: str | None = None,
455
468
  sleep_fn: Callable[[float], None] | None = None,
456
469
  ) -> None:
457
470
  sleep_fn = sleep_fn or time.sleep
@@ -468,6 +481,7 @@ def _sleep_with_status_updates(
468
481
  created_at_utc=created_at_utc,
469
482
  task_runs=task_runs,
470
483
  can_fetch_task_runs=can_fetch_task_runs,
484
+ status_message=status_message,
471
485
  ),
472
486
  )
473
487
 
@@ -489,6 +503,7 @@ def _poll_until_terminal(
489
503
  status_url = get_api_url(f"pipeline_runs/{pipeline_run_id}/status")
490
504
  poll_status_display = _create_poll_status_display()
491
505
  last_status_value: str | None = None
506
+ last_status_message: str | None = None
492
507
  last_task_runs: list[dict[str, object]] = []
493
508
  can_fetch_task_runs = False
494
509
  missing_run_status_polls = 0
@@ -502,6 +517,7 @@ def _poll_until_terminal(
502
517
  created_at_utc=created_at_utc,
503
518
  task_runs=last_task_runs,
504
519
  can_fetch_task_runs=can_fetch_task_runs,
520
+ status_message=last_status_message,
505
521
  )
506
522
  try:
507
523
  status_resp = client.get(status_url)
@@ -530,11 +546,13 @@ def _poll_until_terminal(
530
546
  status_body = {}
531
547
 
532
548
  status_value = status_body.get("runStatus")
549
+ run_message = _run_message(status_body)
533
550
  created_at_utc = created_at_utc or _parse_created_at_utc(status_body)
534
551
 
535
552
  if isinstance(status_value, str):
536
553
  missing_run_status_polls = 0
537
554
  last_status_value = status_value
555
+ last_status_message = run_message if status_value == "QUEUED" else None
538
556
  can_fetch_task_runs = status_value in (
539
557
  IN_PROGRESS_RUN_STATUSES | TERMINAL_RUN_STATUSES
540
558
  )
@@ -548,10 +566,14 @@ def _poll_until_terminal(
548
566
  created_at_utc=created_at_utc,
549
567
  task_runs=last_task_runs,
550
568
  can_fetch_task_runs=can_fetch_task_runs,
569
+ status_message=last_status_message,
551
570
  ),
552
571
  )
553
572
  else:
554
- typer.echo(f"Pipeline ({selector_name}) status: {status_value}")
573
+ status_line = f"Pipeline ({selector_name}) status: {status_value}"
574
+ if last_status_message is not None:
575
+ status_line += f" - {last_status_message}"
576
+ typer.echo(status_line)
555
577
  else:
556
578
  missing_run_status_polls += 1
557
579
  if missing_run_status_polls <= max_missing_run_status_polls:
@@ -572,7 +594,10 @@ def _poll_until_terminal(
572
594
  if status_value == "SKIPPED":
573
595
  _stop_poll_status_display(poll_status_display)
574
596
  poll_status_display = None
575
- typer.echo(yellow("⚠ Pipeline skipped"))
597
+ skipped_text = (
598
+ f"⚠ Pipeline skipped: {run_message}" if run_message else "⚠ Pipeline skipped"
599
+ )
600
+ typer.echo(yellow(skipped_text))
576
601
  raise typer.Exit(code=0)
577
602
 
578
603
  if status_value in {"FAILED", "CANCELLED"}:
@@ -1,3 +1,5 @@
1
+ import subprocess
2
+ import time
1
3
  from datetime import UTC, datetime
2
4
  from pathlib import Path
3
5
 
@@ -96,6 +98,11 @@ def test_build_poll_status_text_includes_duration() -> None:
96
98
  assert text.plain == "Pipeline status: RUNNING (1:30 minutes)"
97
99
 
98
100
 
101
+ def test_build_poll_status_text_includes_status_message() -> None:
102
+ text = _build_poll_status_text("QUEUED", status_message="Waiting for a free slot")
103
+ assert text.plain == "Pipeline status: QUEUED - Waiting for a free slot"
104
+
105
+
99
106
  def test_build_live_status_text_includes_update_age() -> None:
100
107
  text = _build_live_status_text(3)
101
108
  assert text.plain == "Live status (updated 3 seconds ago)"
@@ -1094,6 +1101,93 @@ def test_run_wait_warning(httpx_mock: HTTPXMock, monkeypatch, tmp_path: Path):
1094
1101
  assert result.output.strip().splitlines()[-1] == "⚠ Pipeline completed with warnings"
1095
1102
 
1096
1103
 
1104
+ def _invoke_run_through_statuses(
1105
+ httpx_mock: HTTPXMock,
1106
+ monkeypatch,
1107
+ tmp_path: Path,
1108
+ status_bodies: list[dict[str, str]],
1109
+ ):
1110
+ mapping = {
1111
+ ("rev-parse", "--show-toplevel"): (0, str(tmp_path), ""),
1112
+ ("status", "--porcelain"): (0, "", ""),
1113
+ ("rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"): (1, "", ""),
1114
+ }
1115
+ monkeypatch.setattr(time, "sleep", lambda _: None)
1116
+ monkeypatch.setattr(subprocess, "run", make_git_subprocess_mock(mapping))
1117
+
1118
+ base = "https://app.getorchestra.io/api/engine/public"
1119
+ httpx_mock.add_response(
1120
+ method="POST",
1121
+ url=f"{base}/pipelines/demo/start",
1122
+ json={"pipelineRunId": "run-q"},
1123
+ )
1124
+ for status_body in status_bodies:
1125
+ httpx_mock.add_response(
1126
+ method="GET",
1127
+ url=f"{base}/pipeline_runs/run-q/status",
1128
+ json={**status_body, "pipelineName": "demo"},
1129
+ )
1130
+ httpx_mock.add_response(
1131
+ method="GET",
1132
+ url=f"{base}/pipeline_runs/run-q/task_runs?page_size=50&page=1",
1133
+ json={"results": []},
1134
+ )
1135
+
1136
+ return runner.invoke(app, ["pipeline", "run", "--alias", "demo", "--wait"])
1137
+
1138
+
1139
+ def test_run_wait_keeps_polling_while_cancelling(
1140
+ httpx_mock: HTTPXMock,
1141
+ monkeypatch,
1142
+ tmp_path: Path,
1143
+ ):
1144
+ result = _invoke_run_through_statuses(
1145
+ httpx_mock,
1146
+ monkeypatch,
1147
+ tmp_path,
1148
+ [{"runStatus": "QUEUED"}, {"runStatus": "CANCELLING"}, {"runStatus": "CANCELLED"}],
1149
+ )
1150
+
1151
+ assert result.exit_code == 1
1152
+ assert "Invalid status value" not in result.output
1153
+ assert "status: CANCELLING" in result.output
1154
+ assert "status CANCELLED" in result.output
1155
+
1156
+
1157
+ def test_run_wait_shows_queued_message_then_final_status(
1158
+ httpx_mock: HTTPXMock,
1159
+ monkeypatch,
1160
+ tmp_path: Path,
1161
+ ):
1162
+ result = _invoke_run_through_statuses(
1163
+ httpx_mock,
1164
+ monkeypatch,
1165
+ tmp_path,
1166
+ [
1167
+ {"runStatus": "QUEUED", "message": "Waiting for a free slot"},
1168
+ {"runStatus": "RUNNING", "message": "Waiting for a free slot"},
1169
+ {"runStatus": "SUCCEEDED"},
1170
+ ],
1171
+ )
1172
+
1173
+ assert result.exit_code == 0
1174
+ assert "Pipeline (alias: demo) status: QUEUED - Waiting for a free slot" in result.output
1175
+ assert "Pipeline (alias: demo) status: RUNNING\n" in result.output
1176
+ assert result.output.strip().splitlines()[-1] == "✅ Pipeline succeeded"
1177
+
1178
+
1179
+ def test_run_wait_skipped_prints_reason(httpx_mock: HTTPXMock, monkeypatch, tmp_path: Path):
1180
+ result = _invoke_run_through_statuses(
1181
+ httpx_mock,
1182
+ monkeypatch,
1183
+ tmp_path,
1184
+ [{"runStatus": "SKIPPED", "message": "Replaced by a newer run"}],
1185
+ )
1186
+
1187
+ assert result.exit_code == 0
1188
+ assert result.output.strip().splitlines()[-1] == "⚠ Pipeline skipped: Replaced by a newer run"
1189
+
1190
+
1097
1191
  def test_run_git_backed_path_uses_prepared_branch_and_commit(
1098
1192
  httpx_mock: HTTPXMock,
1099
1193
  monkeypatch,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestra-cli
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Orchestra CLI. Perform operations with Orchestra locally.
5
5
  Author-email: Orchestra Technologies <support@getorchestra.io>
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "orchestra-cli"
3
- version = "0.5.0"
3
+ version = "0.5.1"
4
4
  description = "Orchestra CLI. Perform operations with Orchestra locally."
5
5
  readme = "README.md"
6
6
  authors = [
File without changes
File without changes