wt-compiler 0.2.0__tar.gz → 0.3.0__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 (79) hide show
  1. wt_compiler-0.3.0/CHANGELOG.md +26 -0
  2. {wt_compiler-0.2.0/src/wt_compiler.egg-info → wt_compiler-0.3.0}/PKG-INFO +2 -2
  3. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/pyproject.toml +3 -3
  4. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/_version.py +3 -3
  5. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/compiler.py +3 -8
  6. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/requirements.py +1 -0
  7. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/abstract.py +1 -0
  8. wt_compiler-0.3.0/src/wt_compiler/wizard/templates/.github/workflows/ci.yml.jinja2 +97 -0
  9. wt_compiler-0.3.0/src/wt_compiler/wizard/templates/.github/workflows/tag.yml.jinja2 +97 -0
  10. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/.gitignore.jinja2 +3 -0
  11. {wt_compiler-0.2.0 → wt_compiler-0.3.0/src/wt_compiler.egg-info}/PKG-INFO +2 -2
  12. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler.egg-info/SOURCES.txt +2 -0
  13. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler.egg-info/requires.txt +1 -1
  14. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_wizard_default.py +58 -2
  15. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_wizard_extensibility.py +15 -8
  16. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/uv.lock +4 -4
  17. wt_compiler-0.2.0/CHANGELOG.md +0 -16
  18. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/README.md +0 -0
  19. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/setup.cfg +0 -0
  20. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/__init__.py +0 -0
  21. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/__main__.py +0 -0
  22. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/_models.py +0 -0
  23. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/artifacts.py +0 -0
  24. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/cli.py +0 -0
  25. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/discovery.py +0 -0
  26. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/exceptions.py +0 -0
  27. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/formatting.py +0 -0
  28. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/jsonschema.py +0 -0
  29. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/progress.py +0 -0
  30. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/py.typed +0 -0
  31. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/pypi_source.py +0 -0
  32. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/spec.py +0 -0
  33. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/Dockerfile.jinja2 +0 -0
  34. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/README.jinja2 +0 -0
  35. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/dockerignore.jinja2 +0 -0
  36. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pixi.jinja2 +0 -0
  37. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/cli.jinja2 +0 -0
  38. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/dags/_macros.jinja2 +0 -0
  39. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/dags/init.jinja2 +0 -0
  40. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/dags/run_async.jinja2 +0 -0
  41. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/dags/run_sequential.jinja2 +0 -0
  42. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/dispatch.jinja2 +0 -0
  43. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/metadata.jinja2 +0 -0
  44. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/pkg/response.jinja2 +0 -0
  45. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/tests/conftest.jinja2 +0 -0
  46. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/tests/test_metadata.jinja2 +0 -0
  47. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/templates/tests/test_results.jinja2 +0 -0
  48. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/util.py +0 -0
  49. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/README.md +0 -0
  50. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/__init__.py +0 -0
  51. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/default.py +0 -0
  52. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/.gitattributes.jinja2 +0 -0
  53. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/LICENSE.jinja2 +0 -0
  54. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/README.md.jinja2 +0 -0
  55. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/licenses/Apache-2.0.txt +0 -0
  56. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/licenses/BSD-3-Clause.txt +0 -0
  57. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/licenses/MIT.txt +0 -0
  58. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/spec.yaml.jinja2 +0 -0
  59. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler/wizard/templates/test-cases.yaml.jinja2 +0 -0
  60. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler.egg-info/dependency_links.txt +0 -0
  61. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler.egg-info/entry_points.txt +0 -0
  62. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/src/wt_compiler.egg-info/top_level.txt +0 -0
  63. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/__init__.py +0 -0
  64. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/conftest.py +0 -0
  65. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/fixtures/simple_spec.yaml +0 -0
  66. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_artifacts.py +0 -0
  67. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_cli.py +0 -0
  68. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_compiler.py +0 -0
  69. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_discovery_integration.py +0 -0
  70. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_exceptions.py +0 -0
  71. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_jsonschema.py +0 -0
  72. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_progress.py +0 -0
  73. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_pypi_discovery.py +0 -0
  74. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_pypi_source.py +0 -0
  75. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_requirements.py +0 -0
  76. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_response_model.py +0 -0
  77. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_spec.py +0 -0
  78. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_wizard_abstract.py +0 -0
  79. {wt_compiler-0.2.0 → wt_compiler-0.3.0}/tests/test_wizard_cli_compat.py +0 -0
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## v0.3.0 — 2026-03-19
4
+
5
+ - Add CI and Tag GitHub Actions workflow templates to the wizard scaffold ([#83](https://github.com/wildlife-dynamics/wt/pull/83))
6
+ - Add `.gitignore` template to the wizard scaffold ([#83](https://github.com/wildlife-dynamics/wt/pull/83))
7
+ - Add `linux-aarch64` platform support ([#83](https://github.com/wildlife-dynamics/wt/pull/83))
8
+ - Fix wizard template rendering for nested output paths ([#83](https://github.com/wildlife-dynamics/wt/pull/83))
9
+ - Pin compiled output version bounds for wt-task (`>=0.1.2`) and wt-runner (`>=0.1.4`) ([#96](https://github.com/wildlife-dynamics/wt/pull/96))
10
+ - Remove errant injection of wt-task into wt-runner conda environment ([#96](https://github.com/wildlife-dynamics/wt/pull/96))
11
+ - Pin `datamodel-code-generator` to `==0.42.1` ([#96](https://github.com/wildlife-dynamics/wt/pull/96))
12
+
13
+ ## v0.2.0 — 2026-03-13
14
+
15
+ - Support PyPI dependencies (`git`/`path`/`url` sources) in workflow specs, emitted as `[pypi-dependencies]` in compiled `pixi.toml` ([#60](https://github.com/wildlife-dynamics/wt/pull/60))
16
+ - Generalize compiled `ResponseModel.result` from `DashboardJson | OutputFiles` to `Any`, removing hard dependency on `ecoscope_workflows_core` types ([#61](https://github.com/wildlife-dynamics/wt/pull/61))
17
+ - Make Graphviz graph generation best-effort — warns instead of failing when `dot` binary is unavailable ([#60](https://github.com/wildlife-dynamics/wt/pull/60))
18
+ - Add `wt_registry` entry-point auto-discovery to task discovery pipeline ([#60](https://github.com/wildlife-dynamics/wt/pull/60))
19
+
20
+ ## v0.1.1 — 2026-03-05
21
+
22
+ - Fix incorrect license metadata
23
+
24
+ ## v0.1.0 — 2026-03-05
25
+
26
+ - Initial release
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wt-compiler
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Workflow compiler for generating DAG artifacts from workflow specifications
5
5
  Author: Ecoscope Team
6
6
  License: BSD-3-Clause
@@ -16,7 +16,7 @@ Requires-Dist: pydantic<3.0.0,>=2.0.0
16
16
  Requires-Dist: jinja2>=3.0.0
17
17
  Requires-Dist: ruamel.yaml>=0.17.0
18
18
  Requires-Dist: py-rattler<0.23.0,>=0.22.0
19
- Requires-Dist: datamodel-code-generator>=0.25.0
19
+ Requires-Dist: datamodel-code-generator==0.42.1
20
20
  Requires-Dist: pydot>=1.4.0
21
21
  Requires-Dist: ruff>=0.1.0
22
22
  Requires-Dist: tomli-w>=1.0.0
@@ -25,7 +25,7 @@ dependencies = [
25
25
  "jinja2>=3.0.0",
26
26
  "ruamel.yaml>=0.17.0",
27
27
  "py-rattler>=0.22.0,<0.23.0",
28
- "datamodel-code-generator>=0.25.0",
28
+ "datamodel-code-generator==0.42.1",
29
29
  "pydot>=1.4.0",
30
30
  "ruff>=0.1.0",
31
31
  "tomli-w>=1.0.0",
@@ -111,7 +111,7 @@ ignore = []
111
111
 
112
112
  [tool.pixi.package]
113
113
  name = "wt-compiler"
114
- version = "0.2.0"
114
+ version = "0.3.0"
115
115
 
116
116
  [tool.pixi.package.build]
117
117
  backend = { name = "pixi-build-python", version = "*", channels = ["conda-forge", "https://prefix.dev/pixi-build-backends"] }
@@ -131,7 +131,7 @@ pydantic = ">=2.0.0,<3.0.0"
131
131
  jinja2 = ">=3.0.0"
132
132
  "ruamel.yaml" = ">=0.17.0"
133
133
  py-rattler = ">=0.22.0,<0.23.0"
134
- datamodel-code-generator = ">=0.25.0"
134
+ datamodel-code-generator = "==0.42.1"
135
135
  pydot = ">=1.4.0"
136
136
  ruff = ">=0.1.0"
137
137
  tomli-w = ">=1.0.0"
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.0'
32
- __version_tuple__ = version_tuple = (0, 2, 0)
31
+ __version__ = version = '0.3.0'
32
+ __version_tuple__ = version_tuple = (0, 3, 0)
33
33
 
34
- __commit_id__ = commit_id = 'geecefd3'
34
+ __commit_id__ = commit_id = 'g7b40845'
@@ -471,7 +471,7 @@ class DagCompiler(BaseModel):
471
471
  # Only add wt-task to conda deps when not using PyPI mode
472
472
  if self.wt_pypi_deps is None:
473
473
  cli_runtime_deps[task_pkg] = NamelessMatchSpec.from_match_spec(
474
- MatchSpec(f"{runner_channel}::{task_pkg} *")
474
+ MatchSpec(f"{runner_channel}::{task_pkg} >=0.1.2,<1.0.0")
475
475
  )
476
476
  dependencies.update(cli_runtime_deps)
477
477
 
@@ -480,15 +480,10 @@ class DagCompiler(BaseModel):
480
480
  runner_pypi_deps: dict[str, str | dict[str, Any]] = {}
481
481
 
482
482
  if self.wt_pypi_deps is None:
483
- # Conda mode: wt-runner/wt-task come from conda channel
483
+ # Conda mode: wt-runner comes from conda channel
484
484
  runner_conda_deps[runner_pkg] = NamelessMatchSpec.from_match_spec(
485
- MatchSpec(f"{runner_channel}::{runner_pkg} *")
485
+ MatchSpec(f"{runner_channel}::{runner_pkg} >=0.1.4,<1.0.0")
486
486
  )
487
- if self.variant:
488
- task_pkg = f"wt-task-{self.variant}"
489
- runner_conda_deps[task_pkg] = NamelessMatchSpec.from_match_spec(
490
- MatchSpec(f"{runner_channel}::{task_pkg} *")
491
- )
492
487
  else:
493
488
  # PyPI mode: wt-runner goes in runner feature pypi-deps,
494
489
  # wt-task goes in top-level pypi-deps
@@ -43,6 +43,7 @@ CHANNELS: list[Channel] = [
43
43
  ]
44
44
  PLATFORMS: list[Platform] = [
45
45
  Platform("linux-64"),
46
+ Platform("linux-aarch64"),
46
47
  Platform("osx-arm64"),
47
48
  Platform("win-64"),
48
49
  Platform("osx-64"),
@@ -384,4 +384,5 @@ class AbstractWizardProvider(ABC):
384
384
  template = env.get_template(tname)
385
385
  output_name = tname.removesuffix(".jinja2")
386
386
  output_path = workdir / output_name
387
+ output_path.parent.mkdir(parents=True, exist_ok=True)
387
388
  output_path.write_text(template.render(context))
@@ -0,0 +1,97 @@
1
+ {% raw %}
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: ["main"]
7
+ pull_request:
8
+ branches: ["**"]
9
+
10
+ jobs:
11
+ parse-test-cases:
12
+ runs-on: ubuntu-latest
13
+ outputs:
14
+ cases: ${{ steps.parse.outputs.cases }}
15
+ first_case: ${{ steps.parse.outputs.first_case }}
16
+ release_dir: ${{ steps.parse.outputs.release_dir }}
17
+
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ - name: Parse test cases
21
+ id: parse
22
+ run: |
23
+ # Discover release directory
24
+ shopt -s nullglob
25
+ dirs=(*-workflow/)
26
+ if [ ${#dirs[@]} -eq 0 ]; then
27
+ echo "Error: no *-workflow directory found" >&2; exit 1
28
+ elif [ ${#dirs[@]} -gt 1 ]; then
29
+ echo "Error: multiple *-workflow directories found: ${dirs[*]}" >&2; exit 1
30
+ fi
31
+ echo "release_dir=${dirs[0]%/}" >> $GITHUB_OUTPUT
32
+
33
+ # Fail if no test cases defined
34
+ keys=$(yq -o=json -I=0 'keys' test-cases.yaml)
35
+ if [ "$keys" = "[]" ] || [ "$keys" = "null" ]; then
36
+ echo "::error::test-cases.yaml has no test cases defined" && exit 1
37
+ fi
38
+ # Fail if any case is missing .params
39
+ for case in $(yq 'keys | .[]' test-cases.yaml); do
40
+ params=$(yq ".[\"$case\"].params" test-cases.yaml)
41
+ if [ "$params" = "null" ]; then
42
+ echo "::error::test case '$case' is missing required 'params' field" && exit 1
43
+ fi
44
+ done
45
+
46
+ echo "cases=$keys" >> $GITHUB_OUTPUT
47
+ echo "first_case=$(yq 'keys | .[0]' test-cases.yaml)" >> $GITHUB_OUTPUT
48
+
49
+ test:
50
+ needs: parse-test-cases
51
+ runs-on: ubuntu-latest
52
+ strategy:
53
+ fail-fast: false
54
+ matrix:
55
+ case: ${{ fromJSON(needs.parse-test-cases.outputs.cases) }}
56
+
57
+ steps:
58
+ - uses: actions/checkout@v6
59
+ - name: Install pixi
60
+ run: curl -fsSL https://pixi.sh/install.sh | bash && echo "$HOME/.pixi/bin" >> $GITHUB_PATH
61
+ - name: Set CI variables
62
+ run: |
63
+ echo "RELEASE_DIR=${{ needs.parse-test-cases.outputs.release_dir }}" >> $GITHUB_ENV
64
+ - name: Test
65
+ run: >-
66
+ pixi run --manifest-path $RELEASE_DIR/pixi.toml
67
+ --locked -e test
68
+ test-app-sequential-mock-io
69
+ --case ${{ matrix.case }}
70
+
71
+ docker:
72
+ needs: parse-test-cases
73
+ runs-on: ubuntu-latest
74
+ steps:
75
+ - uses: actions/checkout@v6
76
+ - name: Install pixi
77
+ run: curl -fsSL https://pixi.sh/install.sh | bash && echo "$HOME/.pixi/bin" >> $GITHUB_PATH
78
+ - name: Set CI variables
79
+ run: |
80
+ echo "RELEASE_DIR=${{ needs.parse-test-cases.outputs.release_dir }}" >> $GITHUB_ENV
81
+ echo "CI_TEST_CASE=${{ needs.parse-test-cases.outputs.first_case }}" >> $GITHUB_ENV
82
+ - name: Docker build + run
83
+ run: |
84
+ pixi run --manifest-path $RELEASE_DIR/pixi.toml --locked -e default docker-build
85
+ docker run --name $RELEASE_DIR -d \
86
+ -v $(pwd)/.wt-tmp/$RELEASE_DIR-results:/workflow/results \
87
+ -e PORT=4000 -p 4000:4000 $RELEASE_DIR
88
+ sleep 5
89
+ docker ps
90
+ - name: Invoke container with curl
91
+ run: |
92
+ params=$(yq -o=json -I=0 ".[\"$CI_TEST_CASE\"].params" test-cases.yaml)
93
+ curl --fail-with-body \
94
+ -X POST "http://localhost:4000/?execution_mode=sequential&mock_io=true&results_url=/workflow/results" \
95
+ -H "Content-Type: application/json" \
96
+ -d '{"params": '"${params}"'}'
97
+ {% endraw %}
@@ -0,0 +1,97 @@
1
+ {% raw %}
2
+ name: Tag
3
+
4
+ on:
5
+ pull_request:
6
+ types: [opened, reopened, synchronize, closed]
7
+ branches:
8
+ - 'main'
9
+ - 'patch-*'
10
+
11
+ jobs:
12
+ generate-tag:
13
+ runs-on: ubuntu-latest
14
+ outputs:
15
+ tag: ${{ steps.create_tag.outputs.result }}
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ with:
19
+ fetch-depth: 0
20
+ - name: List existing tags
21
+ run: git tag -l
22
+ - name: Find release directory
23
+ id: find_release_dir
24
+ run: |
25
+ shopt -s nullglob
26
+ dirs=(*-workflow/)
27
+ if [ ${#dirs[@]} -eq 0 ]; then
28
+ echo "Error: no *-workflow directory found" >&2; exit 1
29
+ elif [ ${#dirs[@]} -gt 1 ]; then
30
+ echo "Error: multiple *-workflow directories found: ${dirs[*]}" >&2; exit 1
31
+ fi
32
+ echo "name=${dirs[0]%/}" >> $GITHUB_OUTPUT
33
+ - name: Create tag from VERSION.yaml
34
+ id: create_tag
35
+ uses: mikefarah/yq@v4
36
+ with:
37
+ cmd: yq -r '"v" + (.MAJ | tostring) + "." + (.MIN | tostring) + "." + (.PATCH | tostring)' ${{ steps.find_release_dir.outputs.name }}/VERSION.yaml
38
+ - name: Validate tag doesn't already exist
39
+ run: |
40
+ if git tag -l | grep -q "${{ steps.create_tag.outputs.result }}"; then
41
+ echo "Tag ${{ steps.create_tag.outputs.result }} already exists. Exiting."
42
+ exit 1
43
+ else
44
+ echo "Tag ${{ steps.create_tag.outputs.result }} does not exist. Proceeding."
45
+ fi
46
+ - name: Validate tag contains all three semantic elements
47
+ run: |
48
+ if [[ "${{ steps.create_tag.outputs.result }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
49
+ echo "Tag ${{ steps.create_tag.outputs.result }} is valid, because it adheres to vMAJ.MIN.PATCH format."
50
+ else
51
+ echo "Tag ${{ steps.create_tag.outputs.result }} is not valid. It must adhere to vMAJ.MIN.PATCH format."
52
+ exit 1
53
+ fi
54
+ - name: If this is a PR to a patch branch, ensure the patch version is not 0
55
+ if: startsWith(github.base_ref, 'patch-')
56
+ run: |
57
+ patch_version=$(echo "${{ steps.create_tag.outputs.result }}" | cut -d '.' -f 3)
58
+ if [ "$patch_version" -ne 0 ]; then
59
+ echo "Patch version is not 0. This is an acceptable PR to a patch branch."
60
+ else
61
+ echo "Patch version is 0. Please ensure that the patch version is incremented before merging this PR."
62
+ exit 1
63
+ fi
64
+
65
+ cut-release:
66
+ needs: generate-tag
67
+ if: github.event.pull_request.merged == true
68
+ runs-on: ubuntu-latest
69
+ permissions:
70
+ contents: write
71
+ steps:
72
+ - uses: actions/checkout@v6
73
+ with:
74
+ ref: ${{ github.event.pull_request.merge_commit_sha }}
75
+ fetch-depth: 0
76
+ - name: List existing tags
77
+ run: git tag -l
78
+ - name: Tag
79
+ run: |
80
+ echo "Tagging version ${{ needs.generate-tag.outputs.tag }}"
81
+ echo "Tagging from:\n$(git log -1)"
82
+ git tag ${{ needs.generate-tag.outputs.tag }}
83
+ git tag -l
84
+ - name: Push tag
85
+ run: |
86
+ git config user.name "github-actions[bot]"
87
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
88
+ git push origin --tags
89
+ - name: Create GitHub Release
90
+ env:
91
+ GH_TOKEN: ${{ github.token }}
92
+ run: >-
93
+ gh release create
94
+ '${{ needs.generate-tag.outputs.tag }}'
95
+ --repo '${{ github.repository }}'
96
+ --generate-notes
97
+ {% endraw %}
@@ -13,3 +13,6 @@ venv/
13
13
  # Pixi
14
14
  .pixi/
15
15
  pixi.lock
16
+
17
+ # CI
18
+ .wt-tmp/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wt-compiler
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Workflow compiler for generating DAG artifacts from workflow specifications
5
5
  Author: Ecoscope Team
6
6
  License: BSD-3-Clause
@@ -16,7 +16,7 @@ Requires-Dist: pydantic<3.0.0,>=2.0.0
16
16
  Requires-Dist: jinja2>=3.0.0
17
17
  Requires-Dist: ruamel.yaml>=0.17.0
18
18
  Requires-Dist: py-rattler<0.23.0,>=0.22.0
19
- Requires-Dist: datamodel-code-generator>=0.25.0
19
+ Requires-Dist: datamodel-code-generator==0.42.1
20
20
  Requires-Dist: pydot>=1.4.0
21
21
  Requires-Dist: ruff>=0.1.0
22
22
  Requires-Dist: tomli-w>=1.0.0
@@ -50,6 +50,8 @@ src/wt_compiler/wizard/templates/LICENSE.jinja2
50
50
  src/wt_compiler/wizard/templates/README.md.jinja2
51
51
  src/wt_compiler/wizard/templates/spec.yaml.jinja2
52
52
  src/wt_compiler/wizard/templates/test-cases.yaml.jinja2
53
+ src/wt_compiler/wizard/templates/.github/workflows/ci.yml.jinja2
54
+ src/wt_compiler/wizard/templates/.github/workflows/tag.yml.jinja2
53
55
  src/wt_compiler/wizard/templates/licenses/Apache-2.0.txt
54
56
  src/wt_compiler/wizard/templates/licenses/BSD-3-Clause.txt
55
57
  src/wt_compiler/wizard/templates/licenses/MIT.txt
@@ -3,7 +3,7 @@ pydantic<3.0.0,>=2.0.0
3
3
  jinja2>=3.0.0
4
4
  ruamel.yaml>=0.17.0
5
5
  py-rattler<0.23.0,>=0.22.0
6
- datamodel-code-generator>=0.25.0
6
+ datamodel-code-generator==0.42.1
7
7
  pydot>=1.4.0
8
8
  ruff>=0.1.0
9
9
  tomli-w>=1.0.0
@@ -231,7 +231,7 @@ class TestDump:
231
231
  return provider
232
232
 
233
233
  def test_dump_creates_all_files(self, tmp_path: Path) -> None:
234
- """All 6 files exist after dump."""
234
+ """All expected files exist after dump, including nested paths."""
235
235
  provider = self._make_provider_with_answers()
236
236
  provider.dump(tmp_path)
237
237
  expected_files = [
@@ -241,6 +241,8 @@ class TestDump:
241
241
  "LICENSE",
242
242
  ".gitignore",
243
243
  ".gitattributes",
244
+ ".github/workflows/ci.yml",
245
+ ".github/workflows/tag.yml",
244
246
  ]
245
247
  for fname in expected_files:
246
248
  assert (tmp_path / fname).exists(), f"{fname} not found"
@@ -279,12 +281,13 @@ class TestDump:
279
281
  assert "MIT" in text
280
282
 
281
283
  def test_dump_gitignore_contents(self, tmp_path: Path) -> None:
282
- """Has __pycache__/, .pixi/."""
284
+ """Has __pycache__/, .pixi/, and .wt-tmp/."""
283
285
  provider = self._make_provider_with_answers()
284
286
  provider.dump(tmp_path)
285
287
  text = (tmp_path / ".gitignore").read_text()
286
288
  assert "__pycache__/" in text
287
289
  assert ".pixi/" in text
290
+ assert ".wt-tmp/" in text
288
291
 
289
292
  def test_dump_gitattributes_contents(self, tmp_path: Path) -> None:
290
293
  """Has linguist-generated=true."""
@@ -293,6 +296,59 @@ class TestDump:
293
296
  text = (tmp_path / ".gitattributes").read_text()
294
297
  assert "linguist-generated=true" in text
295
298
 
299
+ def test_dump_ci_yml_parse_test_cases_job(self, tmp_path: Path) -> None:
300
+ """CI workflow contains parse-test-cases job with expected outputs."""
301
+ provider = self._make_provider_with_answers()
302
+ provider.dump(tmp_path)
303
+ text = (tmp_path / ".github/workflows/ci.yml").read_text()
304
+ assert "parse-test-cases:" in text
305
+ assert "steps.parse.outputs.cases" in text
306
+ assert "steps.parse.outputs.first_case" in text
307
+ assert "steps.parse.outputs.release_dir" in text
308
+
309
+ def test_dump_ci_yml_validation_errors(self, tmp_path: Path) -> None:
310
+ """CI workflow contains validation error messages for empty test-cases and missing params."""
311
+ provider = self._make_provider_with_answers()
312
+ provider.dump(tmp_path)
313
+ text = (tmp_path / ".github/workflows/ci.yml").read_text()
314
+ assert "test-cases.yaml has no test cases defined" in text
315
+ assert "is missing required 'params' field" in text
316
+
317
+ def test_dump_ci_yml_test_job_matrix(self, tmp_path: Path) -> None:
318
+ """CI test job uses matrix.case strategy."""
319
+ provider = self._make_provider_with_answers()
320
+ provider.dump(tmp_path)
321
+ text = (tmp_path / ".github/workflows/ci.yml").read_text()
322
+ assert "needs: parse-test-cases" in text
323
+ assert "matrix:" in text
324
+ assert "fromJSON(needs.parse-test-cases.outputs.cases)" in text
325
+ assert "matrix.case" in text
326
+
327
+ def test_dump_ci_yml_docker_job_uses_first_case(self, tmp_path: Path) -> None:
328
+ """CI docker job uses first_case output from parse-test-cases."""
329
+ provider = self._make_provider_with_answers()
330
+ provider.dump(tmp_path)
331
+ text = (tmp_path / ".github/workflows/ci.yml").read_text()
332
+ assert "needs.parse-test-cases.outputs.first_case" in text
333
+ assert ".wt-tmp/" in text
334
+ assert "$RELEASE_DIR" in text
335
+
336
+ def test_dump_tag_yml_preserves_github_expressions(self, tmp_path: Path) -> None:
337
+ """Tag workflow preserves ${{ }} GitHub Actions expressions literally."""
338
+ provider = self._make_provider_with_answers()
339
+ provider.dump(tmp_path)
340
+ text = (tmp_path / ".github/workflows/tag.yml").read_text()
341
+ assert "${{ steps.create_tag.outputs.result }}" in text
342
+ assert "${{ steps.find_release_dir.outputs.name }}" in text
343
+ assert "${{ github.event.pull_request.merge_commit_sha }}" in text
344
+ assert "${{ needs.generate-tag.outputs.tag }}" in text
345
+
346
+ def test_dump_creates_nested_directories(self, tmp_path: Path) -> None:
347
+ """dump() creates .github/workflows/ directory automatically."""
348
+ provider = self._make_provider_with_answers()
349
+ provider.dump(tmp_path)
350
+ assert (tmp_path / ".github" / "workflows").is_dir()
351
+
296
352
  def test_dump_raises_on_incomplete_answers(self, tmp_path: Path) -> None:
297
353
  """Incomplete provider raises UndefinedError when rendering."""
298
354
  import jinja2
@@ -451,14 +451,18 @@ class TestCustomTemplates:
451
451
  assert (out_dir / "extra.yaml").exists()
452
452
  extra_content = (out_dir / "extra.yaml").read_text()
453
453
  assert "my_workflow" in extra_content
454
- # Default files also exist (7 total: 6 defaults + 1 extra)
455
- files = {f.name for f in out_dir.iterdir() if f.is_file()}
456
- default_files = {
454
+ # Default files also exist
455
+ top_level_files = {f.name for f in out_dir.iterdir() if f.is_file()}
456
+ default_top_files = {
457
457
  "spec.yaml", "test-cases.yaml", "README.md",
458
458
  "LICENSE", ".gitignore", ".gitattributes",
459
459
  }
460
- assert default_files.issubset(files)
461
- assert len(files) == 7
460
+ assert default_top_files.issubset(top_level_files)
461
+ # Extra + 6 defaults at top level = 7 files
462
+ assert len(top_level_files) == 7
463
+ # Also verify nested CI files exist
464
+ assert (out_dir / ".github" / "workflows" / "ci.yml").exists()
465
+ assert (out_dir / ".github" / "workflows" / "tag.yml").exists()
462
466
 
463
467
  def test_default_templates_inherited(self, tmp_path: Path) -> None:
464
468
  """Subclass with no custom templates inherits all defaults."""
@@ -474,6 +478,9 @@ class TestCustomTemplates:
474
478
  drive_wizard(provider, answers)
475
479
  provider.dump(tmp_path)
476
480
 
477
- expected = {"spec.yaml", "test-cases.yaml", "README.md", "LICENSE", ".gitignore", ".gitattributes"}
478
- actual = {f.name for f in tmp_path.iterdir() if f.is_file()}
479
- assert expected == actual
481
+ expected_top = {"spec.yaml", "test-cases.yaml", "README.md", "LICENSE", ".gitignore", ".gitattributes"}
482
+ actual_top = {f.name for f in tmp_path.iterdir() if f.is_file()}
483
+ assert expected_top == actual_top
484
+ # Nested CI files
485
+ assert (tmp_path / ".github" / "workflows" / "ci.yml").exists()
486
+ assert (tmp_path / ".github" / "workflows" / "tag.yml").exists()
@@ -149,7 +149,7 @@ wheels = [
149
149
 
150
150
  [[package]]
151
151
  name = "datamodel-code-generator"
152
- version = "0.43.1"
152
+ version = "0.42.1"
153
153
  source = { registry = "https://pypi.org/simple" }
154
154
  dependencies = [
155
155
  { name = "argcomplete" },
@@ -162,9 +162,9 @@ dependencies = [
162
162
  { name = "pydantic" },
163
163
  { name = "pyyaml" },
164
164
  ]
165
- sdist = { url = "https://files.pythonhosted.org/packages/56/89/424f0d73f96bc461f89fe9a60755c86692cabeab8dd5e8227e154fb0a403/datamodel_code_generator-0.43.1.tar.gz", hash = "sha256:269af3f81b89f6aa70118edd0d68a74164ae615827c46d71462c2e96441e40ae", size = 518833, upload-time = "2025-12-12T07:03:28.81Z" }
165
+ sdist = { url = "https://files.pythonhosted.org/packages/4f/0b/03bc35aa726bfc8693c2e6d973df0822b7fe357f466b78e494b11ba5aef2/datamodel_code_generator-0.42.1.tar.gz", hash = "sha256:a7513cf51f6a04cb8e3a390143bd97774a29894568c2ddab28fe49b2b3358e4a", size = 502513, upload-time = "2025-12-08T14:55:03.556Z" }
166
166
  wheels = [
167
- { url = "https://files.pythonhosted.org/packages/91/5e/afad23a916a5e9979e0626c34ecedbd00284305ae5525cef2dbbdfddacd7/datamodel_code_generator-0.43.1-py3-none-any.whl", hash = "sha256:5b2a1c47c6940d441e7394765affce614fadb627853370a385953c718137ead5", size = 173009, upload-time = "2025-12-12T07:03:27.223Z" },
167
+ { url = "https://files.pythonhosted.org/packages/51/d7/35796dc80be514f8f05d3e6a8d462bc384f9c52b03e4d3ca9562f0de2095/datamodel_code_generator-0.42.1-py3-none-any.whl", hash = "sha256:60771ef27bf2ed8c705a5fd7719493b5f87e0372ff1fb91a754680c0d71ec9cd", size = 169360, upload-time = "2025-12-08T14:55:01.655Z" },
168
168
  ]
169
169
 
170
170
  [[package]]
@@ -810,7 +810,7 @@ dev = [
810
810
 
811
811
  [package.metadata]
812
812
  requires-dist = [
813
- { name = "datamodel-code-generator", specifier = ">=0.25.0" },
813
+ { name = "datamodel-code-generator", specifier = "==0.42.1" },
814
814
  { name = "jinja2", specifier = ">=3.0.0" },
815
815
  { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" },
816
816
  { name = "py-rattler", specifier = ">=0.22.0,<0.23.0" },
@@ -1,16 +0,0 @@
1
- # Changelog
2
-
3
- ## v0.2.0 — 2026-03-13
4
-
5
- - Support PyPI dependencies (`git`/`path`/`url` sources) in workflow specs, emitted as `[pypi-dependencies]` in compiled `pixi.toml` ([#60](https://github.com/wildlife-dynamics/wt/pull/60))
6
- - Generalize compiled `ResponseModel.result` from `DashboardJson | OutputFiles` to `Any`, removing hard dependency on `ecoscope_workflows_core` types ([#61](https://github.com/wildlife-dynamics/wt/pull/61))
7
- - Make Graphviz graph generation best-effort — warns instead of failing when `dot` binary is unavailable ([#60](https://github.com/wildlife-dynamics/wt/pull/60))
8
- - Add `wt_registry` entry-point auto-discovery to task discovery pipeline ([#60](https://github.com/wildlife-dynamics/wt/pull/60))
9
-
10
- ## v0.1.1 — 2026-03-05
11
-
12
- - Fix incorrect license metadata
13
-
14
- ## v0.1.0 — 2026-03-05
15
-
16
- - Initial release
File without changes
File without changes