calkit-python 0.27.3__tar.gz → 0.28.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 (175) hide show
  1. {calkit_python-0.27.3 → calkit_python-0.28.0}/.github/workflows/test.yml +4 -0
  2. {calkit_python-0.27.3 → calkit_python-0.28.0}/PKG-INFO +7 -4
  3. {calkit_python-0.27.3 → calkit_python-0.28.0}/README.md +4 -1
  4. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/__init__.py +1 -1
  5. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/check.py +26 -0
  6. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/main.py +33 -0
  7. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/new.py +154 -13
  8. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/core.py +4 -1
  9. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/docker.py +12 -1
  10. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/environments.py +14 -0
  11. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/models/pipeline.py +17 -0
  12. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/test_main.py +64 -0
  13. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/test_new.py +23 -0
  14. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/environments.md +22 -0
  15. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/index.md +4 -1
  16. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/pipeline/index.md +4 -0
  17. {calkit_python-0.27.3 → calkit_python-0.28.0}/pyproject.toml +2 -2
  18. {calkit_python-0.27.3 → calkit_python-0.28.0}/uv.lock +354 -351
  19. {calkit_python-0.27.3 → calkit_python-0.28.0}/.github/FUNDING.yml +0 -0
  20. {calkit_python-0.27.3 → calkit_python-0.28.0}/.github/workflows/docs.yml +0 -0
  21. {calkit_python-0.27.3 → calkit_python-0.28.0}/.github/workflows/format.yml +0 -0
  22. {calkit_python-0.27.3 → calkit_python-0.28.0}/.github/workflows/publish-test.yml +0 -0
  23. {calkit_python-0.27.3 → calkit_python-0.28.0}/.github/workflows/publish.yml +0 -0
  24. {calkit_python-0.27.3 → calkit_python-0.28.0}/.gitignore +0 -0
  25. {calkit_python-0.27.3 → calkit_python-0.28.0}/.pre-commit-config.yaml +0 -0
  26. {calkit_python-0.27.3 → calkit_python-0.28.0}/.python-version +0 -0
  27. {calkit_python-0.27.3 → calkit_python-0.28.0}/CITATION.cff +0 -0
  28. {calkit_python-0.27.3 → calkit_python-0.28.0}/CONTRIBUTING.md +0 -0
  29. {calkit_python-0.27.3 → calkit_python-0.28.0}/LICENSE +0 -0
  30. {calkit_python-0.27.3 → calkit_python-0.28.0}/Makefile +0 -0
  31. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/__main__.py +0 -0
  32. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/calc.py +0 -0
  33. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/check.py +0 -0
  34. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/__init__.py +0 -0
  35. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/cloud.py +0 -0
  36. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/config.py +0 -0
  37. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/core.py +0 -0
  38. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/describe.py +0 -0
  39. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/import_.py +0 -0
  40. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/list.py +0 -0
  41. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/notebooks.py +0 -0
  42. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/office.py +0 -0
  43. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/overleaf.py +0 -0
  44. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cli/update.py +0 -0
  45. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/cloud.py +0 -0
  46. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/conda.py +0 -0
  47. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/config.py +0 -0
  48. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/datasets.py +0 -0
  49. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/dvc.py +0 -0
  50. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/git.py +0 -0
  51. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/github.py +0 -0
  52. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/gui.py +0 -0
  53. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/jupyter.py +0 -0
  54. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/magics.py +0 -0
  55. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/matlab.py +0 -0
  56. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/models/__init__.py +0 -0
  57. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/models/core.py +0 -0
  58. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/models/io.py +0 -0
  59. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/models/iteration.py +0 -0
  60. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/notebooks.py +0 -0
  61. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/office.py +0 -0
  62. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/ops.py +0 -0
  63. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/pipeline.py +0 -0
  64. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/releases.py +0 -0
  65. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/server.py +0 -0
  66. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/__init__.py +0 -0
  67. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/core.py +0 -0
  68. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/__init__.py +0 -0
  69. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/article/paper.tex +0 -0
  70. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/core.py +0 -0
  71. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/jfm/jfm.bst +0 -0
  72. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/jfm/jfm.cls +0 -0
  73. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  74. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/jfm/paper.tex +0 -0
  75. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/templates/latex/jfm/upmath.sty +0 -0
  76. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/__init__.py +0 -0
  77. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/__init__.py +0 -0
  78. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/test_check.py +0 -0
  79. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/test_config.py +0 -0
  80. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/test_list.py +0 -0
  81. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/cli/test_notebooks.py +0 -0
  82. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/models/__init__.py +0 -0
  83. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/models/test_iteration.py +0 -0
  84. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/models/test_pipeline.py +0 -0
  85. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_calc.py +0 -0
  86. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_check.py +0 -0
  87. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_conda.py +0 -0
  88. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_core.py +0 -0
  89. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_dvc.py +0 -0
  90. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_jupyter.py +0 -0
  91. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_magics.py +0 -0
  92. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_notebooks.py +0 -0
  93. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_pipeline.py +0 -0
  94. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/tests/test_templates.py +0 -0
  95. {calkit_python-0.27.3 → calkit_python-0.28.0}/calkit/zenodo.py +0 -0
  96. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/CNAME +0 -0
  97. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/apps.md +0 -0
  98. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/calculations.md +0 -0
  99. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/calkit-yaml.md +0 -0
  100. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/cli-reference.md +0 -0
  101. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/cloud-integration.md +0 -0
  102. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/datasets.md +0 -0
  103. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/dependencies.md +0 -0
  104. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/examples.md +0 -0
  105. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/help.md +0 -0
  106. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/img/c-to-the-k-white.svg +0 -0
  107. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/img/calkit-no-bg.png +0 -0
  108. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/img/connect-zenodo.png +0 -0
  109. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/img/jupyterlab-params.png +0 -0
  110. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/img/vscode-nb-params.png +0 -0
  111. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/installation.md +0 -0
  112. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/local-server.md +0 -0
  113. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/notebooks.md +0 -0
  114. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/overleaf.md +0 -0
  115. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/pipeline/manual-steps.md +0 -0
  116. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/pipeline/running-and-logging.md +0 -0
  117. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/quickstart.md +0 -0
  118. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/references.md +0 -0
  119. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/releases.md +0 -0
  120. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  121. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/conda-envs.md +0 -0
  122. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/existing-project.md +0 -0
  123. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/first-project.md +0 -0
  124. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  125. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  126. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  127. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  128. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  129. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  130. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  131. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  132. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  133. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  134. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  135. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  136. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  137. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  138. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/create-project.png +0 -0
  139. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  140. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/excel-chart.png +0 -0
  141. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/excel-data.png +0 -0
  142. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  143. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/needs-clone.png +0 -0
  144. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/new-stage.png +0 -0
  145. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  146. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  147. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/status-more-rows.png +0 -0
  148. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  149. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/untracked-data.png +0 -0
  150. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/updated-publication.png +0 -0
  151. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  152. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/office/workflow-page.png +0 -0
  153. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/clone.png +0 -0
  154. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/create-project.png +0 -0
  155. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  156. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  157. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  158. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/new-token.png +0 -0
  159. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/reclone.png +0 -0
  160. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  161. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/img/run-proc.png +0 -0
  162. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/index.md +0 -0
  163. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/latex-codespaces.md +0 -0
  164. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/matlab.md +0 -0
  165. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/notebook-pipeline.md +0 -0
  166. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/office.md +0 -0
  167. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/openfoam.md +0 -0
  168. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/tutorials/procedures.md +0 -0
  169. {calkit_python-0.27.3 → calkit_python-0.28.0}/docs/version-control.md +0 -0
  170. {calkit_python-0.27.3 → calkit_python-0.28.0}/mkdocs.yml +0 -0
  171. {calkit_python-0.27.3 → calkit_python-0.28.0}/scripts/install.ps1 +0 -0
  172. {calkit_python-0.27.3 → calkit_python-0.28.0}/scripts/install.sh +0 -0
  173. {calkit_python-0.27.3 → calkit_python-0.28.0}/test/nb-subdir.ipynb +0 -0
  174. {calkit_python-0.27.3 → calkit_python-0.28.0}/test/pipeline.ipynb +0 -0
  175. {calkit_python-0.27.3 → calkit_python-0.28.0}/test/test-log.log +0 -0
