calkit-python 0.34.1__tar.gz → 0.34.2__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 (273) hide show
  1. {calkit_python-0.34.1 → calkit_python-0.34.2}/PKG-INFO +1 -1
  2. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/main.py +17 -6
  3. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/notebooks.py +26 -10
  4. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/detect.py +3 -1
  5. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/environments.py +343 -20
  6. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/jupyterlab/routes.py +9 -3
  7. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/models/pipeline.py +2 -2
  8. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_main.py +51 -0
  9. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/models/test_pipeline.py +6 -4
  10. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_detect.py +4 -3
  11. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_environments.py +136 -0
  12. {calkit_python-0.34.1 → calkit_python-0.34.2}/.gitignore +0 -0
  13. {calkit_python-0.34.1 → calkit_python-0.34.2}/.pre-commit-config.yaml +0 -0
  14. {calkit_python-0.34.1 → calkit_python-0.34.2}/.prettierignore +0 -0
  15. {calkit_python-0.34.1 → calkit_python-0.34.2}/.python-version +0 -0
  16. {calkit_python-0.34.1 → calkit_python-0.34.2}/.yarnrc.yml +0 -0
  17. {calkit_python-0.34.1 → calkit_python-0.34.2}/CITATION.cff +0 -0
  18. {calkit_python-0.34.1 → calkit_python-0.34.2}/CODE_OF_CONDUCT.md +0 -0
  19. {calkit_python-0.34.1 → calkit_python-0.34.2}/CONTRIBUTING.md +0 -0
  20. {calkit_python-0.34.1 → calkit_python-0.34.2}/LICENSE +0 -0
  21. {calkit_python-0.34.1 → calkit_python-0.34.2}/Makefile +0 -0
  22. {calkit_python-0.34.1 → calkit_python-0.34.2}/README.md +0 -0
  23. {calkit_python-0.34.1 → calkit_python-0.34.2}/babel.config.js +0 -0
  24. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/__init__.py +0 -0
  25. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/__main__.py +0 -0
  26. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/calc.py +0 -0
  27. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/check.py +0 -0
  28. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/__init__.py +0 -0
  29. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/check.py +0 -0
  30. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/cloud.py +0 -0
  31. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/config.py +0 -0
  32. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/core.py +0 -0
  33. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/describe.py +0 -0
  34. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/import_.py +0 -0
  35. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/latex.py +0 -0
  36. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/list.py +0 -0
  37. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/new.py +0 -0
  38. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/office.py +0 -0
  39. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/overleaf.py +0 -0
  40. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/slurm.py +0 -0
  41. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cli/update.py +0 -0
  42. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/cloud.py +0 -0
  43. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/conda.py +0 -0
  44. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/config.py +0 -0
  45. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/core.py +0 -0
  46. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/datasets.py +0 -0
  47. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/docker.py +0 -0
  48. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/dvc.py +0 -0
  49. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/git.py +0 -0
  50. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/github.py +0 -0
  51. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/gui.py +0 -0
  52. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/invenio.py +0 -0
  53. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/jupyter.py +0 -0
  54. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/jupyterlab/__init__.py +0 -0
  55. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/package.json +0 -0
  56. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  57. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  58. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  59. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  60. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  61. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/909.99b13e1e3c2a6e1ceb30.js +0 -0
  62. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  63. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  64. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  65. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/remoteEntry.63846b8de2465d49bd89.js +0 -0
  66. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/style.js +0 -0
  67. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/labextension/static/third-party-licenses.json +0 -0
  68. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/licenses.py +0 -0
  69. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/magics.py +0 -0
  70. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/matlab.py +0 -0
  71. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/models/__init__.py +0 -0
  72. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/models/core.py +0 -0
  73. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/models/io.py +0 -0
  74. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/models/iteration.py +0 -0
  75. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/notebooks.py +0 -0
  76. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/office.py +0 -0
  77. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/ops.py +0 -0
  78. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/overleaf.py +0 -0
  79. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/pipeline.py +0 -0
  80. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/releases.py +0 -0
  81. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/server.py +0 -0
  82. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/__init__.py +0 -0
  83. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/core.py +0 -0
  84. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/__init__.py +0 -0
  85. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/article/paper.tex +0 -0
  86. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/core.py +0 -0
  87. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/jfm/jfm.bst +0 -0
  88. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/jfm/jfm.cls +0 -0
  89. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  90. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/jfm/paper.tex +0 -0
  91. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/templates/latex/jfm/upmath.sty +0 -0
  92. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/__init__.py +0 -0
  93. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/__init__.py +0 -0
  94. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_check.py +0 -0
  95. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_config.py +0 -0
  96. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_import.py +0 -0
  97. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_latex.py +0 -0
  98. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_list.py +0 -0
  99. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_new.py +0 -0
  100. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_notebooks.py +0 -0
  101. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/cli/test_overleaf.py +0 -0
  102. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/jupyterlab/__init__.py +0 -0
  103. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/jupyterlab/test_routes.py +0 -0
  104. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/models/__init__.py +0 -0
  105. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/models/test_iteration.py +0 -0
  106. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_calc.py +0 -0
  107. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_check.py +0 -0
  108. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_conda.py +0 -0
  109. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_core.py +0 -0
  110. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_dvc.py +0 -0
  111. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_git.py +0 -0
  112. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_invenio.py +0 -0
  113. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_jupyter.py +0 -0
  114. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_magics.py +0 -0
  115. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_matlab.py +0 -0
  116. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_notebooks.py +0 -0
  117. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_pipeline.py +0 -0
  118. {calkit_python-0.34.1 → calkit_python-0.34.2}/calkit/tests/test_templates.py +0 -0
  119. {calkit_python-0.34.1 → calkit_python-0.34.2}/conftest.py +0 -0
  120. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/CNAME +0 -0
  121. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/apps.md +0 -0
  122. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/calculations.md +0 -0
  123. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/calkit-yaml.md +0 -0
  124. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/cli-reference.md +0 -0
  125. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/cloud-integration.md +0 -0
  126. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/datasets.md +0 -0
  127. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/dependencies.md +0 -0
  128. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/environments.md +0 -0
  129. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/examples.md +0 -0
  130. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/governance.md +0 -0
  131. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/help.md +0 -0
  132. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/c-to-the-k-white.svg +0 -0
  133. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/calkit-fragmentation-compendium.png +0 -0
  134. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/calkit-no-bg.png +0 -0
  135. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/connect-zenodo.png +0 -0
  136. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/jupyterlab/all-green.png +0 -0
  137. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  138. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/jupyterlab/new-env.png +0 -0
  139. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/jupyterlab/new-notebook.png +0 -0
  140. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  141. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/jupyterlab-params.png +0 -0
  142. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/plos-osi-code-2024-03.png +0 -0
  143. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/img/vscode-nb-params.png +0 -0
  144. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/index.md +0 -0
  145. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/installation.md +0 -0
  146. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/jupyterlab.md +0 -0
  147. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/local-server.md +0 -0
  148. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/notebooks.md +0 -0
  149. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/overleaf.md +0 -0
  150. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/pipeline/index.md +0 -0
  151. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/pipeline/manual-steps.md +0 -0
  152. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/pipeline/running-and-logging.md +0 -0
  153. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/pipeline/slurm.md +0 -0
  154. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/quickstart.md +0 -0
  155. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/references.md +0 -0
  156. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/releases.md +0 -0
  157. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/reproducibility.md +0 -0
  158. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  159. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/conda-envs.md +0 -0
  160. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/existing-project.md +0 -0
  161. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/first-project.md +0 -0
  162. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/github-actions.md +0 -0
  163. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  164. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  165. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  166. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  167. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  168. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  169. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  170. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  171. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  172. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  173. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  174. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  175. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  176. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  177. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  178. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/create-project.png +0 -0
  179. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  180. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/excel-chart.png +0 -0
  181. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/excel-data.png +0 -0
  182. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  183. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/needs-clone.png +0 -0
  184. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/new-stage.png +0 -0
  185. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  186. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  187. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/status-more-rows.png +0 -0
  188. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  189. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/untracked-data.png +0 -0
  190. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/updated-publication.png +0 -0
  191. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  192. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/office/workflow-page.png +0 -0
  193. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/clone.png +0 -0
  194. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/create-project.png +0 -0
  195. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  196. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  197. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  198. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/new-token.png +0 -0
  199. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/reclone.png +0 -0
  200. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  201. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/quick-actions.png +0 -0
  202. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/img/run-proc.png +0 -0
  203. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/index.md +0 -0
  204. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/jupyterlab.md +0 -0
  205. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/latex-codespaces.md +0 -0
  206. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/matlab.md +0 -0
  207. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/notebook-pipeline.md +0 -0
  208. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/office.md +0 -0
  209. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/openfoam.md +0 -0
  210. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/tutorials/procedures.md +0 -0
  211. {calkit_python-0.34.1 → calkit_python-0.34.2}/docs/version-control.md +0 -0
  212. {calkit_python-0.34.1 → calkit_python-0.34.2}/install.json +0 -0
  213. {calkit_python-0.34.1 → calkit_python-0.34.2}/jest.config.js +0 -0
  214. {calkit_python-0.34.1 → calkit_python-0.34.2}/jupyter-config/server-config/calkit.json +0 -0
  215. {calkit_python-0.34.1 → calkit_python-0.34.2}/mkdocs.yml +0 -0
  216. {calkit_python-0.34.1 → calkit_python-0.34.2}/package.json +0 -0
  217. {calkit_python-0.34.1 → calkit_python-0.34.2}/pyproject.toml +0 -0
  218. {calkit_python-0.34.1 → calkit_python-0.34.2}/schema/plugin.json +0 -0
  219. {calkit_python-0.34.1 → calkit_python-0.34.2}/scripts/install.ps1 +0 -0
  220. {calkit_python-0.34.1 → calkit_python-0.34.2}/scripts/install.sh +0 -0
  221. {calkit_python-0.34.1 → calkit_python-0.34.2}/scripts/make-calk9.sh +0 -0
  222. {calkit_python-0.34.1 → calkit_python-0.34.2}/scripts/sync-docs.py +0 -0
  223. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/__tests__/useQueries.spec.ts +0 -0
  224. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/calkit-config.ts +0 -0
  225. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/cell-output-marker.ts +0 -0
  226. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/commit-dialog.tsx +0 -0
  227. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/environment-editor.tsx +0 -0
  228. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/notebook-registration.tsx +0 -0
  229. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/notebook-toolbar.tsx +0 -0
  230. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/pipeline-status-bar.tsx +0 -0
  231. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/project-info-editor.tsx +0 -0
  232. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/sidebar-settings.tsx +0 -0
  233. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/sidebar.tsx +0 -0
  234. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/components/stage-editor.tsx +0 -0
  235. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/feature-flags.ts +0 -0
  236. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/file-browser-menu.ts +0 -0
  237. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  238. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/hooks/useQueries.ts +0 -0
  239. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/icons.ts +0 -0
  240. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/index.ts +0 -0
  241. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/io-tracker.ts +0 -0
  242. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/pipeline-state.ts +0 -0
  243. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/queryClient.ts +0 -0
  244. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/request.ts +0 -0
  245. {calkit_python-0.34.1 → calkit_python-0.34.2}/src/shims-mainmenu.d.ts +0 -0
  246. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/base.css +0 -0
  247. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/cell-output-marker.css +0 -0
  248. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/environment-editor.css +0 -0
  249. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/environment-selector.css +0 -0
  250. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/img/calkit-no-bg.png +0 -0
  251. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/index.css +0 -0
  252. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/index.js +0 -0
  253. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/notebook-toolbar.css +0 -0
  254. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/pipeline-status-bar.css +0 -0
  255. {calkit_python-0.34.1 → calkit_python-0.34.2}/style/sidebar.css +0 -0
  256. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  257. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/nb-julia.ipynb +0 -0
  258. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/nb-params.ipynb +0 -0
  259. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/nb-subdir.ipynb +0 -0
  260. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/pipeline.ipynb +0 -0
  261. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/script.py +0 -0
  262. {calkit_python-0.34.1 → calkit_python-0.34.2}/test/test-log.log +0 -0
  263. {calkit_python-0.34.1 → calkit_python-0.34.2}/tsconfig.json +0 -0
  264. {calkit_python-0.34.1 → calkit_python-0.34.2}/tsconfig.test.json +0 -0
  265. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/.gitignore +0 -0
  266. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/README.md +0 -0
  267. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/jupyter_server_test_config.py +0 -0
  268. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/package.json +0 -0
  269. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/playwright.config.js +0 -0
  270. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/tests/calkit.spec.ts +0 -0
  271. {calkit_python-0.34.1 → calkit_python-0.34.2}/ui-tests/yarn.lock +0 -0
  272. {calkit_python-0.34.1 → calkit_python-0.34.2}/uv.lock +0 -0
  273. {calkit_python-0.34.1 → calkit_python-0.34.2}/yarn.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.34.1
