calkit-python 0.39.1__tar.gz → 0.39.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 (328) hide show
  1. {calkit_python-0.39.1 → calkit_python-0.39.2}/PKG-INFO +1 -1
  2. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/main/core.py +41 -20
  3. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/conda.py +182 -10
  4. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/pipeline.py +67 -0
  5. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_conda.py +59 -0
  6. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_pipeline.py +27 -1
  7. {calkit_python-0.39.1 → calkit_python-0.39.2}/.claude-plugin/marketplace.json +0 -0
  8. {calkit_python-0.39.1 → calkit_python-0.39.2}/.gitignore +0 -0
  9. {calkit_python-0.39.1 → calkit_python-0.39.2}/.pre-commit-config.yaml +0 -0
  10. {calkit_python-0.39.1 → calkit_python-0.39.2}/.prettierignore +0 -0
  11. {calkit_python-0.39.1 → calkit_python-0.39.2}/.python-version +0 -0
  12. {calkit_python-0.39.1 → calkit_python-0.39.2}/.vscode/launch.json +0 -0
  13. {calkit_python-0.39.1 → calkit_python-0.39.2}/.vscode/tasks.json +0 -0
  14. {calkit_python-0.39.1 → calkit_python-0.39.2}/.yarnrc.yml +0 -0
  15. {calkit_python-0.39.1 → calkit_python-0.39.2}/AGENTS.md +0 -0
  16. {calkit_python-0.39.1 → calkit_python-0.39.2}/CITATION.cff +0 -0
  17. {calkit_python-0.39.1 → calkit_python-0.39.2}/CODE_OF_CONDUCT.md +0 -0
  18. {calkit_python-0.39.1 → calkit_python-0.39.2}/CONTRIBUTING.md +0 -0
  19. {calkit_python-0.39.1 → calkit_python-0.39.2}/LICENSE +0 -0
  20. {calkit_python-0.39.1 → calkit_python-0.39.2}/Makefile +0 -0
  21. {calkit_python-0.39.1 → calkit_python-0.39.2}/README.md +0 -0
  22. {calkit_python-0.39.1 → calkit_python-0.39.2}/agent-plugin/.claude-plugin/plugin.json +0 -0
  23. {calkit_python-0.39.1 → calkit_python-0.39.2}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
  24. {calkit_python-0.39.1 → calkit_python-0.39.2}/agent-plugin/skills/conventions/SKILL.md +0 -0
  25. {calkit_python-0.39.1 → calkit_python-0.39.2}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
  26. {calkit_python-0.39.1 → calkit_python-0.39.2}/babel.config.js +0 -0
  27. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/__init__.py +0 -0
  28. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/__main__.py +0 -0
  29. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/calc.py +0 -0
  30. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/check.py +0 -0
  31. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/__init__.py +0 -0
  32. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/check.py +0 -0
  33. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/cloud.py +0 -0
  34. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/config.py +0 -0
  35. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/core.py +0 -0
  36. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/describe.py +0 -0
  37. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/dev.py +0 -0
  38. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/import_.py +0 -0
  39. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/latex.py +0 -0
  40. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/list.py +0 -0
  41. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/main/__init__.py +0 -0
  42. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/main/xr.py +0 -0
  43. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/new.py +0 -0
  44. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/notebooks.py +0 -0
  45. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/office.py +0 -0
  46. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/overleaf.py +0 -0
  47. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/slurm.py +0 -0
  48. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cli/update.py +0 -0
  49. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/cloud.py +0 -0
  50. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/config.py +0 -0
  51. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/core.py +0 -0
  52. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/datasets.py +0 -0
  53. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/detect.py +0 -0
  54. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/docker.py +0 -0
  55. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/dvc/__init__.py +0 -0
  56. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/dvc/core.py +0 -0
  57. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/dvc/zip.py +0 -0
  58. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/environments.py +0 -0
  59. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/fs.py +0 -0
  60. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/git.py +0 -0
  61. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/github.py +0 -0
  62. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/gui.py +0 -0
  63. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/invenio.py +0 -0
  64. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/julia.py +0 -0
  65. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/jupyter.py +0 -0
  66. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/jupyterlab/__init__.py +0 -0
  67. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/jupyterlab/routes.py +0 -0
  68. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/package.json +0 -0
  69. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  70. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  71. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  72. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  73. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  74. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  75. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  76. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  77. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  78. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/remoteEntry.65469af996e7a96aa983.js +0 -0
  79. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/style.js +0 -0
  80. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/labextension/static/third-party-licenses.json +0 -0
  81. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/licenses.py +0 -0
  82. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/magics.py +0 -0
  83. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/matlab.py +0 -0
  84. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/models/__init__.py +0 -0
  85. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/models/core.py +0 -0
  86. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/models/io.py +0 -0
  87. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/models/iteration.py +0 -0
  88. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/models/pipeline.py +0 -0
  89. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/notebooks.py +0 -0
  90. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/office.py +0 -0
  91. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/ops.py +0 -0
  92. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/overleaf.py +0 -0
  93. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/releases.py +0 -0
  94. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/server.py +0 -0
  95. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/__init__.py +0 -0
  96. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/core.py +0 -0
  97. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/__init__.py +0 -0
  98. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/article/paper.tex +0 -0
  99. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/core.py +0 -0
  100. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/jfm/jfm.bst +0 -0
  101. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/jfm/jfm.cls +0 -0
  102. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  103. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/jfm/paper.tex +0 -0
  104. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/templates/latex/jfm/upmath.sty +0 -0
  105. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/__init__.py +0 -0
  106. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/__init__.py +0 -0
  107. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/main/__init__.py +0 -0
  108. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/main/test_core.py +0 -0
  109. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/main/test_subprojects.py +0 -0
  110. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/main/test_xr.py +0 -0
  111. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_check.py +0 -0
  112. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_cloud.py +0 -0
  113. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_config.py +0 -0
  114. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_import.py +0 -0
  115. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_latex.py +0 -0
  116. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_list.py +0 -0
  117. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_new.py +0 -0
  118. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_notebooks.py +0 -0
  119. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_overleaf.py +0 -0
  120. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/cli/test_update.py +0 -0
  121. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/dvc/__init__.py +0 -0
  122. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/dvc/test_core.py +0 -0
  123. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/dvc/test_zip.py +0 -0
  124. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/jupyterlab/__init__.py +0 -0
  125. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/jupyterlab/test_routes.py +0 -0
  126. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/models/__init__.py +0 -0
  127. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/models/test_iteration.py +0 -0
  128. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/models/test_pipeline.py +0 -0
  129. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_calc.py +0 -0
  130. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_check.py +0 -0
  131. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_cloud.py +0 -0
  132. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_core.py +0 -0
  133. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_detect.py +0 -0
  134. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_docker.py +0 -0
  135. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_environments.py +0 -0
  136. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_fs.py +0 -0
  137. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_git.py +0 -0
  138. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_invenio.py +0 -0
  139. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_julia.py +0 -0
  140. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_jupyter.py +0 -0
  141. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_magics.py +0 -0
  142. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_matlab.py +0 -0
  143. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_notebooks.py +0 -0
  144. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_releases.py +0 -0
  145. {calkit_python-0.39.1 → calkit_python-0.39.2}/calkit/tests/test_templates.py +0 -0
  146. {calkit_python-0.39.1 → calkit_python-0.39.2}/conftest.py +0 -0
  147. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/CNAME +0 -0
  148. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/ai-tools.md +0 -0
  149. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/apps.md +0 -0
  150. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/calculations.md +0 -0
  151. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/calkit-yaml.md +0 -0
  152. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/cli-reference.md +0 -0
  153. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/cloud-integration.md +0 -0
  154. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/datasets.md +0 -0
  155. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/dependencies.md +0 -0
  156. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/environments.md +0 -0
  157. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/examples.md +0 -0
  158. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/governance.md +0 -0
  159. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/help.md +0 -0
  160. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/c-to-the-k-white.svg +0 -0
  161. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/calkit-fragmentation-compendium.png +0 -0
  162. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/calkit-no-bg.png +0 -0
  163. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/connect-zenodo.png +0 -0
  164. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/jupyterlab/all-green.png +0 -0
  165. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  166. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/jupyterlab/new-env.png +0 -0
  167. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/jupyterlab/new-notebook.png +0 -0
  168. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  169. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/jupyterlab-params.png +0 -0
  170. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/plos-osi-code-2024-03.png +0 -0
  171. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/img/vscode-nb-params.png +0 -0
  172. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/index.md +0 -0
  173. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/installation.md +0 -0
  174. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/jupyterlab.md +0 -0
  175. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/local-server.md +0 -0
  176. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/notebooks.md +0 -0
  177. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/overleaf.md +0 -0
  178. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/pipeline/index.md +0 -0
  179. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/pipeline/manual-steps.md +0 -0
  180. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/pipeline/running-and-logging.md +0 -0
  181. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/pipeline/slurm.md +0 -0
  182. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/quickstart.md +0 -0
  183. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/references.md +0 -0
  184. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/releases.md +0 -0
  185. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/reproducibility.md +0 -0
  186. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/subprojects.md +0 -0
  187. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  188. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/conda-envs.md +0 -0
  189. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/existing-project.md +0 -0
  190. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/first-project.md +0 -0
  191. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/github-actions.md +0 -0
  192. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  193. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  194. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  195. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  196. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  197. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  198. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  199. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  200. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  201. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  202. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  203. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  204. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  205. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  206. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  207. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/create-project.png +0 -0
  208. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  209. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/excel-chart.png +0 -0
  210. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/excel-data.png +0 -0
  211. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  212. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/needs-clone.png +0 -0
  213. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/new-stage.png +0 -0
  214. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  215. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  216. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/status-more-rows.png +0 -0
  217. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  218. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/untracked-data.png +0 -0
  219. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/updated-publication.png +0 -0
  220. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  221. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/office/workflow-page.png +0 -0
  222. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/clone.png +0 -0
  223. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/create-project.png +0 -0
  224. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  225. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  226. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  227. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/new-token.png +0 -0
  228. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/reclone.png +0 -0
  229. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  230. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/quick-actions.png +0 -0
  231. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/run-proc.png +0 -0
  232. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
  233. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
  234. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
  235. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
  236. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
  237. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
  238. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
  239. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/index.md +0 -0
  240. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/jupyterlab.md +0 -0
  241. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/latex-codespaces.md +0 -0
  242. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/matlab.md +0 -0
  243. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/notebook-pipeline.md +0 -0
  244. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/office.md +0 -0
  245. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/openfoam.md +0 -0
  246. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/procedures.md +0 -0
  247. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/tutorials/vscode-slurm-notebook.md +0 -0
  248. {calkit_python-0.39.1 → calkit_python-0.39.2}/docs/version-control.md +0 -0
  249. {calkit_python-0.39.1 → calkit_python-0.39.2}/install.json +0 -0
  250. {calkit_python-0.39.1 → calkit_python-0.39.2}/jest.config.js +0 -0
  251. {calkit_python-0.39.1 → calkit_python-0.39.2}/jupyter-config/server-config/calkit.json +0 -0
  252. {calkit_python-0.39.1 → calkit_python-0.39.2}/mkdocs.yml +0 -0
  253. {calkit_python-0.39.1 → calkit_python-0.39.2}/package.json +0 -0
  254. {calkit_python-0.39.1 → calkit_python-0.39.2}/pyproject.toml +0 -0
  255. {calkit_python-0.39.1 → calkit_python-0.39.2}/schema/plugin.json +0 -0
  256. {calkit_python-0.39.1 → calkit_python-0.39.2}/scripts/generate-docs-references.py +0 -0
  257. {calkit_python-0.39.1 → calkit_python-0.39.2}/scripts/install.ps1 +0 -0
  258. {calkit_python-0.39.1 → calkit_python-0.39.2}/scripts/install.sh +0 -0
  259. {calkit_python-0.39.1 → calkit_python-0.39.2}/scripts/make-calk9.sh +0 -0
  260. {calkit_python-0.39.1 → calkit_python-0.39.2}/scripts/sync-docs.py +0 -0
  261. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/__tests__/useQueries.spec.ts +0 -0
  262. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/calkit-config.ts +0 -0
  263. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/cell-output-marker.ts +0 -0
  264. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/commit-dialog.tsx +0 -0
  265. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/environment-editor.tsx +0 -0
  266. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/notebook-registration.tsx +0 -0
  267. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/notebook-toolbar.tsx +0 -0
  268. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/pipeline-status-bar.tsx +0 -0
  269. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/project-info-editor.tsx +0 -0
  270. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/sidebar-settings.tsx +0 -0
  271. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/sidebar.tsx +0 -0
  272. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/components/stage-editor.tsx +0 -0
  273. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/feature-flags.ts +0 -0
  274. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/file-browser-menu.ts +0 -0
  275. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  276. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/hooks/useQueries.ts +0 -0
  277. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/icons.ts +0 -0
  278. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/index.ts +0 -0
  279. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/io-tracker.ts +0 -0
  280. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/pipeline-state.ts +0 -0
  281. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/queryClient.ts +0 -0
  282. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/request.ts +0 -0
  283. {calkit_python-0.39.1 → calkit_python-0.39.2}/src/shims-mainmenu.d.ts +0 -0
  284. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/base.css +0 -0
  285. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/cell-output-marker.css +0 -0
  286. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/environment-editor.css +0 -0
  287. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/environment-selector.css +0 -0
  288. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/img/calkit-no-bg.png +0 -0
  289. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/index.css +0 -0
  290. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/index.js +0 -0
  291. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/notebook-toolbar.css +0 -0
  292. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/pipeline-status-bar.css +0 -0
  293. {calkit_python-0.39.1 → calkit_python-0.39.2}/style/sidebar.css +0 -0
  294. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  295. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/nb-julia.ipynb +0 -0
  296. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/nb-params.ipynb +0 -0
  297. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/nb-subdir.ipynb +0 -0
  298. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/pipeline.ipynb +0 -0
  299. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/script.py +0 -0
  300. {calkit_python-0.39.1 → calkit_python-0.39.2}/test/test-log.log +0 -0
  301. {calkit_python-0.39.1 → calkit_python-0.39.2}/tsconfig.json +0 -0
  302. {calkit_python-0.39.1 → calkit_python-0.39.2}/tsconfig.test.json +0 -0
  303. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/.gitignore +0 -0
  304. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/README.md +0 -0
  305. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/jupyter_server_test_config.py +0 -0
  306. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/package.json +0 -0
  307. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/playwright.config.js +0 -0
  308. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/tests/calkit.spec.ts +0 -0
  309. {calkit_python-0.39.1 → calkit_python-0.39.2}/ui-tests/yarn.lock +0 -0
  310. {calkit_python-0.39.1 → calkit_python-0.39.2}/uv.lock +0 -0
  311. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/.gitignore +0 -0
  312. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/.vscodeignore +0 -0
  313. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/CHANGELOG.md +0 -0
  314. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/LICENSE +0 -0
  315. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/README.md +0 -0
  316. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/images/calkit-no-bg.png +0 -0
  317. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/package-lock.json +0 -0
  318. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/package.json +0 -0
  319. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/scripts/set-proposed-api.js +0 -0
  320. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/environments.ts +0 -0
  321. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/extension.ts +0 -0
  322. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/notebooks.ts +0 -0
  323. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/test/environments.test.ts +0 -0
  324. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
  325. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/test/notebooks.test.ts +0 -0
  326. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/src/types.ts +0 -0
  327. {calkit_python-0.39.1 → calkit_python-0.39.2}/vscode-ext/tsconfig.json +0 -0
  328. {calkit_python-0.39.1 → calkit_python-0.39.2}/yarn.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.39.1