@@ -23,6 +23,10 @@ jobs:
23
23
  - name: Install Pixi
24
24
  run: |
25
25
  curl -fsSL https://pixi.sh/install.sh | bash
26
+ - name: Setup Julia
27
+ uses: julia-actions/setup-julia@v2
28
+ with:
29
+ version: "1.11"
26
30
  - name: Run tests
27
31
  run: |
28
32
  source ~/.bashrc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.27.3
3
+ Version: 0.28.0
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -15,7 +15,7 @@ Requires-Dist: arithmeval
15
15
  Requires-Dist: bibtexparser
16
16
  Requires-Dist: checksumdir
17
17
  Requires-Dist: docx2pdf
18
- Requires-Dist: dvc==3.61.0
18
+ Requires-Dist: dvc==3.62.0
19
19
  Requires-Dist: fastapi
20
20
  Requires-Dist: gitpython
21
21
  Requires-Dist: jupyterlab>=4.4.5
@@ -31,7 +31,7 @@ Requires-Dist: python-dotenv>=1
31
31
  Requires-Dist: pywin32; platform_system == 'Windows'
32
32
  Requires-Dist: requests
33
33
  Requires-Dist: tqdm>=4.67.1
34
- Requires-Dist: typer
34
+ Requires-Dist: typer==0.16.0
35
35
  Requires-Dist: uvicorn