3
+ Version: 0.34.2
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -1835,9 +1835,9 @@ def execute_and_record(
1835
1835
  list[str],
1836
1836
  typer.Argument(
1837
1837
  help="Command to execute and record. "
1838
- "If the first argument is a script or notebook, "
1839
- "it will be treated as a stage with that script/notebook as "
1840
- "the target."
1838
+ "If the first argument is a script, notebook or LaTeX file, "
1839
+ "it will be treated as a stage with that file as "
1840
+ "the target. Any command, including arguments, is supported."
1841
1841
  ),
1842
1842
  ],
1843
1843
  environment: Annotated[
@@ -1895,6 +1895,17 @@ def execute_and_record(
1895
1895
  ),
1896
1896
  ),
1897
1897
  ] = False,
1898
+ force: Annotated[
1899
+ bool,
1900
+ typer.Option(
1901
+ "--force",
1902
+ "-f",
1903
+ help="Force running stage even if it's up-to-date.",
1904
+ ),
1905
+ ] = False,
1906
+ verbose: Annotated[
1907
+ bool, typer.Option("--verbose", "-v", help="Print verbose output.")
1908
+ ] = False,
1898
1909
  ):
1899
1910
  """Execute a command and if successful, record in the pipeline."""
1900
1911
  import io
@@ -2152,8 +2163,8 @@ def execute_and_record(
2152
2163
  # If we created an environment from dependencies, write the spec file
2153
2164
  if env_result.created_from_dependencies and not dry_run:
2154
2165
  typer.echo(
2155
- "No existing environment detected. "
2156
- "Attempting to create one based on detected dependencies..."
2166
+ "No existing environment detected; "
2167
+ "Attempting to create one based on detected dependencies"
2157
2168
  )
2158
2169
  if env_result.dependencies:
2159
2170
  typer.echo(
@@ -2302,7 +2313,7 @@ def execute_and_record(
2302
2313
  f"Adding stage to pipeline and attempting to execute:"
2303
2314
  f"\n{indented_yaml}"
2304
2315
  )
2305
- run(targets=[stage_name])
2316
+ run(targets=[stage_name], force=force, verbose=verbose)
2306
2317
  except Exception as e:
2307
2318
  # If the stage failed, write the old ck_info back to calkit.yaml to
2308
2319
  # remove the stage that we added
@@ -15,7 +15,7 @@ from typing_extensions import Annotated
15
15
 
16
16
  import calkit
17
17
  import calkit.notebooks
18
- from calkit.cli.core import raise_error
18
+ from calkit.cli.core import raise_error, warn
19
19
 
20
20
  notebooks_app = typer.Typer(no_args_is_help=True)
21
21
 
@@ -115,7 +115,7 @@ def check_env_kernel(
115
115
  verbose: Annotated[
116
116
  bool, typer.Option("--verbose", "-v", help="Print verbose output.")
117
117
  ] = False,
118
- ):
118
+ ) -> tuple[str, str]:
119
119
  """Check that an environment has a registered Jupyter kernel."""
120
120
  from calkit.cli.check import check_environment
121
121
  from calkit.cli.main import run_in_env
@@ -159,7 +159,7 @@ def check_env_kernel(
159
159
  verbose=verbose,
160
160
  relaxed_check=True,
161
161
  )
162
- return kernel_name
162
+ return kernel_name, display_name
163
163
  elif language == "r":
164
164
  cmd = [
165
165
  "Rscript",
@@ -176,7 +176,7 @@ def check_env_kernel(
176
176
  verbose=verbose,
177
177
  relaxed_check=True,
178
178
  )
179
- return kernel_name
179
+ return kernel_name, display_name
180
180
  elif language == "julia":
181
181
  if not no_check:
182
182
  check_environment(env_name=env_name, verbose=verbose)
@@ -214,9 +214,10 @@ def check_env_kernel(
214
214
  kernel_path = res.stdout.strip()
215
215
  typer.echo(f"Registered IJulia kernel at: {kernel_path}")
216
216
  kernel_name = os.path.basename(kernel_path)
217
- return kernel_name
217
+ return kernel_name, display_name
218
218
  else:
219
219
  raise_error(f"{language} not supported")
220
+ return "", "" # For typing analysis since raise_error exits
220
221
 
221
222
 
222
223
  @notebooks_app.command("exec", help="Alias for 'execute'.")
@@ -345,14 +346,10 @@ def execute_notebook(
345
346
  env = envs[env_name]
346
347
  language = language_from_env(env) or "python"
347
348
  typer.echo(f"Using {language} as notebook language")
348
- if language.lower() not in ["python", "matlab", "julia", "r"]:
349
- raise ValueError(
350
- "Language must be one of 'python', 'matlab', 'julia', or 'r'"
351
- )
352
349
  # First, ensure the specified environment has a kernel we can use
353
350
  # We need to check the environment type and create the kernel if needed
354
351
  if language.lower() in ["python", "julia", "r"]:
355
- kernel_name = check_env_kernel(
352
+ kernel_name, display_name = check_env_kernel(
356
353
  env_name=env_name,
357
354
  no_check=no_check,
358
355
  verbose=verbose,
@@ -360,6 +357,25 @@ def execute_notebook(
360
357
  )
361
358
  elif language.lower() == "matlab":
362
359
  kernel_name = "jupyter_matlab_kernel"
360
+ display_name = "MATLAB"
361
+ else:
362
+ raise ValueError(
363
+ "Language must be one of 'python', 'matlab', 'julia', or 'r'"
364
+ )
365
+ # Try to set kernelspec metadata so execution uses the expected kernel
366
+ try:
367
+ with open(path, "r") as f:
368
+ nb_json = json.load(f)
369
+ metadata = nb_json.setdefault("metadata", {})
370
+ kernelspec = metadata.setdefault("kernelspec", {})
371
+ kernelspec["name"] = kernel_name
372
+ kernelspec["display_name"] = display_name
373
+ kernelspec.setdefault("language", language.lower())
374
+ with open(path, "w") as f:
375
+ json.dump(nb_json, f, indent=1)
376
+ except Exception as e:
377
+ if verbose:
378
+ warn(f"Warning: failed to set kernelspec metadata: {e}")
363
379
  # We can't handle parameters unless language is Python, Julia, or R
364
380
  if language.lower() not in ["python", "julia", "r"]:
365
381
  if params or params_json is not None or params_base64 is not None:
@@ -1214,7 +1214,6 @@ def generate_stage_name(cmd: list[str]) -> str:
1214
1214
  if remaining_args:
1215
1215
  args_part = "-".join(remaining_args)
1216
1216
  stage_name += "-" + args_part
1217
-
1218
1217
  # Convert to kebab-case
1219
1218
  stage_name = stage_name.replace("_", "-").lower()
1220
1219
  # Replace dots with dashes (except file extensions which are
@@ -1229,6 +1228,9 @@ def generate_stage_name(cmd: list[str]) -> str:
1229
1228
  stage_name = re.sub(r"-+", "-", stage_name)
1230
1229
  # Remove leading/trailing dashes
1231
1230
  stage_name = stage_name.strip("-")
1231
+ # If any args include the .ipynb extension, add a -notebook suffix
1232
+ if any(arg.endswith(".ipynb") for arg in cmd):
1233
+ stage_name += "-notebook"
1232
1234
  return stage_name
1233
1235
 
1234
1236
 
@@ -10,6 +10,8 @@ import tempfile
10
10
  from pathlib import Path
11
11
  from typing import cast
12
12
 
13
+ import toml
14
+ import yaml
13
15
  from pydantic import BaseModel
14
16
  from sqlitedict import SqliteDict
15
17
 
@@ -1055,6 +1057,214 @@ Title: Auto-generated R environment
1055
1057
  return content
1056
1058
 
1057
1059
 
1060
+ def extract_dependencies_from_spec_file(
1061
+ spec_path: str, language: str | None = None
1062
+ ) -> list[str]:
1063
+ """Extract dependencies from an environment spec file.
1064
+
1065
+ Parameters
1066
+ ----------
1067
+ spec_path : str
1068
+ Path to the spec file (requirements.txt, Project.toml, etc.).
1069
+ language : str | None
1070
+ Language hint to help identify the format. If None, will be inferred
1071
+ from the file path.
1072
+
1073
+ Returns
1074
+ -------
1075
+ list[str]
1076
+ List of package/dependency names.
1077
+ """
1078
+ if not os.path.exists(spec_path):
1079
+ return []
1080
+ try:
1081
+ with open(spec_path, "r", encoding="utf-8") as f:
1082
+ content = f.read()
1083
+ except (IOError, UnicodeDecodeError):
1084
+ return []
1085
+ # Determine format from filename if not provided
1086
+ if language is None:
1087
+ if spec_path.endswith("requirements.txt"):
1088
+ language = "python-requirements"
1089
+ elif spec_path.endswith("pyproject.toml"):
1090
+ language = "python-pyproject"
1091
+ elif spec_path.endswith("Project.toml"):
1092
+ language = "julia"
1093
+ elif spec_path.endswith("DESCRIPTION"):
1094
+ language = "r"
1095
+ elif spec_path.endswith("environment.yml"):
1096
+ language = "conda"
1097
+ dependencies: list[str] = []
1098
+ if language in ["python-requirements"]:
1099
+ # Parse requirements.txt
1100
+ for line in content.split("\n"):
1101
+ line = line.strip()
1102
+ if line and not line.startswith("#"):
1103
+ # Extract package name (before any version specifiers)
1104
+ pkg = line.split("[")[0].split("==")[0].split(">=")[0]
1105
+ pkg = pkg.split("<=")[0].split(">")[0].split("<")[0]
1106
+ pkg = pkg.split("~=")[0].strip()
1107
+ if pkg:
1108
+ dependencies.append(pkg)
1109
+ elif language == "python-pyproject":
1110
+ # Parse pyproject.toml to extract dependencies
1111
+ try:
1112
+ data = toml.loads(content)
1113
+ project_deps = data.get("project", {}).get("dependencies", [])
1114
+ for dep in project_deps:
1115
+ # Extract package name (before any version specifiers)
1116
+ pkg = dep.split("[")[0].split("==")[0].split(">=")[0]
1117
+ pkg = pkg.split("<=")[0].split(">")[0].split("<")[0]
1118
+ pkg = pkg.split("~=")[0].strip()
1119
+ if pkg:
1120
+ dependencies.append(pkg)
1121
+ except Exception:
1122
+ pass
1123
+ elif language == "julia":
1124
+ # Parse Julia Project.toml for [deps] section
1125
+ try:
1126
+ data = toml.loads(content)
1127
+ # Package names are the keys in the [deps] section
1128
+ deps_section = data.get("deps", {})
1129
+ if isinstance(deps_section, dict):
1130
+ dependencies = list(deps_section.keys())
1131
+ except Exception:
1132
+ pass
1133
+ elif language == "r":
1134
+ # Parse R DESCRIPTION file for Imports/Depends fields,
1135
+ # correctly handling multi-line (continued) fields where
1136
+ # continuation lines start with whitespace.
1137
+ lines = content.splitlines()
1138
+ i = 0
1139
+ while i < len(lines):
1140
+ raw_line = lines[i]
1141
+ stripped = raw_line.lstrip()
1142
+ if stripped.startswith(("Imports:", "Depends:")):
1143
+ # Extract the text after the field name and colon
1144
+ _, after_colon = stripped.split(":", 1)
1145
+ pkg_chunks: list[str] = [after_colon.strip()]
1146
+ # Collect continuation lines that start with whitespace
1147
+ j = i + 1
1148
+ while j < len(lines) and (
1149
+ lines[j].startswith(" ") or lines[j].startswith("\t")
1150
+ ):
1151
+ pkg_chunks.append(lines[j].strip())
1152
+ j += 1
1153
+ # Join all chunks into a single dependency string
1154
+ pkg_str = " ".join(pkg_chunks)
1155
+ # Split on commas to get individual package entries
1156
+ pkgs = [p.strip() for p in pkg_str.split(",") if p.strip()]
1157
+ for pkg in pkgs:
1158
+ # Remove version specifications if present
1159
+ pkg = pkg.split("(", 1)[0].strip()
1160
+ if pkg:
1161
+ dependencies.append(pkg)
1162
+ # Continue parsing from the first non-continuation line
1163
+ i = j
1164
+ continue
1165
+ i += 1
1166
+ elif language == "conda":
1167
+ # Parse conda environment.yml
1168
+ try:
1169
+ data = yaml.safe_load(content)
1170
+ deps = data.get("dependencies", [])
1171
+ for dep in deps:
1172
+ if isinstance(dep, str):
1173
+ # Extract package name (before version spec)
1174
+ pkg = dep.split("==")[0].split(">=")[0].split("<=")[0]
1175
+ pkg = pkg.split("=")[0].strip()
1176
+ if "::" in pkg:
1177
+ pkg = pkg.split("::", 1)[1]
1178
+ if pkg:
1179
+ dependencies.append(pkg)
1180
+ elif isinstance(dep, dict):
1181
+ # Handle pip dependencies nested as {pip: [...]}
1182
+ pip_deps = dep.get("pip", [])
1183
+ for pip_dep in pip_deps:
1184
+ if isinstance(pip_dep, str):
1185
+ pkg = (
1186
+ pip_dep.split("==")[0]
1187
+ .split(">=")[0]
1188
+ .split("<=")[0]
1189
+ .split("[")[0]
1190
+ .strip()
1191
+ )
1192
+ if "::" in pkg:
1193
+ pkg = pkg.split("::", 1)[1]
1194
+ if pkg:
1195
+ dependencies.append(pkg)
1196
+ except Exception:
1197
+ pass
1198
+ # Remove duplicates and sort
1199
+ return sorted(list(set(dependencies)))
1200
+
1201
+
1202
+ def env_has_superset_dependencies(
1203
+ env: dict,
1204
+ required_deps: list[str],
1205
+ env_spec_path: str | None = None,
1206
+ strict: bool = False,
1207
+ ) -> bool:
1208
+ """Check if an environment has a superset of required dependencies.
1209
+
1210
+ Parameters
1211
+ ----------
1212
+ env : dict
1213
+ Environment dict from calkit.yaml with 'kind' and 'path' keys.
1214
+ required_deps : list[str]
1215
+ List of required dependencies to check for.
1216
+ env_spec_path : str | None
1217
+ Path to the environment spec file. If None, will use env.get("path").
1218
+ strict : bool
1219
+ If True, require the spec file to exist and have extractable
1220
+ dependencies. If False, be optimistic when spec file doesn't exist.
1221
+
1222
+ Returns
1223
+ -------
1224
+ bool
1225
+ True if the environment contains all required dependencies,
1226
+ False otherwise.
1227
+ """
1228
+ if not required_deps:
1229
+ # No dependencies to check, so any environment works
1230
+ return True
1231
+ if env_spec_path is None:
1232
+ env_spec_path = env.get("path")
1233
+ if not env_spec_path:
1234
+ # No path to check
1235
+ if strict:
1236
+ return False
1237
+ return True
1238
+ if not os.path.exists(env_spec_path):
1239
+ # Spec file doesn't exist
1240
+ if strict:
1241
+ # Strict mode: can't verify, so reject
1242
+ return False
1243
+ # Optimistic mode: assume it might work
1244
+ return True
1245
+ # Extract dependencies from the environment's spec file
1246
+ env_deps = extract_dependencies_from_spec_file(env_spec_path)
1247
+ if not env_deps:
1248
+ # Couldn't extract dependencies (or file is empty)
1249
+ if strict:
1250
+ # Strict mode: can't verify, so reject
1251
+ return False
1252
+ # Optimistic mode: assume it might work
1253
+ return True
1254
+ # Check if env_deps is a superset of required_deps
1255
+ # (case-insensitive comparison for package names)
1256
+ env_deps_lower = {dep.lower() for dep in env_deps}
1257
+ required_deps_lower = {dep.lower() for dep in required_deps}
1258
+ transitive = {
1259
+ "jupyter": {"ipykernel"},
1260
+ "pandas": {"numpy"},
1261
+ }
1262
+ for base, provides in transitive.items():
1263
+ if base in env_deps_lower:
1264
+ env_deps_lower.update(provides)
1265
+ return required_deps_lower.issubset(env_deps_lower)
1266
+
1267
+
1058
1268
  def detect_env_for_stage(
1059
1269
  stage: dict,
1060
1270
  environment: str | None = None,
@@ -1144,8 +1354,74 @@ def detect_env_for_stage(
1144
1354
  is_first_env_for_language = not any(
1145
1355
  env.get("kind") in preferred_kinds for env in envs.values()
1146
1356
  )
1147
- # 2) If there is already an environment for the stage language, use that
1148
- if stage_language:
1357
+ # Stages with analyzable content where we should check dependencies before
1358
+ # reusing existing environments
1359
+ analyzable_stages = {
1360
+ "jupyter-notebook",
1361
+ "python-script",
1362
+ "r-script",
1363
+ "julia-script",
1364
+ "shell-script",
1365
+ }
1366
+ # Initialize detected_dependencies so it's available throughout function
1367
+ detected_dependencies: list[str] = []
1368
+ # For analyzable stages, detect dependencies and check if existing
1369
+ # environments satisfy them
1370
+ if stage_language and stage_kind in analyzable_stages:
1371
+ if stage_kind == "python-script":
1372
+ detected_dependencies = detect_python_dependencies(
1373
+ script_path=stage["script_path"]
1374
+ )
1375
+ elif stage_kind == "r-script":
1376
+ detected_dependencies = detect_r_dependencies(
1377
+ script_path=stage["script_path"]
1378
+ )
1379
+ elif stage_kind == "julia-script":
1380
+ detected_dependencies = detect_julia_dependencies(
1381
+ script_path=stage["script_path"]
1382
+ )
1383
+ elif stage_kind == "jupyter-notebook":
1384
+ notebook_lang = language_from_notebook(stage["notebook_path"])
1385
+ detected_dependencies = detect_dependencies_from_notebook(
1386
+ stage["notebook_path"], language=notebook_lang
1387
+ )
1388
+ elif stage_kind == "matlab-script":
1389
+ # MATLAB detection if needed
1390
+ detected_dependencies = []
1391
+ elif stage_kind == "shell-script":
1392
+ # Shell script detection if needed
1393
+ detected_dependencies = []
1394
+
1395
+ # Check if any existing environment has all these dependencies
1396
+ matching_envs = [
1397
+ (name, env)
1398
+ for name, env in envs.items()
1399
+ if env.get("kind") in preferred_kinds
1400
+ ]
1401
+ if matching_envs and detected_dependencies:
1402
+ # Check if any matching environment has all required dependencies
1403
+ # Use strict mode: only reuse if we can verify the deps are satisfied
1404
+ for env_name, env in sorted(
1405
+ matching_envs, key=lambda item: item[0]
1406
+ ):
1407
+ if env_has_superset_dependencies(
1408
+ env, detected_dependencies, strict=True
1409
+ ):
1410
+ env_name = cast(str, env_name)
1411
+ return EnvForStageResult(
1412
+ name=env_name,
1413
+ env=env,
1414
+ exists=True,
1415
+ spec_path=env.get("path"),
1416
+ dependencies=detected_dependencies,
1417
+ created_from_dependencies=False,
1418
+ )
1419
+ # No existing environment has verified dependencies, fall through to create
1420
+ # If no matching environment found or no dependencies detected,
1421
+ # fall through to create one from dependencies
1422
+ # 2) If there is already an environment for the stage language (for
1423
+ # non-analyzable stages or analyzable stages with no match), use that
1424
+ if stage_language and stage_kind not in analyzable_stages:
1149
1425
  matching_envs = [
1150
1426
  (name, env)
1151
1427
  for name, env in envs.items()
@@ -1172,6 +1448,7 @@ def detect_env_for_stage(
1172
1448
  created_from_dependencies=False,
1173
1449
  )
1174
1450
  # 3) If a typical env spec exists for the stage language, use that
1451
+ # (fallback for analyzable stages if no existing environment matched)
1175
1452
  if stage_language:
1176
1453
  if stage_language == "latex":
1177
1454
  res = env_from_name_or_path(
@@ -1219,6 +1496,46 @@ def detect_env_for_stage(
1219
1496
  ck_info=ck_info,
1220
1497
  language=stage_language,
1221
1498
  )
1499
+ # For analyzable stages with detected dependencies, verify the
1500
+ # spec file has all required packages before reusing
1501
+ if stage_kind in analyzable_stages:
1502
+ # Detect dependencies for this stage if not already done
1503
+ if not detected_dependencies:
1504
+ if stage_kind == "python-script":
1505
+ detected_dependencies = detect_python_dependencies(
1506
+ script_path=stage["script_path"]
1507
+ )
1508
+ elif stage_kind == "r-script":
1509
+ detected_dependencies = detect_r_dependencies(
1510
+ script_path=stage["script_path"]
1511
+ )
1512
+ elif stage_kind == "julia-script":
1513
+ detected_dependencies = detect_julia_dependencies(
1514
+ script_path=stage["script_path"]
1515
+ )
1516
+ elif stage_kind == "jupyter-notebook":
1517
+ notebook_lang = language_from_notebook(
1518
+ stage["notebook_path"]
1519
+ )
1520
+ detected_dependencies = (
1521
+ detect_dependencies_from_notebook(
1522
+ stage["notebook_path"],
1523
+ language=notebook_lang,
1524
+ )
1525
+ )
1526
+ # Only reuse if it has all the dependencies (strict mode)
1527
+ if (
1528
+ detected_dependencies
1529
+ and not env_has_superset_dependencies(
1530
+ res.env,
1531
+ detected_dependencies,
1532
+ spec_path,
1533
+ strict=True,
1534
+ )
1535
+ ):
1536
+ # This spec file doesn't have all deps, try next
1537
+ # candidate
1538
+ continue
1222
1539
  return EnvForStageResult(
1223
1540
  name=res.name,
1224
1541
  env=res.env,
@@ -1237,6 +1554,7 @@ def detect_env_for_stage(
1237
1554
  dependencies = detect_python_dependencies(
1238
1555
  script_path=stage["script_path"]
1239
1556
  )
1557
+ project_name = calkit.detect_project_name(prepend_owner=False)
1240
1558
  # Generate unique environment name
1241
1559
  if is_first_env_for_language:
1242
1560
  temp_path = "pyproject.toml"
@@ -1248,15 +1566,16 @@ def detect_env_for_stage(
1248
1566
  "path": spec_path,
1249
1567
  }
1250
1568
  else:
1251
- temp_path = ".calkit/envs/py/requirements.txt"
1252
- env_name = make_env_name(temp_path, all_env_names, kind="uv-venv")
1253
- spec_path = f".calkit/envs/{env_name}/requirements.txt"
1254
- spec_content = create_python_requirements_content(dependencies)
1569
+ temp_path = ".calkit/envs/py/pyproject.toml"
1570
+ env_name = make_env_name(temp_path, all_env_names, kind="uv")
1571
+ spec_path = f".calkit/envs/{env_name}/pyproject.toml"
1572
+ spec_content = create_uv_pyproject_content(
1573
+ dependencies,
1574
+ project_name=f"{project_name}-{env_name}",
1575
+ )
1255
1576
  env_dict = {
1256
- "kind": "uv-venv",
1577
+ "kind": "uv",
1257
1578
  "path": spec_path,
1258
- "python": DEFAULT_PYTHON_VERSION,
1259
- "prefix": os.path.join(os.path.dirname(spec_path), ".venv"),
1260
1579
  }
1261
1580
  elif stage["kind"] == "r-script":
1262
1581
  dependencies = detect_r_dependencies(script_path=stage["script_path"])
@@ -1304,6 +1623,7 @@ def detect_env_for_stage(
1304
1623
  stage["notebook_path"], language=notebook_lang
1305
1624
  )
1306
1625
  if notebook_lang == "python" or notebook_lang is None:
1626
+ project_name = calkit.detect_project_name(prepend_owner=False)
1307
1627
  # Add ipykernel for Jupyter notebook support
1308
1628
  if "ipykernel" not in dependencies:
1309
1629
  dependencies.append("ipykernel")
@@ -1318,19 +1638,16 @@ def detect_env_for_stage(
1318
1638
  "path": spec_path,
1319
1639
  }
1320
1640
  else:
1321
- temp_path = ".calkit/envs/py/requirements.txt"
1322
- env_name = make_env_name(
1323
- temp_path, all_env_names, kind="uv-venv"
1641
+ temp_path = ".calkit/envs/py/pyproject.toml"
1642
+ env_name = make_env_name(temp_path, all_env_names, kind="uv")
1643
+ spec_path = f".calkit/envs/{env_name}/pyproject.toml"
1644
+ spec_content = create_uv_pyproject_content(
1645
+ dependencies,
1646
+ project_name=f"{project_name}-{env_name}",
1324
1647
  )
1325
- spec_path = f".calkit/envs/{env_name}/requirements.txt"
1326
- spec_content = create_python_requirements_content(dependencies)
1327
1648
  env_dict = {
1328
- "kind": "uv-venv",
1649
+ "kind": "uv",
1329
1650
  "path": spec_path,
1330
- "python": DEFAULT_PYTHON_VERSION,
1331
- "prefix": os.path.join(
1332
- os.path.dirname(spec_path), ".venv"
1333
- ),
1334
1651
  }
1335
1652
  elif notebook_lang == "r":
1336
1653
  # Add IRkernel for Jupyter notebook support
@@ -1354,6 +1671,7 @@ def detect_env_for_stage(
1354
1671
  # Add IJulia for Jupyter notebook support
1355
1672
  if "IJulia" not in dependencies:
1356
1673
  dependencies.append("IJulia")
1674
+ project_name = calkit.detect_project_name(prepend_owner=False)
1357
1675
  # Generate unique environment name
1358
1676
  if is_first_env_for_language:
1359
1677
  temp_path = "Project.toml"
@@ -1361,16 +1679,21 @@ def detect_env_for_stage(
1361
1679
  temp_path, all_env_names, kind="julia"
1362
1680
  )
1363
1681
  spec_path = "Project.toml"
1682
+ julia_env_name = project_name
1364
1683
  else:
1365
1684
  temp_path = ".calkit/envs/julia/Project.toml"
1366
1685
  env_name = make_env_name(
1367
1686
  temp_path, all_env_names, kind="julia"
1368
1687
  )
1369
1688
  spec_path = f".calkit/envs/{env_name}/Project.toml"
1370
- spec_content = create_julia_project_file_content(dependencies)
1689
+ julia_env_name = f"{project_name}-{env_name}"
1690
+ spec_content = create_julia_project_file_content(
1691
+ dependencies, project_name=julia_env_name
1692
+ )
1371
1693
  env_dict = {
1372
1694
  "kind": "julia",
1373
1695
  "path": spec_path,
1696
+ "julia": _get_julia_version(),
1374
1697
  }
1375
1698
  if not spec_path or not env_name:
1376
1699
  raise ValueError(
@@ -389,11 +389,13 @@ class NotebookKernelRouteHandler(APIHandler):
389
389
  400, "Both 'path' and 'environment' are required"
390
390
  )
391
391
  try:
392
- kernel_name = check_env_kernel(env_name=env_name)
392
+ kernel_name, display_name = check_env_kernel(env_name=env_name)
393
393
  except Exception as e:
394
394
  self.log.error(f"Failed to check env kernel for {env_name}: {e}")
395
395
  return self.error(500, f"Failed to check environment kernel: {e}")
396
- self.finish(json.dumps({"name": kernel_name}))
396
+ self.finish(
397
+ json.dumps({"name": kernel_name, "display_name": display_name})
398
+ )
397
399
 
398
400
 
399
401
  class NotebookStageRouteHandler(APIHandler):
@@ -412,7 +414,11 @@ class NotebookStageRouteHandler(APIHandler):
412
414
  executed_ipynb_storage = body.get("executed_ipynb_storage", "git")
413
415
  if not notebook_path or not stage_name or not env_name:
414
416
  return self.error(
415
- 400, "Request body must include 'path' and 'stage_name'"
417
+ 400,
418
+ (
419
+ "Request body must include 'path', 'stage_name', and "
420
+ "'environment'"
421
+ ),
416
422
  )
417
423
  ck_info = calkit.load_calkit_info()
418
424
  stages = ck_info.get("pipeline", {}).get("stages", {})
@@ -431,7 +431,7 @@ class MatlabScriptStage(Stage):
431
431
  def dvc_cmd(self) -> str:
432
432
  cmd = self.xenv_cmd
433
433
  if self.environment == "_system":
434
- cmd += "matlab -batch"
434
+ cmd += "matlab -noFigureWindows -batch"
435
435
  matlab_cmd = ""
436
436
  if self.matlab_path is not None:
437
437
  matlab_cmd += f"addpath(genpath('{self.matlab_path}')); "
@@ -450,7 +450,7 @@ class MatlabCommandStage(Stage):
450
450
  matlab_cmd = self.command.replace('"', '\\"')
451
451
  cmd = self.xenv_cmd
452
452
  if self.environment == "_system":
453
- cmd += "matlab -batch"
453
+ cmd += "matlab -noFigureWindows -batch"
454
454
  cmd += f' "{matlab_cmd}"'
455
455
  return cmd
456
456