3
+ Version: 0.39.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
@@ -669,9 +669,11 @@ def add(
669
669
  paths.remove(".")
670
670
  if dvc_repo is not None:
671
671
  dvc_status = dvc_repo.data_status()
672
- for dvc_uncommitted in dvc_status["uncommitted"].get(
672
+ uncommitted = dvc_status["uncommitted"]
673
+ dvc_uncommitted_all = uncommitted.get(
673
674
  "modified", []
674
- ):
675
+ ) + uncommitted.get("deleted", [])
676
+ for dvc_uncommitted in dvc_uncommitted_all:
675
677
  if os.path.exists(dvc_uncommitted):
676
678
  if dry_run:
677
679
  typer.echo(
@@ -679,12 +681,25 @@ def add(
679
681
  )
680
682
  else:
681
683
  typer.echo(f"Adding {dvc_uncommitted} to DVC")
682
- dvc_repo.commit(dvc_uncommitted, force=True)
684
+ dvc_repo.commit(
685
+ dvc_uncommitted,
686
+ force=True,
687
+ allow_missing=True,
688
+ )
683
689
  else:
684
- warn(
685
- f"DVC uncommitted '{dvc_uncommitted}' does not "
686
- "exist; skipping"
687
- )
690
+ if dry_run:
691
+ typer.echo(
692
+ f"Would commit deleted {dvc_uncommitted} to DVC"
693
+ )
694
+ else:
695
+ typer.echo(
696
+ f"Committing deleted {dvc_uncommitted} to DVC"
697
+ )
698
+ dvc_repo.commit(
699
+ dvc_uncommitted,
700
+ force=True,
701
+ allow_missing=True,
702
+ )
688
703
  if not disable_auto_ignore:
689
704
  for untracked_file in untracked_git_files:
690
705
  if (
@@ -1453,13 +1468,8 @@ def run(
1453
1468
  raise_error("Failed to initialize Git repo")
1454
1469
  # Set env vars
1455
1470
  calkit.set_env_vars(ck_info=ck_info)
1456
- # Clean all notebooks in the pipeline
1457
- try:
1458
- calkit.notebooks.clean_all_in_pipeline(ck_info=ck_info)
1459
- except Exception as e:
1460
- raise_error(f"Failed to clean notebooks: {e.__class__.__name__}: {e}")
1461
1471
  if not quiet:
1462
- typer.echo("Getting system information")
1472
+ calkit.echo("💻 Getting system information")
1463
1473
  # Get system information
1464
1474
  system_info = calkit.get_system_info()
1465
1475
  if save_logs:
@@ -1475,7 +1485,7 @@ def run(
1475
1485
  json.dump(system_info, f, indent=2)
1476
1486
  # First check any system-level dependencies exist
1477
1487
  if not quiet:
1478
- typer.echo("Checking system-level dependencies")
1488
+ calkit.echo("🔗 Checking system-level dependencies")
1479
1489
  try:
1480
1490
  calkit.check_system_deps(ck_info=ck_info, system_info=system_info)
1481
1491
  except Exception as e:
@@ -1483,7 +1493,7 @@ def run(
1483
1493
  raise_error(str(e))
1484
1494
  # Check all environments in the pipeline (with caching)
1485
1495
  # If any failed, warn the user that we might have problems running
1486
- typer.echo("Checking environments")
1496
+ calkit.echo("📦 Checking environments")
1487
1497
  env_check_results = calkit.environments.check_all_in_pipeline(
1488
1498
  ck_info=ck_info, targets=targets, force=force
1489
1499
  )
@@ -1493,7 +1503,13 @@ def run(
1493
1503
  failed = not result.get("success", False)
1494
1504
  if failed:
1495
1505
  warn(f"Failed to check environment '{env_name}'")
1496
- # Check environments for each subproject so DVC can detect changes
1506
+ # Clean all notebooks in the pipeline
1507
+ try:
1508
+ calkit.echo("📓 Cleaning notebooks")
1509
+ calkit.notebooks.clean_all_in_pipeline(ck_info=ck_info)
1510
+ except Exception as e:
1511
+ raise_error(f"Failed to clean notebooks: {e.__class__.__name__}: {e}")
1512
+ # Check environments and clean notebooks for each subproject
1497
1513
  subprojects = ck_info.get("subprojects", [])
1498
1514
  if subprojects:
1499
1515
  prev_cwd = os.getcwd()
@@ -1503,11 +1519,13 @@ def run(
1503
1519
  sp = Path(subproject["path"]).as_posix()
1504
1520
  if not os.path.isdir(sp):
1505
1521
  continue
1506
- if not quiet:
1507
- typer.echo(f"Checking environments for subproject: {sp}")
1508
1522
  os.chdir(sp)
1509
1523
  try:
1510
1524
  sp_ck_info = calkit.load_calkit_info()
1525
+ if not quiet:
1526
+ calkit.echo(
1527
+ f"📦 Checking environments for subproject: {sp}"
1528
+ )
1511
1529
  sp_env_results = calkit.environments.check_all_in_pipeline(
1512
1530
  ck_info=sp_ck_info, force=force
1513
1531
  )
@@ -1519,9 +1537,12 @@ def run(
1519
1537
  f"Failed to check environment '{env_name}' "
1520
1538
  f"in subproject '{sp}'"
1521
1539
  )
1540
+ if not quiet:
1541
+ calkit.echo(f"📓 Cleaning notebooks for subproject: {sp}")
1542
+ calkit.notebooks.clean_all_in_pipeline(ck_info=sp_ck_info)
1522
1543
  except Exception as e:
1523
1544
  warn(
1524
- f"Failed to check environments for subproject '{sp}': "
1545
+ f"Failed to prepare subproject '{sp}': "
1525
1546
  f"{e.__class__.__name__}: {e}"
1526
1547
  )
1527
1548
  finally:
@@ -1533,7 +1554,7 @@ def run(
1533
1554
  dvc_stages = None
1534
1555
  if ck_info.get("pipeline", {}) or ck_info.get("subprojects"):
1535
1556
  if not quiet:
1536
- typer.echo("Compiling DVC pipeline")
1557
+ calkit.echo("🔀 Compiling DVC pipeline")
1537
1558
  try:
1538
1559
  dvc_stages = calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
1539
1560
  except Exception as e:
@@ -7,6 +7,7 @@ import os
7
7
  import re
8
8
  import shutil
9
9
  import subprocess
10
+ import sys
10
11
  import warnings
11
12
  from pathlib import Path
12
13
  from typing import cast
@@ -137,6 +138,75 @@ def _editable_package_name_from_dir(dir_path: str) -> str:
137
138
  raise ValueError(f"Could not determine package name from {dir_path}")
138
139
 
139
140
 
141
+ def _run_pip_freeze(env_prefix: str) -> list[str]:
142
+ """Run pip freeze inside a conda env and return the list of packages.
143
+
144
+ Uses the env's pip executable directly (avoids ``conda run``, which can
145
+ touch the env directory and invalidate the stored mtime check).
146
+ This captures git URLs and exact refs that ``conda env export`` drops.
147
+ """
148
+ if sys.platform == "win32":
149
+ pip_exe = os.path.join(env_prefix, "Scripts", "pip.exe")
150
+ else:
151
+ pip_exe = os.path.join(env_prefix, "bin", "pip")
152
+ if not os.path.isfile(pip_exe):
153
+ return []
154
+ output = subprocess.check_output([pip_exe, "freeze"]).decode()
155
+ return [
156
+ line.strip()
157
+ for line in output.splitlines()
158
+ if line.strip() and not line.startswith("#")
159
+ ]
160
+
161
+
162
+ _GIT_RE = re.compile(r"\s*@\s*git\+", re.IGNORECASE)
163
+
164
+
165
+ def _pkg_name_from_dep(dep: str) -> str:
166
+ """Extract the normalized package name from a pip/conda dep string."""
167
+ name = _GIT_RE.split(dep)[0]
168
+ name = re.split(r"[@=<>]", name)[0]
169
+ return name.strip().lower()
170
+
171
+
172
+ def _enrich_pip_deps_from_freeze(
173
+ pip_deps: list[str],
174
+ pip_freeze: list[str],
175
+ ) -> list[str]:
176
+ """Replace pip dep entries with pip freeze versions when available.
177
+
178
+ Preserves git URLs and exact refs that conda env export drops.
179
+ Editable installs in pip_deps are kept unchanged.
180
+ """
181
+ freeze_by_name = {
182
+ _pkg_name_from_dep(line): line
183
+ for line in pip_freeze
184
+ if not line.startswith("-e ") and "@ file://" not in line
185
+ }
186
+ result = []
187
+ for dep in pip_deps:
188
+ if dep.startswith("-e ") or dep.startswith("--editable "):
189
+ result.append(dep)
190
+ continue
191
+ name = _pkg_name_from_dep(dep)
192
+ result.append(freeze_by_name.get(name, dep))
193
+ return result
194
+
195
+
196
+ def _normalize_git_dep_url(dep: str) -> str:
197
+ """Extract and normalize the git URL+ref from a dep string for comparison.
198
+
199
+ Returns the lowercased URL with .git suffix removed.
200
+ """
201
+ parts = _GIT_RE.split(dep, maxsplit=1)
202
+ if len(parts) < 2:
203
+ return ""
204
+ url = parts[1].strip().lower()
205
+ if url.endswith(".git"):
206
+ url = url[:-4]
207
+ return url.rstrip("/")
208
+
209
+
140
210
  def _check_single(
141
211
  req: str, actual: str, env_spec_dir: str, conda: bool = False
142
212
  ) -> bool:
@@ -156,11 +226,28 @@ def _check_single(
156
226
  req = os.path.join(env_spec_dir, req)
157
227
  req = _editable_package_name_from_dir(req)
158
228
  editable = True
159
- # If this is a Git version, we can't check it
160
- # TODO: Clone Git repos to check?
161
- if "@git" in req:
162
- warnings.warn(f"Cannot check Git version for {req}")
163
- req = req.split("@git")[0]
229
+ # Handle git-based requirements both legacy "pkg@git+url" and PEP 508
230
+ # "pkg @ git+url" forms.
231
+ req_is_git = bool(_GIT_RE.search(req))
232
+ actual_is_git = bool(_GIT_RE.search(actual))
233
+ if req_is_git and actual_is_git:
234
+ # Both sides have git URLs: compare normalized URL+ref directly.
235
+ # A short SHA in the spec matches a full SHA in the installed dep when
236
+ # the full SHA starts with the short one.
237
+ req_url = _normalize_git_dep_url(req)
238
+ actual_url = _normalize_git_dep_url(actual)
239
+ if req_url and actual_url:
240
+ req_base, _, req_ref = req_url.rpartition("@")
241
+ actual_base, _, actual_ref = actual_url.rpartition("@")
242
+ if req_base != actual_base:
243
+ return False
244
+ return actual_ref.startswith(req_ref) or req_ref.startswith(
245
+ actual_ref
246
+ )
247
+ # Fallback: compare names only
248
+ return _pkg_name_from_dep(req) == _pkg_name_from_dep(actual)
249
+ if req_is_git:
250
+ req = _GIT_RE.split(req)[0].strip()
164
251
  req_name = re.split("[=<>]", req)[0].strip()
165
252
  req_spec = req.removeprefix(req_name).strip().replace(" ", "")
166
253
  if "[" in req_name:
@@ -176,9 +263,16 @@ def _check_single(
176
263
  numbers_and_dots = re.match(r"^[0-9.]+$", req_spec[2:])
177
264
  if numbers_and_dots and len(req_spec.split(".")) < 3:
178
265
  req_spec += ".*"
179
- actual_name, actual_vers = re.split("[=<>]+", actual, maxsplit=1)
180
- if actual_name != req_name:
266
+ if actual_is_git:
267
+ # Spec has no git URL but installed dep does; name match is sufficient
268
+ actual = _GIT_RE.split(actual)[0].strip()
269
+ actual_parts = re.split("[=<>]+", actual, maxsplit=1)
270
+ actual_name = actual_parts[0]
271
+ actual_vers = actual_parts[1] if len(actual_parts) > 1 else ""
272
+ if actual_name.strip().lower() != req_name.lower():
181
273
  return False
274
+ if req_is_git or actual_is_git:
275
+ return True
182
276
  actual_spec = actual.removeprefix(actual_name)
183
277
  if conda and actual_spec.startswith("="):
184
278
  actual_spec = "=" + actual_spec
@@ -328,6 +422,7 @@ def check_env(
328
422
  os.remove(lock_to_use_for_creation)
329
423
  lock_to_use_for_creation = None
330
424
  res = EnvCheckResult()
425
+ early_pip_freeze: list[str] = []
331
426
  if verbose:
332
427
  log_func("Getting conda info")
333
428
  conda_exe = find_conda_exe()
@@ -360,8 +455,10 @@ def check_env(
360
455
  prefix_orig = prefix
361
456
  if prefix is not None:
362
457
  prefix = os.path.abspath(prefix)
458
+ env_prefix_path = prefix
363
459
  env_check_fpath = os.path.join(prefix, "env-export.yml")
364
460
  else:
461
+ env_prefix_path = os.path.join(envs_dir, env_name)
365
462
  env_check_fpath = os.path.join(
366
463
  os.path.expanduser("~"),
367
464
  ".calkit",
@@ -371,6 +468,8 @@ def check_env(
371
468
  env_check_dir = os.path.dirname(env_check_fpath)
372
469
  os.makedirs(env_check_dir, exist_ok=True)
373
470
  env_spec_dir = os.path.dirname(os.path.abspath(env_fpath))
471
+ spec_pip_deps = _get_pip_dependency_list(env_spec["dependencies"])
472
+ spec_has_git_pip = any(_GIT_RE.search(d) for d in spec_pip_deps)
374
473
  # Create env export command, which will be used later
375
474
  export_cmd = [
376
475
  conda_exe, # Mamba output is slightly different
@@ -464,8 +563,26 @@ def check_env(
464
563
  env_check["mtime"] = os.path.getmtime(
465
564
  os.path.normpath(env_check["prefix"])
466
565
  )
467
- with open(env_check_fpath, "w") as f:
468
- ryaml.dump(env_check, f)
566
+ # If the spec has git pip deps, enrich the in-memory env_check pip
567
+ # section so that git refs are compared correctly during the dep check
568
+ # rather than falling back to name-only matching.
569
+ if spec_has_git_pip:
570
+ log_func("Running pip freeze to enrich git dep comparison")
571
+ try:
572
+ early_pip_freeze = _run_pip_freeze(env_prefix_path)
573
+ except Exception as e:
574
+ log_func(
575
+ f"pip freeze failed; git dep URLs may be missing: {e}"
576
+ )
577
+ if early_pip_freeze:
578
+ check_pip = _get_pip_dependency_list(env_check["dependencies"])
579
+ enriched_check_pip = _enrich_pip_deps_from_freeze(
580
+ check_pip, early_pip_freeze
581
+ )
582
+ for dep_entry in env_check["dependencies"]:
583
+ if isinstance(dep_entry, dict) and "pip" in dep_entry:
584
+ dep_entry["pip"] = enriched_check_pip
585
+ break
469
586
  # Determine if the env matches
470
587
  env_needs_rebuild = False
471
588
  existing_conda_deps, existing_pip_deps = _split_env_dependencies(
@@ -552,12 +669,41 @@ def check_env(
552
669
  )
553
670
  # If the env was rebuilt, export the env check
554
671
  res.env_needs_export = env_needs_export
672
+ # Determine whether we need pip freeze output for enriching the stored
673
+ # env check and lock file with exact git URLs/refs.
674
+ needs_pip_freeze = (
675
+ env_needs_export
676
+ or not res.env_exists
677
+ or res.env_needs_rebuild
678
+ or spec_has_git_pip
679
+ )
680
+ pip_freeze: list[str] = []
681
+ if needs_pip_freeze:
682
+ # Reuse the early freeze captured before dep check when possible so
683
+ # we don't run pip twice; fall back to a fresh run after a rebuild.
684
+ if early_pip_freeze and not res.env_needs_rebuild:
685
+ pip_freeze = early_pip_freeze
686
+ else:
687
+ log_func("Running pip freeze to capture git deps")
688
+ try:
689
+ pip_freeze = _run_pip_freeze(env_prefix_path)
690
+ except Exception as e:
691
+ log_func(
692
+ f"pip freeze failed; git dep URLs may be missing: {e}"
693
+ )
555
694
  if env_needs_export:
556
695
  log_func(f"Exporting existing env to {env_check_fpath}")
557
696
  env_check = json.loads(subprocess.check_output(export_cmd).decode())
558
697
  env_check["mtime"] = os.path.getmtime(
559
698
  os.path.normpath(env_check["prefix"])
560
699
  )
700
+ if pip_freeze:
701
+ check_pip = _get_pip_dependency_list(env_check["dependencies"])
702
+ enriched = _enrich_pip_deps_from_freeze(check_pip, pip_freeze)
703
+ for dep_entry in env_check["dependencies"]:
704
+ if isinstance(dep_entry, dict) and "pip" in dep_entry:
705
+ dep_entry["pip"] = enriched
706
+ break
561
707
  with open(env_check_fpath, "w") as f:
562
708
  ryaml.dump(env_check, f)
563
709
  if lock_fpath is None:
@@ -602,8 +748,13 @@ def check_env(
602
748
  editable_pip_deps[pkg_name] = dir_path
603
749
  export_pip_deps = _get_pip_dependency_list(env_export["dependencies"])
604
750
  if export_pip_deps:
751
+ # Enrich with pip freeze to preserve git URLs, then fix editable paths
752
+ if pip_freeze:
753
+ export_pip_deps = _enrich_pip_deps_from_freeze(
754
+ export_pip_deps, pip_freeze
755
+ )
605
756
  for i, dep in enumerate(export_pip_deps):
606
- dep_name = re.split("[=<>]+", dep, maxsplit=1)[0]
757
+ dep_name = _pkg_name_from_dep(dep)
607
758
  if dep_name in editable_pip_deps:
608
759
  path_rel_to_project_root = editable_pip_deps[dep_name]
609
760
  lock_dir = os.path.dirname(lock_fpath)
@@ -613,9 +764,30 @@ def check_env(
613
764
  export_pip_deps[i] = (
614
765
  "-e " + Path(path_rel_to_lock).as_posix()
615
766
  )
767
+ # Write the modified list back (enrichment returns a new list)
768
+ for dep_entry in env_export["dependencies"]:
769
+ if isinstance(dep_entry, dict) and "pip" in dep_entry:
770
+ dep_entry["pip"] = export_pip_deps
771
+ break
616
772
  out_dir = os.path.dirname(lock_fpath)
617
773
  if out_dir:
618
774
  os.makedirs(out_dir, exist_ok=True)
619
775
  with open(lock_fpath, "w") as f:
620
776
  ryaml.dump(env_export, f)
777
+ elif pip_freeze and spec_has_git_pip and os.path.isfile(lock_fpath):
778
+ # The env matched the spec so no full re-export was done, but the
779
+ # existing lock file may pre-date pip freeze enrichment. Update its
780
+ # pip section in place — no conda env export needed.
781
+ with open(lock_fpath) as f:
782
+ lock_data = ryaml.load(f) or {}
783
+ lock_pip = _get_pip_dependency_list(lock_data.get("dependencies", []))
784
+ enriched = _enrich_pip_deps_from_freeze(lock_pip, pip_freeze)
785
+ if enriched != lock_pip:
786
+ log_func("Enriching existing lock file pip section with git URLs")
787
+ for dep_entry in lock_data.get("dependencies", []):
788
+ if isinstance(dep_entry, dict) and "pip" in dep_entry:
789
+ dep_entry["pip"] = enriched
790
+ break
791
+ with open(lock_fpath, "w") as f:
792
+ ryaml.dump(lock_data, f)
621
793
  return res
@@ -324,6 +324,60 @@ def _paths_overlap(a: str, b: str) -> bool:
324
324
  return b.startswith(a + "/") or a.startswith(b + "/")
325
325
 
326
326
 
327
+ def _expand_dep_excluding_subprojects(
328
+ dep: str,
329
+ isolated_sp_paths: list[str],
330
+ root: str | None = None,
331
+ ) -> list[str]:
332
+ """Expand a directory dep to avoid traversing into isolated subprojects.
333
+
334
+ DVC cannot collect a directory dep that contains a nested DVC repo (i.e.
335
+ an isolated subproject identified by a ``.dvc/`` directory). This function
336
+ walks ``dep``, listing its contents and skipping any isolated subproject
337
+ directory, so the returned paths collectively cover the same files without
338
+ crossing into the subprojects.
339
+
340
+ ``root`` is the directory relative to which ``dep`` and ``isolated_sp_paths``
341
+ are expressed (defaults to cwd).
342
+ """
343
+ root_path = Path(root) if root else Path(".")
344
+ dep_path = root_path / dep
345
+ # Resolve once for reliable path comparison
346
+ dep_resolved = dep_path.resolve()
347
+ sp_set = {(root_path / sp).resolve() for sp in isolated_sp_paths}
348
+ nested = [
349
+ sp
350
+ for sp in sp_set
351
+ if sp != dep_resolved and sp.is_relative_to(dep_resolved)
352
+ ]
353
+ if not nested or not dep_path.is_dir():
354
+ return [dep]
355
+ result: list[str] = []
356
+
357
+ def _walk(dir_path: Path) -> None:
358
+ try:
359
+ children = sorted(dir_path.iterdir())
360
+ except PermissionError:
361
+ return
362
+ for child in children:
363
+ child_resolved = child.resolve()
364
+ if child_resolved in sp_set:
365
+ # Skip the isolated subproject entirely
366
+ continue
367
+ if child.is_dir() and any(
368
+ sp.is_relative_to(child_resolved) for sp in nested
369
+ ):
370
+ # This directory is an ancestor of an isolated SP; descend
371
+ _walk(child)
372
+ else:
373
+ rel = child.relative_to(root_path)
374
+ suffix = "/" if child.is_dir() else ""
375
+ result.append(rel.as_posix() + suffix)
376
+
377
+ _walk(dep_path)
378
+ return result if result else [dep]
379
+
380
+
327
381
  def collapse_dvc_stages(
328
382
  stages: dict,
329
383
  cmd: str | None = None,
@@ -728,6 +782,7 @@ def to_dvc(
728
782
  # For inline subprojects (no .dvc/ dir), DVC discovers them automatically
729
783
  # via --all-pipelines so no wrapper stage is needed.
730
784
  wrapper_stages: dict[str, dict] = {}
785
+ isolated_sp_paths: list[str] = []
731
786
  for subproject in ck_info.get("subprojects", []):
732
787
  if not isinstance(subproject, dict) or not subproject.get("path"):
733
788
  raise ValueError("Subprojects must have a 'path' defined")
@@ -746,6 +801,7 @@ def to_dvc(
746
801
  )
747
802
  if not sp_is_isolated:
748
803
  continue
804
+ isolated_sp_paths.append(sp)
749
805
  if not sp_dvc_stages:
750
806
  import warnings
751
807
 
@@ -915,6 +971,17 @@ def to_dvc(
915
971
  dvc_stage["deps"] = formatted_deps
916
972
  dvc_stage["outs"] = formatted_outs
917
973
  dvc_stage["matrix"] = dvc_matrix
974
+ # Expand any directory deps that contain isolated subprojects so DVC
975
+ # doesn't try to traverse into their .dvcignore files
976
+ if isolated_sp_paths:
977
+ expanded_deps = []
978
+ for dep in dvc_stage.get("deps", []):
979
+ expanded_deps.extend(
980
+ _expand_dep_excluding_subprojects(
981
+ dep, isolated_sp_paths, root=wdir
982
+ )
983
+ )
984
+ dvc_stage["deps"] = expanded_deps
918
985
  # Add a description to the DVC stage
919
986
  desc = (
920
987
  f"Automatically generated from the '{stage_name}' stage "
@@ -10,6 +10,7 @@ import calkit
10
10
  from calkit.conda import (
11
11
  _check_list,
12
12
  _check_single,
13
+ _enrich_pip_deps_from_freeze,
13
14
  _get_pip_dependency_list,
14
15
  _split_env_dependencies,
15
16
  check_env,
@@ -31,6 +32,64 @@ def test_check_single():
31
32
  assert _check_single(
32
33
  "python>=3.12,<3.13", "python==3.12.18", env_spec_dir=".", conda=False
33
34
  )
35
+ # PEP 508 git direct reference in req matches plain installed version
36
+ assert _check_single(
37
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
38
+ "pyxdsm==0.4.0",
39
+ env_spec_dir=".",
40
+ conda=False,
41
+ )
42
+ # Different package name must not match
43
+ assert not _check_single(
44
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
45
+ "other==1.0",
46
+ env_spec_dir=".",
47
+ conda=False,
48
+ )
49
+ # Both sides git: same ref → match
50
+ assert _check_single(
51
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
52
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
53
+ env_spec_dir=".",
54
+ conda=False,
55
+ )
56
+ # Both sides git: short spec ref matches long installed ref (prefix)
57
+ assert _check_single(
58
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
59
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b07ca1234",
60
+ env_spec_dir=".",
61
+ conda=False,
62
+ )
63
+ # Both sides git: different refs → no match
64
+ assert not _check_single(
65
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
66
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@aabbcc1",
67
+ env_spec_dir=".",
68
+ conda=False,
69
+ )
70
+
71
+
72
+ def test_enrich_pip_deps_from_freeze():
73
+ pip_freeze = [
74
+ "numpy==1.24.3",
75
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b07ca",
76
+ "scipy==1.11.0",
77
+ ]
78
+ spec_deps = [
79
+ "numpy==1.24.3",
80
+ "pyxdsm @ git+https://github.com/rebeccamccabe/pyXDSM.git@fc0b49b",
81
+ "scipy",
82
+ ]
83
+ result = _enrich_pip_deps_from_freeze(spec_deps, pip_freeze)
84
+ # numpy and scipy get the exact freeze version
85
+ assert "numpy==1.24.3" in result
86
+ assert "scipy==1.11.0" in result
87
+ # pyxdsm gets the full git URL from freeze
88
+ assert any("fc0b49b07ca" in r for r in result)
89
+ # Editable installs are kept unchanged
90
+ editable = ["-e ../mypackage", "numpy==1.24.3"]
91
+ result2 = _enrich_pip_deps_from_freeze(editable, pip_freeze)
92
+ assert result2[0] == "-e ../mypackage"
34
93
 
35
94
 
36
95
  def test_check_list():
@@ -9,7 +9,11 @@ import pytest
9
9
  import calkit
10
10
  import calkit.pipeline
11
11
  from calkit.environments import get_env_lock_fpath
12
- from calkit.pipeline import collapse_dvc_stages, stages_are_similar
12
+ from calkit.pipeline import (
13
+ _expand_dep_excluding_subprojects,
14
+ collapse_dvc_stages,
15
+ stages_are_similar,
16
+ )
13
17
 
14
18
 
15
19
  def test_stages_are_similar():
@@ -1160,6 +1164,28 @@ def test_wrapper_stage_no_dep_out_overlap(tmp_dir):
1160
1164
  ), f"wrapper dep '{dep}' tree-overlaps out '{out}'"
1161
1165
 
1162
1166
 
1167
+ def test_expand_dep_excluding_subprojects(tmp_dir):
1168
+ # Directory dep not containing any isolated subproject returns as-is
1169
+ os.makedirs("src")
1170
+ open("src/a.py", "w").close()
1171
+ result = _expand_dep_excluding_subprojects("src", [])
1172
+ assert result == ["src"]
1173
+ # Directory dep containing an isolated subproject is expanded to siblings
1174
+ os.makedirs("sim/modules/SubProj/.dvc")
1175
+ open("sim/modules/other.py", "w").close()
1176
+ open("sim/run.py", "w").close()
1177
+ result = _expand_dep_excluding_subprojects("sim", ["sim/modules/SubProj"])
1178
+ assert "sim/run.py" in result
1179
+ assert "sim/modules/other.py" in result
1180
+ # The isolated subproject itself must not appear in the expansion
1181
+ assert not any("SubProj" in r for r in result)
1182
+ # A dep that is not a directory returns as-is
1183
+ result = _expand_dep_excluding_subprojects(
1184
+ "sim/run.py", ["sim/modules/SubProj"]
1185
+ )
1186
+ assert result == ["sim/run.py"]
1187
+
1188
+
1163
1189
  def test_translate_run_targets(tmp_dir):
1164
1190
  # Set up a parent project with one inline subproject and one isolated.
1165
1191
  subprocess.check_call(["git", "init"])
File without changes
File without changes
File without changes
File without changes