36
36
  Provides-Extra: data
37
37
  Requires-Dist: pandas>=2.2.3; extra == 'data'
@@ -89,7 +89,10 @@ while guiding users away from common reproducibility pitfalls.
89
89
  - A CLI to run the project's pipeline to verify it's reproducible,
90
90
  regenerating outputs as needed and
91
91
  ensuring all
92
- computational environments (e.g., [Conda](https://docs.conda.io/en/latest/), [Docker](https://docker.com)) match their specification.
92
+ computational environments
93
+ (e.g., [Conda](https://docs.conda.io/en/latest/),
94
+ [Docker](https://docker.com), uv, Julia)
95
+ match their specification.
93
96
  - A schema to store structured metadata describing the
94
97
  project's important outputs (in its `calkit.yaml` file)
95
98
  and how they are created
@@ -49,7 +49,10 @@ while guiding users away from common reproducibility pitfalls.
49
49
  - A CLI to run the project's pipeline to verify it's reproducible,
50
50
  regenerating outputs as needed and
51
51
  ensuring all
52
- computational environments (e.g., [Conda](https://docs.conda.io/en/latest/), [Docker](https://docker.com)) match their specification.
52
+ computational environments
53
+ (e.g., [Conda](https://docs.conda.io/en/latest/),
54
+ [Docker](https://docker.com), uv, Julia)
55
+ match their specification.
53
56
  - A schema to store structured metadata describing the
54
57
  project's important outputs (in its `calkit.yaml` file)
55
58
  and how they are created
@@ -1,4 +1,4 @@
1
- __version__ = "0.27.3"
1
+ __version__ = "0.28.0"
2
2
 
3
3
  from .core import * # noqa: F403, I001
4
4
  from . import git # noqa: F401
@@ -140,6 +140,32 @@ def check_environment(
140
140
  env=env, env_name=env_name, as_posix=False
141
141
  ), # type: ignore
142
142
  )
143
+ elif env["kind"] == "julia":
144
+ env_path = env.get("path")
145
+ if env_path is None:
146
+ raise_error(
147
+ "Julia environments require a path pointing to Project.toml"
148
+ )
149
+ env_fname = os.path.basename(env_path)
150
+ if not env_fname == "Project.toml":
151
+ raise_error(
152
+ "Julia environments require a path pointing to Project.toml"
153
+ )
154
+ env_dir = os.path.dirname(env_path)
155
+ if not env_dir:
156
+ env_dir = "."
157
+ cmd = [
158
+ "julia",
159
+ f"--project={env_dir}",
160
+ "-e",
161
+ "using Pkg; Pkg.instantiate();",
162
+ ]
163
+ try:
164
+ subprocess.check_call(
165
+ cmd, env=os.environ.copy() | {"JULIA_LOAD_PATH": "@:@stdlib"}
166
+ )
167
+ except subprocess.CalledProcessError:
168
+ raise_error("Failed to check julia environment")
143
169
  else:
144
170
  raise_error(f"Environment kind '{env['kind']}' not supported")
145
171
  return get_env_lock_fpath(env=env, env_name=env_name, as_posix=False)
@@ -38,6 +38,7 @@ from calkit.cli.check import (
38
38
  check_app,
39
39
  check_conda_env,
40
40
  check_docker_env,
41
+ check_environment,
41
42
  check_matlab_env,
42
43
  check_venv,
43
44
  )
@@ -1389,6 +1390,38 @@ def run_in_env(
1389
1390
  subprocess.check_call(cmd, shell=True, cwd=wdir)
1390
1391
  except subprocess.CalledProcessError:
1391
1392
  raise_error(f"Failed to run in {kind}")
1393
+ elif env["kind"] == "julia":
1394
+ if not no_check:
1395
+ check_environment(env_name=env_name, verbose=verbose)
1396
+ env_path = env.get("path")
1397
+ if env_path is None:
1398
+ raise_error(
1399
+ "Julia environments require a path pointing to Project.toml"
1400
+ )
1401
+ env_fname = os.path.basename(env_path)
1402
+ if not env_fname == "Project.toml":
1403
+ raise_error(
1404
+ "Julia environments require a path pointing to Project.toml"
1405
+ )
1406
+ env_dir = os.path.dirname(env_path)
1407
+ if not env_dir:
1408
+ env_dir = "."
1409
+ julia_cmd = [
1410
+ "julia",
1411
+ "--project=" + env_dir,
1412
+ "-e",
1413
+ " ".join(cmd),
1414
+ ]
1415
+ if verbose:
1416
+ typer.echo(f"Running command: {julia_cmd}")
1417
+ try:
1418
+ subprocess.check_call(
1419
+ julia_cmd,
1420
+ cwd=wdir,
1421
+ env=os.environ.copy() | {"JULIA_LOAD_PATH": "@:@stdlib"},
1422
+ )
1423
+ except subprocess.CalledProcessError:
1424
+ raise_error("Failed to run in julia environment")
1392
1425
  elif env["kind"] == "ssh":
1393
1426
  try:
1394
1427
  host = os.path.expandvars(env["host"])
@@ -588,7 +588,8 @@ def new_docker_env(
588
588
  layers: Annotated[
589
589
  list[str],
590
590
  typer.Option(
591
- "--add-layer", help="Add a layer (options: miniforge, foampy)."
591
+ "--add-layer",
592
+ help="Add a layer (options: miniforge, foampy, uv, julia).",
592
593
  ),
593
594
  ] = [],
594
595
  env_vars: Annotated[
@@ -703,11 +704,11 @@ def new_docker_env(
703
704
  wdir=wdir,
704
705
  )
705
706
  if base is not None or path is not None:
706
- env["path"] = path
707
+ env["path"] = path # type: ignore
707
708
  if description is not None:
708
709
  env["description"] = description
709
710
  if layers:
710
- env["layers"] = layers
711
+ env["layers"] = layers # type: ignore
711
712
  if platform:
712
713
  env["platform"] = platform
713
714
  if user:
@@ -715,18 +716,18 @@ def new_docker_env(
715
716
  if gpus:
716
717
  env["gpus"] = gpus
717
718
  if env_vars:
718
- env["env_vars"] = {}
719
+ env["env_vars"] = {} # type: ignore
719
720
  for var in env_vars:
720
721
  if "=" not in var:
721
722
  raise_error(f"Invalid environment variable format: {var}")
722
723
  key, value = var.split("=", 1)
723
724
  env["env_vars"][key] = value
724
725
  if args:
725
- env["args"] = args
726
+ env["args"] = args # type: ignore
726
727
  if deps:
727
- env["deps"] = deps
728
+ env["deps"] = deps # type: ignore
728
729
  if ports:
729
- env["ports"] = ports
730
+ env["ports"] = ports # type: ignore
730
731
  envs[name] = env
731
732
  ck_info["environments"] = envs
732
733
  with open("calkit.yaml", "w") as f:
@@ -799,14 +800,14 @@ def new_dataset(
799
800
  title: Annotated[str, typer.Option("--title")],
800
801
  description: Annotated[str, typer.Option("--description")],
801
802
  stage_name: Annotated[
802
- str,
803
+ str | None,
803
804
  typer.Option(
804
805
  "--stage",
805
806
  help="Name of the pipeline stage that generates this dataset.",
806
807
  ),
807
808
  ] = None,
808
809
  cmd: Annotated[
809
- str,
810
+ str | None,
810
811
  typer.Option(
811
812
  "--cmd", help="Command to add to the stage, if specified."
812
813
  ),
@@ -825,7 +826,7 @@ def new_dataset(
825
826
  ),
826
827
  ] = [],
827
828
  outs_from_stage: Annotated[
828
- str,
829
+ str | None,
829
830
  typer.Option(
830
831
  "--deps-from-stage-outs",
831
832
  help="Stage name from which to add outputs as dependencies.",
@@ -1066,7 +1067,7 @@ def new_publication(
1066
1067
  calkit.ryaml.dump(ck_info, f)
1067
1068
  repo.git.add("calkit.yaml")
1068
1069
  # Copy in template files if applicable
1069
- if template_type == "latex":
1070
+ if template is not None and template_type == "latex":
1070
1071
  if overwrite and os.path.exists(path):
1071
1072
  shutil.rmtree(path)
1072
1073
  calkit.templates.use_template(
@@ -1309,7 +1310,7 @@ def new_venv(
1309
1310
  str, typer.Option("--prefix", help="Prefix for environment location.")
1310
1311
  ] = ".venv",
1311
1312
  description: Annotated[
1312
- str, typer.Option("--description", help="Description.")
1313
+ str | None, typer.Option("--description", help="Description.")
1313
1314
  ] = None,
1314
1315
  overwrite: Annotated[
1315
1316
  bool,
@@ -1390,7 +1391,7 @@ def new_pixi_env(
1390
1391
  list[str], typer.Option("--pip", help="Packages to install with pip.")
1391
1392
  ] = [],
1392
1393
  description: Annotated[
1393
- str, typer.Option("--description", help="Description.")
1394
+ str | None, typer.Option("--description", help="Description.")
1394
1395
  ] = None,
1395
1396
  platforms: Annotated[
1396
1397
  list[str], typer.Option("--platform", "-p", help="Platform.")
@@ -1479,6 +1480,86 @@ def new_pixi_env(
1479
1480
  repo.git.commit(["-m", f"Add pixi env {name}"])
1480
1481
 
1481
1482
 
1483
+ @new_app.command("julia-env")
1484
+ def new_julia_env(
1485
+ packages: Annotated[
1486
+ list[str],
1487
+ typer.Argument(help="Packages to include in the environment."),
1488
+ ],
1489
+ name: Annotated[
1490
+ str, typer.Option("--name", "-n", help="Environment name.")
1491
+ ],
1492
+ path: Annotated[
1493
+ str, typer.Option("--path", help="Path for Project.toml file.")
1494
+ ] = "Project.toml",
1495
+ description: Annotated[
1496
+ str | None, typer.Option("--description", help="Description.")
1497
+ ] = None,
1498
+ overwrite: Annotated[
1499
+ bool,
1500
+ typer.Option(
1501
+ "--overwrite",
1502
+ "-f",
1503
+ help="Overwrite any existing environment with this name.",
1504
+ ),
1505
+ ] = False,
1506
+ no_commit: Annotated[
1507
+ bool, typer.Option("--no-commit", help="Do not commit changes.")
1508
+ ] = False,
1509
+ ):
1510
+ """Create a new Julia environment."""
1511
+ if not os.path.basename(path) == "Project.toml":
1512
+ raise_error(
1513
+ "Julia environment paths must point to a Project.toml file"
1514
+ )
1515
+ try:
1516
+ repo = git.Repo()
1517
+ except git.InvalidGitRepositoryError:
1518
+ raise_error(
1519
+ "Current directory is not a Git repository; run calkit init"
1520
+ )
1521
+ # Add environment to Calkit info
1522
+ ck_info = calkit.load_calkit_info()
1523
+ # If environments is a list instead of a dict, reformulate it
1524
+ envs = ck_info.get("environments", {})
1525
+ if isinstance(envs, list):
1526
+ typer.echo("Converting environments from list to dict")
1527
+ envs = {env.pop("name"): env for env in envs}
1528
+ if name in envs and not overwrite:
1529
+ raise_error(
1530
+ f"Environment with name {name} already exists "
1531
+ "(use -f to overwrite)"
1532
+ )
1533
+ # Create the environment now
1534
+ env_dir = os.path.dirname(path)
1535
+ if env_dir:
1536
+ os.makedirs(env_dir, exist_ok=True)
1537
+ else:
1538
+ env_dir = "."
1539
+ cmd = ["julia", f"--project={env_dir}", "-e"]
1540
+ install_cmd = "using Pkg;"
1541
+ for package in packages:
1542
+ install_cmd += f' Pkg.add("{package}");'
1543
+ cmd.append(install_cmd)
1544
+ try:
1545
+ subprocess.run(cmd, check=True)
1546
+ except subprocess.CalledProcessError:
1547
+ raise_error("Failed to create new Julia environment")
1548
+ typer.echo("Adding environment to calkit.yaml")
1549
+ env = dict(kind="julia", path=path, name=name)
1550
+ if description is not None:
1551
+ env["description"] = description
1552
+ envs[name] = env
1553
+ ck_info["environments"] = envs
1554
+ with open("calkit.yaml", "w") as f:
1555
+ ryaml.dump(ck_info, f)
1556
+ repo.git.add(path)
1557
+ repo.git.add(os.path.join(env_dir, "Manifest.toml"))
1558
+ repo.git.add("calkit.yaml")
1559
+ if not no_commit and repo.git.diff("--staged"):
1560
+ repo.git.commit(["-m", f"Add Julia env {name}"])
1561
+
1562
+
1482
1563
  class Status(str, Enum):
1483
1564
  in_progress = "in-progress"
1484
1565
  on_hold = "on-hold"
@@ -1786,6 +1867,66 @@ def new_python_script_stage(
1786
1867
  )
1787
1868
 
1788
1869
 
1870
+ @new_app.command(name="julia-script-stage")
1871
+ def new_julia_script_stage(
1872
+ name: StageArgs.name,
1873
+ environment: StageArgs.environment,
1874
+ script_path: StageArgs.script_path,
1875
+ inputs: StageArgs.inputs = [],
1876
+ outputs: StageArgs.outputs = [],
1877
+ outs_git: StageArgs.outs_git = [],
1878
+ outs_git_no_delete: StageArgs.outs_git_no_delete = [],
1879
+ outs_no_delete: StageArgs.outs_no_delete = [],
1880
+ outs_no_store: StageArgs.outs_no_store = [],
1881
+ outs_no_store_no_delete: StageArgs.outs_no_store_no_delete = [],
1882
+ iter_arg: Annotated[
1883
+ tuple[str, str] | None,
1884
+ typer.Option(
1885
+ "--iter",
1886
+ help=(
1887
+ "Iterate over an argument with a comma-separated list, e.g., "
1888
+ "--iter-arg var_name val1,val2,val3."
1889
+ ),
1890
+ ),
1891
+ ] = None,
1892
+ overwrite: StageArgs.overwrite = False,
1893
+ no_check: StageArgs.no_check = False,
1894
+ no_commit: StageArgs.no_commit = False,
1895
+ ) -> None:
1896
+ """Add a stage to the pipeline that runs a Julia script."""
1897
+ ck_outs = _to_ck_outs(
1898
+ outputs=outputs,
1899
+ outs_git=outs_git,
1900
+ outs_git_no_delete=outs_git_no_delete,
1901
+ outs_no_delete=outs_no_delete,
1902
+ outs_no_store=outs_no_store,
1903
+ outs_no_store_no_delete=outs_no_store_no_delete,
1904
+ )
1905
+ try:
1906
+ if iter_arg is not None:
1907
+ arg_name, vals = iter_arg
1908
+ i = [StageIteration(arg_name=arg_name, values=vals.split(","))] # type: ignore
1909
+ else:
1910
+ i = None
1911
+ stage = calkit.models.pipeline.JuliaScriptStage(
1912
+ kind="julia-script",
1913
+ environment=environment,
1914
+ inputs=inputs, # type: ignore
1915
+ outputs=ck_outs,
1916
+ script_path=script_path,
1917
+ iterate_over=i,
1918
+ )
1919
+ except Exception as e:
1920
+ raise_error(f"Invalid stage specification: {e}")
1921
+ _save_stage(
1922
+ stage=stage,
1923
+ name=name,
1924
+ overwrite=overwrite,
1925
+ no_check=no_check,
1926
+ no_commit=no_commit,
1927
+ )
1928
+
1929
+
1789
1930
  @new_app.command(name="matlab-script-stage")
1790
1931
  def new_matlab_script_stage(
1791
1932
  name: StageArgs.name,
@@ -309,6 +309,8 @@ def check_system_deps(
309
309
  if "docker" not in deps:
310
310
  deps.append("docker")
311
311
  deps.append({"MATLAB_LICENSE_SERVER": {"kind": "env-var"}})
312
+ elif kind == "julia" and "julia" not in deps:
313
+ deps.append("julia")
312
314
  for dep in deps:
313
315
  if isinstance(dep, dict):
314
316
  keys = list(dep.keys())
@@ -488,7 +490,7 @@ def detect_project_name(
488
490
  if name is not None and not prepend_owner:
489
491
  return name
490
492
  owner = ck_info.get("owner")
491
- if name is None and owner is None:
493
+ if name is None or owner is None:
492
494
  try:
493
495
  url = Repo(path=wdir).remote().url
494
496
  except ValueError:
@@ -560,6 +562,7 @@ def get_system_info() -> dict:
560
562
  "uv",
561
563
  "pixi",
562
564
  "Rscript",
565
+ "julia",
563
566
  ]:
564
567
  system_info[f"{dep}_version"] = get_dep_version(dep)
565
568
  system_info_str = json.dumps(system_info, sort_keys=True).encode()
@@ -47,7 +47,17 @@ RUN pip install --no-cache-dir numpy pandas matplotlib h5py \
47
47
  """.strip()
48
48
 
49
49
  UV_LAYER_TEXT = """
50
- COPY --from=ghcr.io/astral-sh/uv:0.6.14 /uv /uvx /bin/
50
+ COPY --from=ghcr.io/astral-sh/uv:0.8.5 /uv /uvx /bin/
51
+ """
52
+
53
+ JULIA_LAYER_TEXT = """
54
+ # Install Julia
55
+ # Ensure base image is a bullseye distribution
56
+ COPY --from=julia:1.11.6-bullseye /usr/local/julia /usr/local/julia
57
+ ENV JULIA_PATH=/usr/local/julia \
58
+ PATH=$PATH:/usr/local/julia/bin \
59
+ JULIA_GPG=3673DF529D9049477F76B37566E3C7DC03D6E495 \
60
+ JULIA_VERSION=1.11.6
51
61
  """
52
62
 
53
63
  LAYERS = {
@@ -55,4 +65,5 @@ LAYERS = {
55
65
  "miniforge": MINIFORGE_LAYER_TXT,
56
66
  "foampy": FOAMPY_LAYER_TEXT,
57
67
  "uv": UV_LAYER_TEXT,
68
+ "julia": JULIA_LAYER_TEXT,
58
69
  }
@@ -28,6 +28,20 @@ def get_env_lock_fpath(
28
28
  lock_fpath += ".yml"
29
29
  elif env_kind == "matlab":
30
30
  lock_fpath += ".json"
31
+ elif env_kind == "julia":
32
+ env_path = env.get("path")
33
+ if env_path is None:
34
+ raise ValueError(
35
+ "Julia environments require a path pointing to Project.toml"
36
+ )
37
+ env_fname = os.path.basename(env_path)
38
+ if not env_fname == "Project.toml":
39
+ raise ValueError(
40
+ "Julia environments require a path pointing to Project.toml"
41
+ )
42
+ # Simply replace Project.toml with Manifest.toml
43
+ env_dir = os.path.dirname(env_path)
44
+ lock_fpath = os.path.join(env_dir, "Manifest.toml")
31
45
  else:
32
46
  return
33
47
  if as_posix:
@@ -109,6 +109,8 @@ class Stage(BaseModel):
109
109
  "shell-script",
110
110
  "jupyter-notebook",
111
111
  "r-script",
112
+ "julia-script",
113
+ "word-to-pdf",
112
114
  ]
113
115
  environment: str
114
116
  wdir: str | None = None
@@ -309,6 +311,20 @@ class RScriptStage(Stage):
309
311
  return cmd
310
312
 
311
313
 
314
+ class JuliaScriptStage(Stage):
315
+ kind: Literal["julia-script"] = "julia-script"
316
+ script_path: str
317
+
318
+ @property
319
+ def dvc_cmd(self) -> str:
320
+ cmd = f'{self.xenv_cmd} -- "include(\\"{self.script_path}\\")"'
321
+ return cmd
322
+
323
+ @property
324
+ def dvc_deps(self) -> list[str]:
325
+ return [self.script_path] + super().dvc_deps
326
+
327
+
312
328
  class JupyterNotebookStage(Stage):
313
329
  """A stage that runs a Jupyter notebook.
314
330
 
@@ -466,6 +482,7 @@ class Pipeline(BaseModel):
466
482
  | RScriptStage
467
483
  | WordToPdfStage
468
484
  | JupyterNotebookStage
485
+ | JuliaScriptStage
469
486
  ),
470
487
  Discriminator("kind"),
471
488
  ],
@@ -234,6 +234,41 @@ def test_run_in_venv(tmp_dir):
234
234
  assert out == "2.0.0"
235
235
 
236
236
 
237
+ def test_run_in_julia_env(tmp_dir):
238
+ subprocess.check_call("calkit init", shell=True)
239
+ subprocess.check_call(
240
+ [
241
+ "calkit",
242
+ "new",
243
+ "julia-env",
244
+ "-n",
245
+ "my-julia",
246
+ "--no-commit",
247
+ "Revise",
248
+ "PkgVersion",
249
+ ]
250
+ )
251
+ out = (
252
+ subprocess.check_output(
253
+ [
254
+ "calkit",
255
+ "xenv",
256
+ "-n",
257
+ "my-julia",
258
+ "--",
259
+ (
260
+ "using Revise; using PkgVersion; "
261
+ "println(PkgVersion.Version(Revise))"
262
+ ),
263
+ ]
264
+ )
265
+ .decode()
266
+ .strip()
267
+ )
268
+ # TODO: Allow specifying version
269
+ assert out
270
+
271
+
237
272
  def test_to_shell_cmd():
238
273
  cmd = ["python", "-c", "import math; print('hello world')"]
239
274
  subprocess.check_call(cmd)
@@ -420,6 +455,35 @@ def test_run(tmp_dir):
420
455
  repo = git.Repo()
421
456
  repo.git.checkout("HEAD^")
422
457
  out = subprocess.check_output(["calkit", "run"], text=True)
458
+ # Test that we can run a Julia script
459
+ with open("julia_script.jl", "w") as f:
460
+ f.write('println("Hello from julia_script.jl")')
461
+ subprocess.check_call(
462
+ [
463
+ "calkit",
464
+ "new",
465
+ "julia-env",
466
+ "--name",
467
+ "j1",
468
+ "--path",
469
+ "something/Project.toml",
470
+ "PkgVersion",
471
+ ]
472
+ )
473
+ subprocess.check_call(
474
+ [
475
+ "calkit",
476
+ "new",
477
+ "julia-script-stage",
478
+ "--name",
479
+ "stage-2",
480
+ "--script-path",
481
+ "julia_script.jl",
482
+ "-e",
483
+ "j1",
484
+ ]
485
+ )
486
+ subprocess.check_call(["calkit", "run"])
423
487
 
424
488
 
425
489
  def test_stage_run_info_from_log_content():
@@ -633,3 +633,26 @@ def test_new_matlab_script_stage(tmp_dir):
633
633
  "results/output.txt",
634
634
  "results/output2.txt",
635
635
  ]
636
+
637
+
638
+ def test_new_julia_env(tmp_dir):
639
+ subprocess.check_call(["calkit", "init"])
640
+ subprocess.check_call(
641
+ ["calkit", "new", "julia-env", "--name", "j1", "WaterLily"]
642
+ )
643
+ assert os.path.isfile("Project.toml")
644
+ assert os.path.isfile("Manifest.toml")
645
+ subprocess.check_call(
646
+ [
647
+ "calkit",
648
+ "new",
649
+ "julia-env",
650
+ "--name",
651
+ "j2",
652
+ "Revise",
653
+ "--path",
654
+ "envs/my-env/Project.toml",
655
+ ]
656
+ )
657
+ assert os.path.isfile("envs/my-env/Project.toml")
658
+ assert os.path.isfile("envs/my-env/Manifest.toml")
@@ -269,6 +269,28 @@ Again this highlights Calkit's declarative design philosophy.
269
269
  Declare the environment and what command should be executed inside,
270
270
  and Calkit will handle the rest.
271
271
 
272
+ ### Julia
273
+
274
+ [Julia](https://julialang.org/) environments have paths that point to a
275
+ `Project.toml` file.
276
+ Creating a new Julia environment is similar to creating a Python environment:
277
+
278
+ ```sh
279
+ calkit new julia-env \
280
+ --name my-julia-env \
281
+ --path ./envs/my-julia-env/Project.toml \
282
+ WaterLily \
283
+ Makie
284
+ ```
285
+
286
+ Unlike Python environments, for which `xenv` runs a shell command,
287
+ `xenv` runs Julia commands in a Julia environment.
288
+ For example:
289
+
290
+ ```sh
291
+ calkit xenv -n my-julia-env -- "println(\"hello world\");"
292
+ ```
293
+
272
294
  ### SSH
273
295
 
274
296
  It's possible to define a remote environment that uses `ssh` to connect
@@ -32,7 +32,10 @@ while guiding users away from common reproducibility pitfalls.
32
32
  - A CLI to run the project's pipeline to verify it's reproducible,
33
33
  regenerating outputs as needed and
34
34
  ensuring all
35
- computational environments (e.g., [Conda](https://docs.conda.io/en/latest/), [Docker](https://docker.com)) match their specification.
35
+ computational environments
36
+ (e.g., [Conda](https://docs.conda.io/en/latest/),
37
+ [Docker](https://docker.com), uv, Julia)
38
+ match their specification.
36
39
  - A schema to store structured metadata describing the
37
40
  project's important outputs (in its `calkit.yaml` file)
38
41
  and how they are created
@@ -82,6 +82,10 @@ For more details, see `calkit.models.pipeline`.
82
82
  - `script_path`
83
83
  - `args` (list, optional)
84
84
 
85
+ ### `julia-script`
86
+
87
+ - `script_path`
88
+
85
89
  ## Iteration
86
90
 
87
91
  ### Over a list of values