verdog-cli 0.1.2__tar.gz → 0.1.4__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.
- {verdog_cli-0.1.2/verdog_cli.egg-info → verdog_cli-0.1.4}/PKG-INFO +2 -2
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/pyproject.toml +2 -2
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_backend_handoff.py +3 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_catalogue_cli.py +26 -4
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_sync.py +99 -6
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/local.py +40 -33
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/main.py +2 -1
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/manage.py +78 -55
- {verdog_cli-0.1.2 → verdog_cli-0.1.4/verdog_cli.egg-info}/PKG-INFO +2 -2
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli.egg-info/requires.txt +1 -1
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/LICENSE +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/MANIFEST.in +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/README.md +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/setup.cfg +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/conftest.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_check_receipt.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_install_process.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_runs.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/tests/test_session.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/__init__.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/api.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/catalogue.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/py.typed +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/requirements.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/runner.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/runs.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/session.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli/sync.py +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli.egg-info/SOURCES.txt +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli.egg-info/dependency_links.txt +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli.egg-info/entry_points.txt +0 -0
- {verdog_cli-0.1.2 → verdog_cli-0.1.4}/verdog_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: verdog-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: The Verdog command-line client.
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
Project-URL: Repository, https://github.com/verdog-ai/verdog-cli
|
|
@@ -8,7 +8,7 @@ Project-URL: Documentation, https://drexlerd.github.io/verdog-website/
|
|
|
8
8
|
Requires-Python: >=3.12
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: verdog-runtime<0.2,>=0.1.
|
|
11
|
+
Requires-Dist: verdog-runtime<0.2,>=0.1.6
|
|
12
12
|
Requires-Dist: packaging>=23.2
|
|
13
13
|
Requires-Dist: tabulate>=0.10
|
|
14
14
|
Requires-Dist: ty==0.0.72
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "verdog-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "The Verdog command-line client."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-only"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
12
12
|
requires-python = ">=3.12"
|
|
13
|
-
dependencies = ["verdog-runtime>=0.1.
|
|
13
|
+
dependencies = ["verdog-runtime>=0.1.6,<0.2", "packaging>=23.2", "tabulate>=0.10", "ty==0.0.72"]
|
|
14
14
|
|
|
15
15
|
[project.scripts]
|
|
16
16
|
verdog = "verdog_cli.main:main"
|
|
@@ -304,6 +304,9 @@ def test_new_projects_persist_the_selected_backend(
|
|
|
304
304
|
assert received == (
|
|
305
305
|
[f"{expected}/api/v1/check"] if command == "init" else []
|
|
306
306
|
)
|
|
307
|
+
if command == "init":
|
|
308
|
+
project = json.loads((destination / "project.json").read_text("utf-8"))
|
|
309
|
+
assert (project["workflows"], project["subroutines"]) == ([], [])
|
|
307
310
|
|
|
308
311
|
|
|
309
312
|
@pytest.mark.parametrize("selection", ["flag", "environment"])
|
|
@@ -72,7 +72,7 @@ def test_describe_json_is_canonical_and_read_only(
|
|
|
72
72
|
captured = capsys.readouterr()
|
|
73
73
|
assert captured.err == ""
|
|
74
74
|
assert json.loads(captured.out) == {
|
|
75
|
-
"schema_version":
|
|
75
|
+
"schema_version": 33,
|
|
76
76
|
"package": "test.project",
|
|
77
77
|
"workflow_id": "main",
|
|
78
78
|
"display_name": "Research workflow",
|
|
@@ -114,7 +114,7 @@ def test_describe_json_is_canonical_and_read_only(
|
|
|
114
114
|
"closure": [],
|
|
115
115
|
"environment": {
|
|
116
116
|
"python": ">=3.12",
|
|
117
|
-
"schema_version":
|
|
117
|
+
"schema_version": 33,
|
|
118
118
|
"requirements": ["requests[security]>=2", "zeta>=2"],
|
|
119
119
|
},
|
|
120
120
|
}
|
|
@@ -171,7 +171,7 @@ def test_catalogue_client_encodes_filters_and_publication_environment(
|
|
|
171
171
|
{"name": "Tools"},
|
|
172
172
|
environment={
|
|
173
173
|
"python": ">=3.12",
|
|
174
|
-
"schema_version":
|
|
174
|
+
"schema_version": 33,
|
|
175
175
|
"requirements": ["httpx>=0.27"],
|
|
176
176
|
},
|
|
177
177
|
)
|
|
@@ -197,7 +197,7 @@ def test_catalogue_client_encodes_filters_and_publication_environment(
|
|
|
197
197
|
"preview": {"name": "Tools"},
|
|
198
198
|
"environment": {
|
|
199
199
|
"python": ">=3.12",
|
|
200
|
-
"schema_version":
|
|
200
|
+
"schema_version": 33,
|
|
201
201
|
"requirements": ["httpx>=0.27"],
|
|
202
202
|
},
|
|
203
203
|
},
|
|
@@ -704,3 +704,25 @@ def test_retract_reports_the_entry_id_after_an_empty_204_response(
|
|
|
704
704
|
assert captured.out.startswith(f"Retracted catalogue entry {entry_id}.\n")
|
|
705
705
|
assert "@" not in captured.out
|
|
706
706
|
assert retracted == [entry_id]
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@pytest.mark.parametrize(
|
|
710
|
+
("roots", "requested", "expected"),
|
|
711
|
+
[
|
|
712
|
+
(["only"], None, "only"),
|
|
713
|
+
(["left", "main"], None, "main"),
|
|
714
|
+
(["left", "right"], "right", "right"),
|
|
715
|
+
(["left", "right"], None, "choose a root subroutine explicitly"),
|
|
716
|
+
],
|
|
717
|
+
)
|
|
718
|
+
def test_import_into_defaults_to_the_only_root_subroutine_or_main(
|
|
719
|
+
roots: list[str], requested: str | None, expected: str
|
|
720
|
+
) -> None:
|
|
721
|
+
from verdog_cli import manage
|
|
722
|
+
|
|
723
|
+
project = {"subroutines": [{"id": root} for root in roots]}
|
|
724
|
+
if expected.startswith("choose"):
|
|
725
|
+
with pytest.raises(WorkspaceError, match="left, right"):
|
|
726
|
+
manage._import_parent(project, requested) # pyright: ignore[reportPrivateUsage]
|
|
727
|
+
return
|
|
728
|
+
assert manage._import_parent(project, requested) == expected # pyright: ignore[reportPrivateUsage]
|
|
@@ -20,6 +20,7 @@ from verdog_cli.local import (
|
|
|
20
20
|
Clone,
|
|
21
21
|
LocalDefinition,
|
|
22
22
|
WorkspaceError,
|
|
23
|
+
default_root,
|
|
23
24
|
)
|
|
24
25
|
from verdog_cli.requirements import parse_requirements
|
|
25
26
|
from verdog_cli.sync import (
|
|
@@ -100,8 +101,8 @@ def _manifest(
|
|
|
100
101
|
{
|
|
101
102
|
"schema_version": SCHEMA_VERSION,
|
|
102
103
|
"package": package,
|
|
103
|
-
"
|
|
104
|
-
"
|
|
104
|
+
"workflows": [_workflow(cast(str, root_subroutine["id"]))],
|
|
105
|
+
"subroutines": [root_subroutine],
|
|
105
106
|
"externals": [] if externals is None else externals,
|
|
106
107
|
"sources": [],
|
|
107
108
|
}
|
|
@@ -628,7 +629,7 @@ def test_environment_ids_cannot_escape_the_generated_root(
|
|
|
628
629
|
) -> None:
|
|
629
630
|
clone = _clone(tmp_path, [])
|
|
630
631
|
project = clone.project
|
|
631
|
-
project["
|
|
632
|
+
project["workflows"][0]["subroutine"] = "../outside"
|
|
632
633
|
(clone.root / "project.json").write_text(
|
|
633
634
|
json.dumps(project), encoding="utf-8"
|
|
634
635
|
)
|
|
@@ -648,7 +649,7 @@ def test_local_definition_ids_use_the_language_identifier_grammar(
|
|
|
648
649
|
) -> None:
|
|
649
650
|
clone = _clone(tmp_path, [])
|
|
650
651
|
project = clone.project
|
|
651
|
-
project["
|
|
652
|
+
project["subroutines"][0]["subroutines"] = [_subroutine(identifier)]
|
|
652
653
|
(clone.root / "project.json").write_text(
|
|
653
654
|
json.dumps(project), encoding="utf-8"
|
|
654
655
|
)
|
|
@@ -777,8 +778,8 @@ def test_sync_visits_every_workflow_in_each_nested_checkout(
|
|
|
777
778
|
|
|
778
779
|
# Provision local workflow envelopes independently, also below pins.
|
|
779
780
|
left_project = left_clone.project
|
|
780
|
-
left_project["
|
|
781
|
-
left_project["
|
|
781
|
+
left_project["subroutines"][0]["workflows"] = [_workflow("main__other")]
|
|
782
|
+
left_project["subroutines"][0]["subroutines"] = [_subroutine("other")]
|
|
782
783
|
(left / "project.json").write_text(
|
|
783
784
|
json.dumps(left_project), encoding="utf-8"
|
|
784
785
|
)
|
|
@@ -1521,3 +1522,95 @@ def test_reachable_local_workflow_preflight_handles_recursive_calls(
|
|
|
1521
1522
|
monkeypatch.setattr("verdog_cli.sync.require_current_environment", require)
|
|
1522
1523
|
require_current_workflow_environments(root, _root_definition(root))
|
|
1523
1524
|
assert checked == ["main", "main__child"]
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
def _roots(root: Path, project: dict[str, Any]) -> Clone:
|
|
1528
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
1529
|
+
(root / "project.json").write_text(
|
|
1530
|
+
json.dumps(
|
|
1531
|
+
{"schema_version": SCHEMA_VERSION, "package": "ada.roots"} | project
|
|
1532
|
+
),
|
|
1533
|
+
encoding="utf-8",
|
|
1534
|
+
)
|
|
1535
|
+
return Clone(root, "http://unused", None)
|
|
1536
|
+
|
|
1537
|
+
|
|
1538
|
+
@pytest.mark.parametrize(
|
|
1539
|
+
("ids", "expected"),
|
|
1540
|
+
[(["one"], "one"), (["alpha", "main", "beta"], "main")],
|
|
1541
|
+
)
|
|
1542
|
+
def test_default_root_is_the_only_one_or_main(
|
|
1543
|
+
ids: list[str], expected: str
|
|
1544
|
+
) -> None:
|
|
1545
|
+
assert default_root("workflow", ids) == expected
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
@pytest.mark.parametrize(
|
|
1549
|
+
("ids", "listed"),
|
|
1550
|
+
[(["alpha", "beta"], "alpha, beta"), ([], "none declared")],
|
|
1551
|
+
)
|
|
1552
|
+
def test_default_root_otherwise_asks_for_a_choice(
|
|
1553
|
+
ids: list[str], listed: str
|
|
1554
|
+
) -> None:
|
|
1555
|
+
with pytest.raises(
|
|
1556
|
+
WorkspaceError, match=f"choose a root subroutine explicitly: {listed}"
|
|
1557
|
+
):
|
|
1558
|
+
default_root("subroutine", ids)
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
def test_a_v32_manifest_reads_as_one_root_workflow_and_subroutine(
|
|
1562
|
+
tmp_path: Path,
|
|
1563
|
+
) -> None:
|
|
1564
|
+
clone = _roots(
|
|
1565
|
+
tmp_path,
|
|
1566
|
+
{
|
|
1567
|
+
"schema_version": 32,
|
|
1568
|
+
"workflow": _workflow("main"),
|
|
1569
|
+
"subroutine": _subroutine("main"),
|
|
1570
|
+
},
|
|
1571
|
+
)
|
|
1572
|
+
assert clone.project["schema_version"] == SCHEMA_VERSION
|
|
1573
|
+
assert [(item.kind, item.module) for item in clone.local_definitions()] == [
|
|
1574
|
+
("workflow", "ada.roots.workflows.main"),
|
|
1575
|
+
("subroutine", "ada.roots.subroutines.main"),
|
|
1576
|
+
]
|
|
1577
|
+
assert clone.workflow_definition().local_id == "main"
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def test_empty_and_multi_root_projects_list_their_definitions(
|
|
1581
|
+
tmp_path: Path,
|
|
1582
|
+
) -> None:
|
|
1583
|
+
empty = _roots(tmp_path / "empty", {"workflows": [], "subroutines": []})
|
|
1584
|
+
assert empty.local_definitions() == ()
|
|
1585
|
+
with pytest.raises(WorkspaceError, match="none declared"):
|
|
1586
|
+
empty.workflow_definition()
|
|
1587
|
+
|
|
1588
|
+
clone = _roots(
|
|
1589
|
+
tmp_path / "two",
|
|
1590
|
+
{
|
|
1591
|
+
"workflows": [_workflow("left"), _workflow("right")],
|
|
1592
|
+
"subroutines": [
|
|
1593
|
+
_subroutine("left", subroutines=[_subroutine("inner")]),
|
|
1594
|
+
_subroutine("right"),
|
|
1595
|
+
],
|
|
1596
|
+
},
|
|
1597
|
+
)
|
|
1598
|
+
assert [
|
|
1599
|
+
(item.kind, item.local_id, item.module)
|
|
1600
|
+
for item in clone.local_definitions()
|
|
1601
|
+
] == [
|
|
1602
|
+
("workflow", "left", "ada.roots.workflows.left"),
|
|
1603
|
+
("workflow", "right", "ada.roots.workflows.right"),
|
|
1604
|
+
("subroutine", "left", "ada.roots.subroutines.left"),
|
|
1605
|
+
(
|
|
1606
|
+
"subroutine",
|
|
1607
|
+
"left__inner",
|
|
1608
|
+
"ada.roots.subroutines.left.subroutines.inner",
|
|
1609
|
+
),
|
|
1610
|
+
("subroutine", "right", "ada.roots.subroutines.right"),
|
|
1611
|
+
]
|
|
1612
|
+
assert clone.workflow_definition("right").local_id == "right"
|
|
1613
|
+
with pytest.raises(
|
|
1614
|
+
WorkspaceError, match="choose a root workflow explicitly: left, right"
|
|
1615
|
+
):
|
|
1616
|
+
runner.run(clone)
|
|
@@ -36,7 +36,7 @@ EXTERNAL_ROOT = "external"
|
|
|
36
36
|
Shared by dependency paths, type-check exclusions, and drop cleanup.
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
|
-
SCHEMA_VERSION =
|
|
39
|
+
SCHEMA_VERSION = 33
|
|
40
40
|
|
|
41
41
|
_DEFINITION_SEPARATOR = "__"
|
|
42
42
|
|
|
@@ -51,6 +51,17 @@ def definition_leaf(identifier: str, /) -> str:
|
|
|
51
51
|
return identifier.rsplit(_DEFINITION_SEPARATOR, 1)[-1]
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def default_root(kind: str, ids: list[str]) -> str:
|
|
55
|
+
"""The root definition used when none is named: the only one, or `main`."""
|
|
56
|
+
if len(ids) == 1:
|
|
57
|
+
return ids[0]
|
|
58
|
+
if "main" in ids:
|
|
59
|
+
return "main"
|
|
60
|
+
raise WorkspaceError(
|
|
61
|
+
f"choose a root {kind} explicitly: {', '.join(ids) or 'none declared'}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
54
65
|
def package_problem(package: str) -> str | None:
|
|
55
66
|
"""Why a canonical `<space>.<name>` package is invalid, if it is."""
|
|
56
67
|
parts = package.split(".")
|
|
@@ -308,11 +319,19 @@ class Clone:
|
|
|
308
319
|
raise WorkspaceError("project.json is not an object")
|
|
309
320
|
project = cast(dict[str, Any], decoded)
|
|
310
321
|
version = project.get("schema_version")
|
|
311
|
-
if version
|
|
322
|
+
if version == 32:
|
|
323
|
+
# ponytail: the compiler migrates the file; reading v32 only needs
|
|
324
|
+
# its single root workflow and subroutine lifted into lists.
|
|
325
|
+
workflow = project.pop("workflow", None)
|
|
326
|
+
subroutine = project.pop("subroutine", None)
|
|
327
|
+
project["schema_version"] = SCHEMA_VERSION
|
|
328
|
+
project["workflows"] = [] if workflow is None else [workflow]
|
|
329
|
+
project["subroutines"] = [] if subroutine is None else [subroutine]
|
|
330
|
+
elif version != SCHEMA_VERSION:
|
|
312
331
|
raise WorkspaceError(
|
|
313
332
|
f"project schema v"
|
|
314
333
|
f"{version} is unsupported; this CLI requires schema v"
|
|
315
|
-
f"{SCHEMA_VERSION}"
|
|
334
|
+
f"{SCHEMA_VERSION} (or v32)"
|
|
316
335
|
)
|
|
317
336
|
return project
|
|
318
337
|
|
|
@@ -563,18 +582,12 @@ class Clone:
|
|
|
563
582
|
"""Return the declared workflow envelopes and subroutine graphs."""
|
|
564
583
|
project = self.project
|
|
565
584
|
package = project.get("package")
|
|
566
|
-
root_workflow = project.get("workflow")
|
|
567
|
-
root_subroutine = project.get("subroutine")
|
|
568
585
|
if (
|
|
569
586
|
not isinstance(package, str)
|
|
570
587
|
or not package
|
|
571
588
|
or package_problem(package) is not None
|
|
572
|
-
or not isinstance(root_workflow, dict)
|
|
573
|
-
or not isinstance(root_subroutine, dict)
|
|
574
589
|
):
|
|
575
|
-
raise WorkspaceError(
|
|
576
|
-
"project.json has no root workflow and subroutine"
|
|
577
|
-
)
|
|
590
|
+
raise WorkspaceError("project.json has no valid package")
|
|
578
591
|
|
|
579
592
|
definitions: list[LocalDefinition] = []
|
|
580
593
|
identifiers: set[tuple[Literal["workflow", "subroutine"], str]] = set()
|
|
@@ -622,13 +635,18 @@ class Clone:
|
|
|
622
635
|
raise WorkspaceError("a local subroutine has no id")
|
|
623
636
|
identifier = definition_child(owner, leaf)
|
|
624
637
|
add(raw, module, "subroutine", identifier)
|
|
638
|
+
visit_children(raw, module, identifier)
|
|
639
|
+
|
|
640
|
+
def visit_children(
|
|
641
|
+
raw: dict[str, Any], module: str, owner: str | None
|
|
642
|
+
) -> None:
|
|
625
643
|
for child in objects(raw.get("workflows"), "workflows"):
|
|
626
644
|
if "external" in child:
|
|
627
645
|
continue
|
|
628
646
|
target = child.get("subroutine")
|
|
629
647
|
if not isinstance(target, str) or not target:
|
|
630
648
|
raise WorkspaceError("a local workflow has no subroutine")
|
|
631
|
-
child_id = definition_child(
|
|
649
|
+
child_id = definition_child(owner, definition_leaf(target))
|
|
632
650
|
add(
|
|
633
651
|
child,
|
|
634
652
|
f"{module}.workflows.{definition_leaf(target)}",
|
|
@@ -642,26 +660,10 @@ class Clone:
|
|
|
642
660
|
visit_subroutine(
|
|
643
661
|
child,
|
|
644
662
|
f"{module}.subroutines.{child_id}",
|
|
645
|
-
|
|
663
|
+
owner,
|
|
646
664
|
)
|
|
647
665
|
|
|
648
|
-
|
|
649
|
-
workflow_id = workflow_body.get("subroutine")
|
|
650
|
-
subroutine_body = cast(dict[str, Any], root_subroutine)
|
|
651
|
-
subroutine_id = subroutine_body.get("id")
|
|
652
|
-
if not isinstance(subroutine_id, str) or not subroutine_id:
|
|
653
|
-
raise WorkspaceError("the root subroutine has no id")
|
|
654
|
-
add(
|
|
655
|
-
workflow_body,
|
|
656
|
-
f"{package}.workflows.{workflow_id}",
|
|
657
|
-
"workflow",
|
|
658
|
-
workflow_id,
|
|
659
|
-
)
|
|
660
|
-
visit_subroutine(
|
|
661
|
-
subroutine_body,
|
|
662
|
-
f"{package}.subroutines.{subroutine_id}",
|
|
663
|
-
None,
|
|
664
|
-
)
|
|
666
|
+
visit_children(project, package, None)
|
|
665
667
|
return tuple(definitions)
|
|
666
668
|
|
|
667
669
|
def workflow_definitions(self) -> tuple[LocalDefinition, ...]:
|
|
@@ -675,12 +677,17 @@ class Clone:
|
|
|
675
677
|
def workflow_definition(
|
|
676
678
|
self, identifier: str | None = None, /
|
|
677
679
|
) -> LocalDefinition:
|
|
678
|
-
"""Select a workflow by local id, defaulting
|
|
680
|
+
"""Select a workflow by local id, defaulting per `default_root`."""
|
|
679
681
|
workflows = self.workflow_definitions()
|
|
680
|
-
if not workflows:
|
|
681
|
-
raise WorkspaceError("project.json has no workflow")
|
|
682
682
|
if identifier is None:
|
|
683
|
-
|
|
683
|
+
identifier = default_root(
|
|
684
|
+
"workflow",
|
|
685
|
+
[
|
|
686
|
+
item.local_id
|
|
687
|
+
for item in workflows
|
|
688
|
+
if _DEFINITION_SEPARATOR not in item.local_id
|
|
689
|
+
],
|
|
690
|
+
)
|
|
684
691
|
found = next(
|
|
685
692
|
(item for item in workflows if item.local_id == identifier), None
|
|
686
693
|
)
|
|
@@ -470,7 +470,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
470
470
|
"workflow",
|
|
471
471
|
nargs="?",
|
|
472
472
|
metavar="WORKFLOW",
|
|
473
|
-
help="project-local workflow path (the root workflow
|
|
473
|
+
help="project-local workflow path (default: the only root workflow, "
|
|
474
|
+
"else main)",
|
|
474
475
|
)
|
|
475
476
|
execute.add_argument(
|
|
476
477
|
"--output-dir",
|
|
@@ -37,9 +37,6 @@ def _objects(value: Any) -> list[dict[str, Any]]:
|
|
|
37
37
|
def _scoped_subroutine_definitions(
|
|
38
38
|
project: dict[str, Any],
|
|
39
39
|
) -> list[tuple[str, dict[str, Any]]]:
|
|
40
|
-
root = _object(project.get("subroutine"))
|
|
41
|
-
if not root:
|
|
42
|
-
return []
|
|
43
40
|
found: list[tuple[str, dict[str, Any]]] = []
|
|
44
41
|
|
|
45
42
|
def visit(definition: dict[str, Any], owner: str | None) -> None:
|
|
@@ -50,7 +47,8 @@ def _scoped_subroutine_definitions(
|
|
|
50
47
|
for child in _objects(definition.get("subroutines")):
|
|
51
48
|
visit(child, identifier)
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
for root in _objects(project.get("subroutines")):
|
|
51
|
+
visit(root, None)
|
|
54
52
|
return found
|
|
55
53
|
|
|
56
54
|
|
|
@@ -208,11 +206,9 @@ def _clone(arguments: argparse.Namespace) -> int:
|
|
|
208
206
|
|
|
209
207
|
|
|
210
208
|
ENTRY_WORKFLOW = "main"
|
|
211
|
-
"""The
|
|
209
|
+
"""The root workflow/subroutine id of `blank_graph`'s one-workflow project.
|
|
212
210
|
|
|
213
|
-
|
|
214
|
-
output
|
|
215
|
-
through the compiler; the client itself depends only on the public runtime.
|
|
211
|
+
Backend integration tests and fixtures build on it; `init` starts empty.
|
|
216
212
|
"""
|
|
217
213
|
|
|
218
214
|
|
|
@@ -272,44 +268,52 @@ def blank_graph(package: str, name: str) -> dict[str, Any]:
|
|
|
272
268
|
"schema_version": local.SCHEMA_VERSION,
|
|
273
269
|
"package": package,
|
|
274
270
|
"generated_from": "0" * 64,
|
|
275
|
-
"
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"name": name,
|
|
285
|
-
"ports": {"enter": "enter", "exit": "exit", "failure": "failure"},
|
|
286
|
-
"features": [],
|
|
287
|
-
"profiles": [],
|
|
288
|
-
"profile_parameters": [],
|
|
289
|
-
"sessions": [],
|
|
290
|
-
"session_parameters": [],
|
|
291
|
-
"nodes": [
|
|
292
|
-
port("enter", "Enter"),
|
|
293
|
-
port("exit", "Exit"),
|
|
294
|
-
port("failure", "Failure"),
|
|
295
|
-
],
|
|
296
|
-
"edges": [
|
|
297
|
-
{
|
|
298
|
-
"id": "enter__exit",
|
|
299
|
-
"name": "Pass through",
|
|
300
|
-
"source": "enter",
|
|
301
|
-
"target": "exit",
|
|
302
|
-
"conditions": [],
|
|
303
|
-
"effects": [],
|
|
304
|
-
}
|
|
305
|
-
],
|
|
306
|
-
"workflows": [],
|
|
307
|
-
"subroutines": [],
|
|
308
|
-
},
|
|
271
|
+
"workflows": [
|
|
272
|
+
{
|
|
273
|
+
"subroutine": ENTRY_WORKFLOW,
|
|
274
|
+
"profiles": [],
|
|
275
|
+
"sessions": [],
|
|
276
|
+
"profile_arguments": {},
|
|
277
|
+
"session_arguments": {},
|
|
278
|
+
}
|
|
279
|
+
],
|
|
309
280
|
"externals": [],
|
|
310
281
|
"sources": [],
|
|
311
282
|
"editor": {"layouts": {}},
|
|
312
283
|
"extensions": {},
|
|
284
|
+
"subroutines": [
|
|
285
|
+
{
|
|
286
|
+
"id": ENTRY_WORKFLOW,
|
|
287
|
+
"name": name,
|
|
288
|
+
"ports": {
|
|
289
|
+
"enter": "enter",
|
|
290
|
+
"exit": "exit",
|
|
291
|
+
"failure": "failure",
|
|
292
|
+
},
|
|
293
|
+
"features": [],
|
|
294
|
+
"profiles": [],
|
|
295
|
+
"profile_parameters": [],
|
|
296
|
+
"sessions": [],
|
|
297
|
+
"session_parameters": [],
|
|
298
|
+
"nodes": [
|
|
299
|
+
port("enter", "Enter"),
|
|
300
|
+
port("exit", "Exit"),
|
|
301
|
+
port("failure", "Failure"),
|
|
302
|
+
],
|
|
303
|
+
"edges": [
|
|
304
|
+
{
|
|
305
|
+
"id": "enter__exit",
|
|
306
|
+
"name": "Pass through",
|
|
307
|
+
"source": "enter",
|
|
308
|
+
"target": "exit",
|
|
309
|
+
"conditions": [],
|
|
310
|
+
"effects": [],
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
"workflows": [],
|
|
314
|
+
"subroutines": [],
|
|
315
|
+
}
|
|
316
|
+
],
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
|
|
@@ -355,9 +359,14 @@ def _init(arguments: argparse.Namespace) -> int:
|
|
|
355
359
|
)
|
|
356
360
|
|
|
357
361
|
root.mkdir(parents=True, exist_ok=True)
|
|
362
|
+
# An empty project: root workflows and subroutines are added in the editor.
|
|
363
|
+
project: dict[str, Any] = {
|
|
364
|
+
**blank_graph(package, name),
|
|
365
|
+
"workflows": [],
|
|
366
|
+
"subroutines": [],
|
|
367
|
+
}
|
|
358
368
|
(root / "project.json").write_text(
|
|
359
|
-
json.dumps(
|
|
360
|
-
+ "\n",
|
|
369
|
+
json.dumps(project, indent=2, ensure_ascii=False) + "\n",
|
|
361
370
|
encoding="utf-8",
|
|
362
371
|
)
|
|
363
372
|
# Git, because every other verb finds the project by walking up to a `.git`.
|
|
@@ -372,13 +381,15 @@ def _init(arguments: argparse.Namespace) -> int:
|
|
|
372
381
|
local.write_config(root, origin)
|
|
373
382
|
|
|
374
383
|
print(f"Started {package} in {root}")
|
|
375
|
-
print(f" workflow {ENTRY_WORKFLOW!r}: enter -> exit, with a failure port")
|
|
376
384
|
|
|
377
385
|
from verdog_cli import main
|
|
378
386
|
|
|
379
387
|
status = main.generate_clone(local.open_clone(root))
|
|
380
388
|
if status == 0:
|
|
381
|
-
print(
|
|
389
|
+
print(
|
|
390
|
+
"Next: add a subroutine and make it runnable, then run "
|
|
391
|
+
"`verdog sync` to create its runtime environment."
|
|
392
|
+
)
|
|
382
393
|
return status
|
|
383
394
|
|
|
384
395
|
|
|
@@ -582,9 +593,9 @@ def publish_workflow(arguments: argparse.Namespace) -> int:
|
|
|
582
593
|
)
|
|
583
594
|
repository = f"{owner}/{name}"
|
|
584
595
|
commit = clone_head(clone, repository)
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
596
|
+
descriptor = cli_catalogue.describe_workflow(
|
|
597
|
+
clone, arguments.workflow or None
|
|
598
|
+
)
|
|
588
599
|
if arguments.private:
|
|
589
600
|
print(
|
|
590
601
|
"verdog: --private is deprecated; catalogue "
|
|
@@ -1065,11 +1076,19 @@ def _prepare_import(arguments: argparse.Namespace) -> _ImportPlan:
|
|
|
1065
1076
|
|
|
1066
1077
|
|
|
1067
1078
|
def _import_parent(project: dict[str, Any], requested: object) -> str:
|
|
1068
|
-
root = _object(project.get("subroutine"))
|
|
1069
|
-
parent = str(requested or root.get("id", ""))
|
|
1070
1079
|
known = {
|
|
1071
1080
|
identifier for identifier, _ in _scoped_subroutine_definitions(project)
|
|
1072
1081
|
}
|
|
1082
|
+
parent = str(
|
|
1083
|
+
requested
|
|
1084
|
+
or local.default_root(
|
|
1085
|
+
"subroutine",
|
|
1086
|
+
[
|
|
1087
|
+
str(root.get("id"))
|
|
1088
|
+
for root in _objects(project.get("subroutines"))
|
|
1089
|
+
],
|
|
1090
|
+
)
|
|
1091
|
+
)
|
|
1073
1092
|
if parent not in known:
|
|
1074
1093
|
raise local.WorkspaceError(f"this project has no subroutine {parent}")
|
|
1075
1094
|
return parent
|
|
@@ -2078,7 +2097,7 @@ def register(commands: Any) -> None:
|
|
|
2078
2097
|
who.set_defaults(handler=_whoami)
|
|
2079
2098
|
|
|
2080
2099
|
started = commands.add_parser("init", help="start a new project here")
|
|
2081
|
-
started.add_argument("name", nargs="?", help="what the
|
|
2100
|
+
started.add_argument("name", nargs="?", help="what the project is called")
|
|
2082
2101
|
started.add_argument(
|
|
2083
2102
|
"directory", nargs="?", help="where to put it (default: here)"
|
|
2084
2103
|
)
|
|
@@ -2145,7 +2164,8 @@ def register(commands: Any) -> None:
|
|
|
2145
2164
|
describe.add_argument(
|
|
2146
2165
|
"workflow",
|
|
2147
2166
|
nargs="?",
|
|
2148
|
-
help="project-local workflow path
|
|
2167
|
+
help="project-local workflow path (default: the only root workflow, "
|
|
2168
|
+
"else main)",
|
|
2149
2169
|
)
|
|
2150
2170
|
describe.add_argument(
|
|
2151
2171
|
"--project",
|
|
@@ -2171,7 +2191,8 @@ def register(commands: Any) -> None:
|
|
|
2171
2191
|
publish.add_argument(
|
|
2172
2192
|
"workflow",
|
|
2173
2193
|
nargs="?",
|
|
2174
|
-
help="project-local workflow path
|
|
2194
|
+
help="project-local workflow path (default: the only root workflow, "
|
|
2195
|
+
"else main)",
|
|
2175
2196
|
)
|
|
2176
2197
|
publish.add_argument(
|
|
2177
2198
|
"--private",
|
|
@@ -2207,7 +2228,9 @@ def register(commands: Any) -> None:
|
|
|
2207
2228
|
"workflow", help="the published workflow path to call"
|
|
2208
2229
|
)
|
|
2209
2230
|
imported.add_argument(
|
|
2210
|
-
"--into",
|
|
2231
|
+
"--into",
|
|
2232
|
+
help="the project-local subroutine path that declares it "
|
|
2233
|
+
"(default: the only root subroutine, else main)",
|
|
2211
2234
|
)
|
|
2212
2235
|
imported.add_argument(
|
|
2213
2236
|
"--alias",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: verdog-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: The Verdog command-line client.
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
Project-URL: Repository, https://github.com/verdog-ai/verdog-cli
|
|
@@ -8,7 +8,7 @@ Project-URL: Documentation, https://drexlerd.github.io/verdog-website/
|
|
|
8
8
|
Requires-Python: >=3.12
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: verdog-runtime<0.2,>=0.1.
|
|
11
|
+
Requires-Dist: verdog-runtime<0.2,>=0.1.6
|
|
12
12
|
Requires-Dist: packaging>=23.2
|
|
13
13
|
Requires-Dist: tabulate>=0.10
|
|
14
14
|
Requires-Dist: ty==0.0.72
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|