calkit-python 0.35.4__tar.gz → 0.35.5__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 (285) hide show
  1. {calkit_python-0.35.4 → calkit_python-0.35.5}/Makefile +7 -2
  2. {calkit_python-0.35.4 → calkit_python-0.35.5}/PKG-INFO +1 -1
  3. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/conda.py +78 -28
  4. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/overleaf.py +32 -1
  5. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/pipeline.py +37 -8
  6. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_overleaf.py +38 -0
  7. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_conda.py +120 -2
  8. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_pipeline.py +47 -0
  9. calkit_python-0.35.5/docs/cli-reference.md +2586 -0
  10. calkit_python-0.35.5/scripts/generate-cli-reference.py +257 -0
  11. calkit_python-0.35.4/docs/cli-reference.md +0 -3
  12. {calkit_python-0.35.4 → calkit_python-0.35.5}/.gitignore +0 -0
  13. {calkit_python-0.35.4 → calkit_python-0.35.5}/.pre-commit-config.yaml +0 -0
  14. {calkit_python-0.35.4 → calkit_python-0.35.5}/.prettierignore +0 -0
  15. {calkit_python-0.35.4 → calkit_python-0.35.5}/.python-version +0 -0
  16. {calkit_python-0.35.4 → calkit_python-0.35.5}/.yarnrc.yml +0 -0
  17. {calkit_python-0.35.4 → calkit_python-0.35.5}/CITATION.cff +0 -0
  18. {calkit_python-0.35.4 → calkit_python-0.35.5}/CODE_OF_CONDUCT.md +0 -0
  19. {calkit_python-0.35.4 → calkit_python-0.35.5}/CONTRIBUTING.md +0 -0
  20. {calkit_python-0.35.4 → calkit_python-0.35.5}/LICENSE +0 -0
  21. {calkit_python-0.35.4 → calkit_python-0.35.5}/README.md +0 -0
  22. {calkit_python-0.35.4 → calkit_python-0.35.5}/babel.config.js +0 -0
  23. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/__init__.py +0 -0
  24. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/__main__.py +0 -0
  25. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/calc.py +0 -0
  26. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/check.py +0 -0
  27. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/__init__.py +0 -0
  28. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/check.py +0 -0
  29. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/cloud.py +0 -0
  30. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/config.py +0 -0
  31. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/core.py +0 -0
  32. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/describe.py +0 -0
  33. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/import_.py +0 -0
  34. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/latex.py +0 -0
  35. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/list.py +0 -0
  36. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/main/__init__.py +0 -0
  37. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/main/core.py +0 -0
  38. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/main/xr.py +0 -0
  39. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/new.py +0 -0
  40. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/notebooks.py +0 -0
  41. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/office.py +0 -0
  42. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/overleaf.py +0 -0
  43. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/slurm.py +0 -0
  44. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cli/update.py +0 -0
  45. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/cloud.py +0 -0
  46. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/config.py +0 -0
  47. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/core.py +0 -0
  48. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/datasets.py +0 -0
  49. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/detect.py +0 -0
  50. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/docker.py +0 -0
  51. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/dvc.py +0 -0
  52. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/environments.py +0 -0
  53. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/fs.py +0 -0
  54. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/git.py +0 -0
  55. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/github.py +0 -0
  56. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/gui.py +0 -0
  57. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/invenio.py +0 -0
  58. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/julia.py +0 -0
  59. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/jupyter.py +0 -0
  60. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/jupyterlab/__init__.py +0 -0
  61. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/jupyterlab/routes.py +0 -0
  62. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/package.json +0 -0
  63. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  64. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  65. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  66. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  67. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  68. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/909.651be47ca47390b78a92.js +0 -0
  69. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  70. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  71. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  72. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/remoteEntry.c091821b3d7f2d287a67.js +0 -0
  73. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/style.js +0 -0
  74. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/labextension/static/third-party-licenses.json +0 -0
  75. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/licenses.py +0 -0
  76. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/magics.py +0 -0
  77. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/matlab.py +0 -0
  78. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/models/__init__.py +0 -0
  79. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/models/core.py +0 -0
  80. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/models/io.py +0 -0
  81. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/models/iteration.py +0 -0
  82. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/models/pipeline.py +0 -0
  83. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/notebooks.py +0 -0
  84. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/office.py +0 -0
  85. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/ops.py +0 -0
  86. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/releases.py +0 -0
  87. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/server.py +0 -0
  88. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/__init__.py +0 -0
  89. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/core.py +0 -0
  90. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/__init__.py +0 -0
  91. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/article/paper.tex +0 -0
  92. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/core.py +0 -0
  93. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/jfm/jfm.bst +0 -0
  94. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/jfm/jfm.cls +0 -0
  95. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  96. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/jfm/paper.tex +0 -0
  97. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/templates/latex/jfm/upmath.sty +0 -0
  98. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/__init__.py +0 -0
  99. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/__init__.py +0 -0
  100. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/main/__init__.py +0 -0
  101. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/main/test_core.py +0 -0
  102. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/main/test_xr.py +0 -0
  103. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_check.py +0 -0
  104. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_config.py +0 -0
  105. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_import.py +0 -0
  106. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_latex.py +0 -0
  107. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_list.py +0 -0
  108. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_new.py +0 -0
  109. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/cli/test_notebooks.py +0 -0
  110. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/jupyterlab/__init__.py +0 -0
  111. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/jupyterlab/test_routes.py +0 -0
  112. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/models/__init__.py +0 -0
  113. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/models/test_iteration.py +0 -0
  114. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/models/test_pipeline.py +0 -0
  115. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_calc.py +0 -0
  116. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_check.py +0 -0
  117. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_core.py +0 -0
  118. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_detect.py +0 -0
  119. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_docker.py +0 -0
  120. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_dvc.py +0 -0
  121. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_environments.py +0 -0
  122. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_fs.py +0 -0
  123. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_git.py +0 -0
  124. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_invenio.py +0 -0
  125. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_julia.py +0 -0
  126. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_jupyter.py +0 -0
  127. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_magics.py +0 -0
  128. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_matlab.py +0 -0
  129. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_notebooks.py +0 -0
  130. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_releases.py +0 -0
  131. {calkit_python-0.35.4 → calkit_python-0.35.5}/calkit/tests/test_templates.py +0 -0
  132. {calkit_python-0.35.4 → calkit_python-0.35.5}/conftest.py +0 -0
  133. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/CNAME +0 -0
  134. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/apps.md +0 -0
  135. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/calculations.md +0 -0
  136. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/calkit-yaml.md +0 -0
  137. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/cloud-integration.md +0 -0
  138. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/datasets.md +0 -0
  139. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/dependencies.md +0 -0
  140. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/environments.md +0 -0
  141. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/examples.md +0 -0
  142. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/governance.md +0 -0
  143. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/help.md +0 -0
  144. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/c-to-the-k-white.svg +0 -0
  145. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/calkit-fragmentation-compendium.png +0 -0
  146. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/calkit-no-bg.png +0 -0
  147. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/connect-zenodo.png +0 -0
  148. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/jupyterlab/all-green.png +0 -0
  149. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  150. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/jupyterlab/new-env.png +0 -0
  151. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/jupyterlab/new-notebook.png +0 -0
  152. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  153. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/jupyterlab-params.png +0 -0
  154. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/plos-osi-code-2024-03.png +0 -0
  155. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/img/vscode-nb-params.png +0 -0
  156. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/index.md +0 -0
  157. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/installation.md +0 -0
  158. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/jupyterlab.md +0 -0
  159. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/local-server.md +0 -0
  160. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/notebooks.md +0 -0
  161. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/overleaf.md +0 -0
  162. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/pipeline/index.md +0 -0
  163. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/pipeline/manual-steps.md +0 -0
  164. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/pipeline/running-and-logging.md +0 -0
  165. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/pipeline/slurm.md +0 -0
  166. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/quickstart.md +0 -0
  167. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/references.md +0 -0
  168. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/releases.md +0 -0
  169. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/reproducibility.md +0 -0
  170. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  171. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/conda-envs.md +0 -0
  172. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/existing-project.md +0 -0
  173. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/first-project.md +0 -0
  174. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/github-actions.md +0 -0
  175. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  176. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  177. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  178. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  179. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  180. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  181. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  182. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  183. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  184. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  185. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  186. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  187. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  188. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  189. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  190. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/create-project.png +0 -0
  191. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  192. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/excel-chart.png +0 -0
  193. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/excel-data.png +0 -0
  194. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  195. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/needs-clone.png +0 -0
  196. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/new-stage.png +0 -0
  197. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  198. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  199. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/status-more-rows.png +0 -0
  200. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  201. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/untracked-data.png +0 -0
  202. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/updated-publication.png +0 -0
  203. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  204. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/office/workflow-page.png +0 -0
  205. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/clone.png +0 -0
  206. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/create-project.png +0 -0
  207. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  208. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  209. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  210. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/new-token.png +0 -0
  211. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/reclone.png +0 -0
  212. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  213. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/quick-actions.png +0 -0
  214. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/img/run-proc.png +0 -0
  215. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/index.md +0 -0
  216. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/jupyterlab.md +0 -0
  217. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/latex-codespaces.md +0 -0
  218. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/matlab.md +0 -0
  219. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/notebook-pipeline.md +0 -0
  220. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/office.md +0 -0
  221. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/openfoam.md +0 -0
  222. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/tutorials/procedures.md +0 -0
  223. {calkit_python-0.35.4 → calkit_python-0.35.5}/docs/version-control.md +0 -0
  224. {calkit_python-0.35.4 → calkit_python-0.35.5}/install.json +0 -0
  225. {calkit_python-0.35.4 → calkit_python-0.35.5}/jest.config.js +0 -0
  226. {calkit_python-0.35.4 → calkit_python-0.35.5}/jupyter-config/server-config/calkit.json +0 -0
  227. {calkit_python-0.35.4 → calkit_python-0.35.5}/mkdocs.yml +0 -0
  228. {calkit_python-0.35.4 → calkit_python-0.35.5}/package.json +0 -0
  229. {calkit_python-0.35.4 → calkit_python-0.35.5}/pyproject.toml +0 -0
  230. {calkit_python-0.35.4 → calkit_python-0.35.5}/schema/plugin.json +0 -0
  231. {calkit_python-0.35.4 → calkit_python-0.35.5}/scripts/install.ps1 +0 -0
  232. {calkit_python-0.35.4 → calkit_python-0.35.5}/scripts/install.sh +0 -0
  233. {calkit_python-0.35.4 → calkit_python-0.35.5}/scripts/make-calk9.sh +0 -0
  234. {calkit_python-0.35.4 → calkit_python-0.35.5}/scripts/sync-docs.py +0 -0
  235. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/__tests__/useQueries.spec.ts +0 -0
  236. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/calkit-config.ts +0 -0
  237. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/cell-output-marker.ts +0 -0
  238. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/commit-dialog.tsx +0 -0
  239. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/environment-editor.tsx +0 -0
  240. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/notebook-registration.tsx +0 -0
  241. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/notebook-toolbar.tsx +0 -0
  242. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/pipeline-status-bar.tsx +0 -0
  243. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/project-info-editor.tsx +0 -0
  244. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/sidebar-settings.tsx +0 -0
  245. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/sidebar.tsx +0 -0
  246. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/components/stage-editor.tsx +0 -0
  247. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/feature-flags.ts +0 -0
  248. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/file-browser-menu.ts +0 -0
  249. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  250. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/hooks/useQueries.ts +0 -0
  251. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/icons.ts +0 -0
  252. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/index.ts +0 -0
  253. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/io-tracker.ts +0 -0
  254. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/pipeline-state.ts +0 -0
  255. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/queryClient.ts +0 -0
  256. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/request.ts +0 -0
  257. {calkit_python-0.35.4 → calkit_python-0.35.5}/src/shims-mainmenu.d.ts +0 -0
  258. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/base.css +0 -0
  259. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/cell-output-marker.css +0 -0
  260. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/environment-editor.css +0 -0
  261. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/environment-selector.css +0 -0
  262. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/img/calkit-no-bg.png +0 -0
  263. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/index.css +0 -0
  264. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/index.js +0 -0
  265. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/notebook-toolbar.css +0 -0
  266. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/pipeline-status-bar.css +0 -0
  267. {calkit_python-0.35.4 → calkit_python-0.35.5}/style/sidebar.css +0 -0
  268. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  269. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/nb-julia.ipynb +0 -0
  270. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/nb-params.ipynb +0 -0
  271. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/nb-subdir.ipynb +0 -0
  272. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/pipeline.ipynb +0 -0
  273. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/script.py +0 -0
  274. {calkit_python-0.35.4 → calkit_python-0.35.5}/test/test-log.log +0 -0
  275. {calkit_python-0.35.4 → calkit_python-0.35.5}/tsconfig.json +0 -0
  276. {calkit_python-0.35.4 → calkit_python-0.35.5}/tsconfig.test.json +0 -0
  277. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/.gitignore +0 -0
  278. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/README.md +0 -0
  279. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/jupyter_server_test_config.py +0 -0
  280. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/package.json +0 -0
  281. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/playwright.config.js +0 -0
  282. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/tests/calkit.spec.ts +0 -0
  283. {calkit_python-0.35.4 → calkit_python-0.35.5}/ui-tests/yarn.lock +0 -0
  284. {calkit_python-0.35.4 → calkit_python-0.35.5}/uv.lock +0 -0
  285. {calkit_python-0.35.4 → calkit_python-0.35.5}/yarn.lock +0 -0
@@ -37,12 +37,17 @@ test-docs: sync-docs ## Test if documentation can be built without warnings or e
37
37
  @uv run mkdocs build -s
38
38
 
39
39
  .PHONY: sync-docs
40
- sync-docs: ## Sync documentation content from docs/*.md into README.md.
40
+ sync-docs: cli-reference ## Sync documentation content from docs/*.md into README.md.
41
41
  @echo "🚀 Syncing documentation"
42
42
  @uv run python scripts/sync-docs.py
43
43
 
44
+ .PHONY: cli-reference
45
+ cli-reference: ## Generate docs/cli-reference.md from CLI help output.
46
+ @echo "🚀 Generating CLI reference"
47
+ @uv run python scripts/generate-cli-reference.py
48
+
44
49
  .PHONY: docs
45
- docs: ## Build and serve the documentation.
50
+ docs: sync-docs ## Build and serve the documentation.
46
51
  @uv run mkdocs serve --livereload
47
52
 
48
53
  .PHONY: import-profile
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.35.4
3
+ Version: 0.35.5
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -9,6 +9,7 @@ import shutil
9
9
  import subprocess
10
10
  import warnings
11
11
  from pathlib import Path
12
+ from typing import cast
12
13
 
13
14
  import toml
14
15
  from packaging.specifiers import SpecifierSet
@@ -123,7 +124,13 @@ def _editable_package_name_from_dir(dir_path: str) -> str:
123
124
  elif os.path.isfile(os.path.join(dir_path, "pyproject.toml")):
124
125
  # Read pyproject.toml to get the package name
125
126
  with open(os.path.join(dir_path, "pyproject.toml")) as f:
126
- pyproject = toml.load(f)
127
+ try:
128
+ pyproject = toml.load(f)
129
+ except Exception as e:
130
+ raise type(e)(
131
+ f"Failed to load pyproject.toml from {dir_path}; "
132
+ "check that it is valid TOML"
133
+ ) from e
127
134
  if "project" in pyproject:
128
135
  if "name" in pyproject["project"]:
129
136
  return pyproject["project"]["name"]
@@ -139,6 +146,7 @@ def _check_single(
139
146
  """
140
147
  # If this is an editable install it needs to be handled specially
141
148
  # It also needs to be relative to the env spec dir
149
+ editable = False
142
150
  if req.startswith("-e ") or req.startswith("--editable "):
143
151
  req = req.split(" ", 1)[1]
144
152
  if "#" in req:
@@ -147,6 +155,7 @@ def _check_single(
147
155
  # Create path relative to env spec dir
148
156
  req = os.path.join(env_spec_dir, req)
149
157
  req = _editable_package_name_from_dir(req)
158
+ editable = True
150
159
  # If this is a Git version, we can't check it
151
160
  # TODO: Clone Git repos to check?
152
161
  if "@git" in req:
@@ -182,7 +191,7 @@ def _check_single(
182
191
  # TODO: Check exact version only
183
192
  return True
184
193
  spec = SpecifierSet(req_spec)
185
- return spec.contains(version)
194
+ return spec.contains(version, prereleases=editable)
186
195
 
187
196
 
188
197
  def _check_list(
@@ -193,6 +202,10 @@ def _check_list(
193
202
  if "::" in req:
194
203
  req = req.split("::", 1)[1]
195
204
  for installed in actual:
205
+ if not isinstance(installed, str):
206
+ raise ValueError(
207
+ f"Expected installed package to be a string, got {installed}"
208
+ )
196
209
  if _check_single(
197
210
  req, installed, env_spec_dir=env_spec_dir, conda=conda
198
211
  ):
@@ -200,6 +213,46 @@ def _check_list(
200
213
  return False
201
214
 
202
215
 
216
+ def _split_env_dependencies(
217
+ dependencies: list[str | dict[str, str | list[str]]],
218
+ ) -> tuple[list[str], list[str]]:
219
+ """Split an environment dependency list into conda and pip deps.
220
+
221
+ Conda environment files commonly include both the plain ``"pip"`` package
222
+ marker and a nested ``{"pip": [...]}`` section. This helper normalizes the
223
+ latter so callers do not need to assume it is the final list entry or that
224
+ the pip section is already represented as a list.
225
+ """
226
+ conda_deps = []
227
+ pip_deps = []
228
+ for dep in dependencies:
229
+ if isinstance(dep, dict):
230
+ dep_pip = dep.get("pip", [])
231
+ if isinstance(dep_pip, str):
232
+ dep_pip = [dep_pip]
233
+ elif dep_pip is None:
234
+ dep_pip = []
235
+ pip_deps.extend(dep_pip)
236
+ else:
237
+ conda_deps.append(dep)
238
+ return conda_deps, pip_deps
239
+
240
+
241
+ def _get_pip_dependency_list(
242
+ dependencies: list[str | dict[str, str | list[str]]],
243
+ ) -> list[str]:
244
+ """Return a mutable pip dependency list from an env dependency list."""
245
+ for dep in dependencies:
246
+ if isinstance(dep, dict) and "pip" in dep:
247
+ dep_pip = dep["pip"]
248
+ if isinstance(dep_pip, str):
249
+ dep["pip"] = [dep_pip]
250
+ elif dep_pip is None:
251
+ dep["pip"] = []
252
+ return cast(list[str], dep["pip"])
253
+ return []
254
+
255
+
203
256
  class EnvCheckResult(BaseModel):
204
257
  env_exists: bool | None = None
205
258
  env_needs_export: bool | None = None
@@ -393,18 +446,12 @@ def check_env(
393
446
  ryaml.dump(env_check, f)
394
447
  # Determine if the env matches
395
448
  env_needs_rebuild = False
396
- if isinstance(env_check["dependencies"][-1], dict):
397
- existing_conda_deps = env_check["dependencies"][:-1]
398
- existing_pip_deps = env_check["dependencies"][-1]["pip"]
399
- else:
400
- existing_conda_deps = env_check["dependencies"]
401
- existing_pip_deps = []
402
- if isinstance(env_spec["dependencies"][-1], dict):
403
- required_conda_deps = env_spec["dependencies"][:-1]
404
- required_pip_deps = env_spec["dependencies"][-1]["pip"]
405
- else:
406
- required_conda_deps = env_spec["dependencies"]
407
- required_pip_deps = []
449
+ existing_conda_deps, existing_pip_deps = _split_env_dependencies(
450
+ env_check["dependencies"]
451
+ )
452
+ required_conda_deps, required_pip_deps = _split_env_dependencies(
453
+ env_spec["dependencies"]
454
+ )
408
455
  if relaxed:
409
456
  log_func("Running in relaxed mode; combining pip and conda deps")
410
457
  for dep in existing_pip_deps:
@@ -516,20 +563,23 @@ def check_env(
516
563
  # Note that this needs to be relative to the env lock directory,
517
564
  # since that's how pip will interpret it
518
565
  editable_pip_deps = {}
519
- if isinstance(env_spec["dependencies"][-1], dict):
520
- # Map editable install dir to package name we'd see in lock
521
- required_pip_deps = env_spec["dependencies"][-1]["pip"]
522
- for dep in required_pip_deps:
523
- if dep.startswith("-e ") or dep.startswith("--editable "):
524
- dir_path = dep.split(" ", 1)[1]
525
- if "#" in dir_path:
526
- dir_path = dir_path.split("#", 1)[0]
527
- dir_path = dir_path.strip()
528
- dir_path = os.path.join(env_spec_dir, dir_path)
529
- pkg_name = _editable_package_name_from_dir(dir_path)
530
- editable_pip_deps[pkg_name] = dir_path
531
- if isinstance(env_export["dependencies"][-1], dict):
532
- export_pip_deps = env_export["dependencies"][-1]["pip"]
566
+ required_pip_deps = _get_pip_dependency_list(env_spec["dependencies"])
567
+ for dep in required_pip_deps:
568
+ if dep.startswith("-e ") or dep.startswith("--editable "):
569
+ dir_path = dep.split(" ", 1)[1]
570
+ if "#" in dir_path:
571
+ dir_path = dir_path.split("#", 1)[0]
572
+ dir_path = dir_path.strip()
573
+ dir_path = os.path.join(env_spec_dir, dir_path)
574
+ pkg_name = _editable_package_name_from_dir(dir_path)
575
+ if verbose:
576
+ log_func(
577
+ f"Found editable pip dependency '{pkg_name}' "
578
+ f"at '{dir_path}'"
579
+ )
580
+ editable_pip_deps[pkg_name] = dir_path
581
+ export_pip_deps = _get_pip_dependency_list(env_export["dependencies"])
582
+ if export_pip_deps:
533
583
  for i, dep in enumerate(export_pip_deps):
534
584
  dep_name = re.split("[=<>]+", dep, maxsplit=1)[0]
535
585
  if dep_name in editable_pip_deps:
@@ -317,6 +317,32 @@ class OverleafSyncPaths:
317
317
  - self.files_in_overleaf_last_sync
318
318
  )
319
319
 
320
+ @cached_property
321
+ def dvc_files(self) -> set[str]:
322
+ """Files tracked by DVC within the Overleaf project folder.
323
+
324
+ These paths are relative to the project directory (i.e., relative to
325
+ the Overleaf repo root). Files tracked by DVC may not exist on disk if
326
+ they haven't been pulled, but should still be kept on Overleaf rather
327
+ than deleted.
328
+ """
329
+ try:
330
+ import calkit.dvc
331
+
332
+ dvc_paths = calkit.dvc.list_paths(
333
+ wdir=str(self.main_repo.working_dir), recursive=True
334
+ )
335
+ except Exception as e:
336
+ warnings.warn(f"Could not list DVC files: {e}")
337
+ return set()
338
+ prefix = Path(self.path_in_project).as_posix().rstrip("/") + "/"
339
+ result = set()
340
+ for p in dvc_paths:
341
+ p_posix = Path(p).as_posix()
342
+ if p_posix.startswith(prefix):
343
+ result.add(p_posix[len(prefix) :])
344
+ return result
345
+
320
346
  @cached_property
321
347
  def files_to_keep_on_overleaf(self) -> set[str]:
322
348
  """Files that should be preserved on Overleaf.
@@ -324,9 +350,14 @@ class OverleafSyncPaths:
324
350
  This includes:
325
351
  1. All files being copied from local
326
352
  2. Any files newly added on Overleaf since last sync
353
+ 3. Any files tracked by DVC within the project path (these may not
354
+ exist on disk if not pulled, but should not be deleted from
355
+ Overleaf)
327
356
  """
328
357
  return (
329
- set(self.files_to_copy_to_overleaf) | self.newly_added_on_overleaf
358
+ set(self.files_to_copy_to_overleaf)
359
+ | self.newly_added_on_overleaf
360
+ | self.dvc_files
330
361
  )
331
362
 
332
363
  @cached_property
@@ -117,6 +117,20 @@ def to_dvc(
117
117
  except Exception as e:
118
118
  raise ValueError(f"Pipeline is not defined properly: {e}")
119
119
  dvc_stages = {}
120
+ # Read existing dvc.yaml now so we can clean up stale .gitignore entries
121
+ # when stage outputs are renamed or removed
122
+ if write:
123
+ dvc_yaml_path = os.path.join(wdir, "dvc.yaml") if wdir else "dvc.yaml"
124
+ if os.path.isfile(dvc_yaml_path):
125
+ with open(dvc_yaml_path) as f:
126
+ existing_dvc_yaml = calkit.ryaml.load(f)
127
+ else:
128
+ existing_dvc_yaml = {}
129
+ if existing_dvc_yaml is None:
130
+ existing_dvc_yaml = {}
131
+ existing_dvc_stages = existing_dvc_yaml.get("stages", {})
132
+ else:
133
+ existing_dvc_stages = {}
120
134
  # First, gather up any env lock paths we might need for DVC deps
121
135
  used_envs = set(
122
136
  [stage.inner_environment for stage in pipeline.stages.values()]
@@ -227,6 +241,27 @@ def to_dvc(
227
241
  outputs += stage.notebook_outputs
228
242
  elif stage.kind == "sbatch":
229
243
  outputs.append(stage.log_output)
244
+ # Build the set of current output paths so we can detect stale
245
+ # .gitignore entries from the previous version of the stage
246
+ current_out_paths = set()
247
+ for out in outputs:
248
+ if isinstance(out, PathOutput):
249
+ current_out_paths.add(out.path)
250
+ elif isinstance(out, str):
251
+ current_out_paths.add(out)
252
+ # If this stage already existed, un-ignore any outputs that have
253
+ # been renamed or removed so .gitignore does not accumulate stale
254
+ # entries (e.g., after a capitalization change in the path)
255
+ old_stage = existing_dvc_stages.get(stage_name, {})
256
+ for old_out in old_stage.get("outs", []):
257
+ if isinstance(old_out, str):
258
+ old_path = old_out
259
+ elif isinstance(old_out, dict):
260
+ old_path = list(old_out.keys())[0]
261
+ else:
262
+ continue
263
+ if old_path not in current_out_paths:
264
+ calkit.git.ensure_path_is_not_ignored(repo, path=old_path)
230
265
  # Deal with any gitignore changes necessary
231
266
  for out in outputs:
232
267
  if isinstance(out, PathOutput) and out.storage is None:
@@ -268,14 +303,8 @@ def to_dvc(
268
303
  else:
269
304
  dvc_stages[stage_name]["deps"].append(out)
270
305
  if write:
271
- if os.path.isfile("dvc.yaml"):
272
- with open("dvc.yaml") as f:
273
- dvc_yaml = calkit.ryaml.load(f)
274
- else:
275
- dvc_yaml = {}
276
- if dvc_yaml is None:
277
- dvc_yaml = {}
278
- existing_stages = dvc_yaml.get("stages", {})
306
+ dvc_yaml = existing_dvc_yaml
307
+ existing_stages = existing_dvc_stages
279
308
  for stage_name, stage in existing_stages.items():
280
309
  # Skip private stages (ones whose names start with an underscore)
281
310
  # and stages that are automatically generated
@@ -181,6 +181,44 @@ def test_overleaf(tmp_dir):
181
181
  subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
182
182
  print("Overleaf Git show after adding fig2 back:", ol_repo.git.show())
183
183
  assert "ol-project/figs/fig2.txt" in ls_files(repo)
184
+ # Test that if a file is deleted from Git but added to DVC, it is not
185
+ # deleted from Overleaf (the file still logically exists in the DVC repo)
186
+ with open(
187
+ os.path.join(repo.working_dir, "ol-project", "figs", "fig3.txt"), "w"
188
+ ) as f:
189
+ f.write("Fig3 created in main repo")
190
+ repo.git.add("ol-project/figs/fig3.txt")
191
+ repo.git.commit(["-m", "Add figure 3"])
192
+ assert "ol-project/figs/fig3.txt" in ls_files(repo)
193
+ subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
194
+ ol_repo_git_show = ol_repo.git.show()
195
+ assert "diff --git a/figs/fig3.txt b/figs/fig3.txt" in ol_repo_git_show
196
+ # Now move from Git to DVC: first remove from Git index (keeping file on
197
+ # disk), then add to DVC so it gets moved to DVC cache
198
+ repo.git.rm(["--cached", "ol-project/figs/fig3.txt"])
199
+ subprocess.run(
200
+ ["dvc", "add", "ol-project/figs/fig3.txt"],
201
+ check=True,
202
+ cwd=repo.working_dir,
203
+ )
204
+ # Commit the DVC pointer file (fig3.txt is now tracked by DVC, not Git)
205
+ repo.git.add("ol-project/figs/fig3.txt.dvc", "ol-project/figs/.gitignore")
206
+ repo.git.commit(["-m", "Move figure 3 from git to DVC"])
207
+ assert "ol-project/figs/fig3.txt" not in ls_files(repo)
208
+ # Also remove the local file to simulate the file not being pulled from
209
+ # DVC (i.e., only the DVC pointer exists locally, not the actual file)
210
+ fig3_path = os.path.join(
211
+ repo.working_dir, "ol-project", "figs", "fig3.txt"
212
+ )
213
+ if os.path.exists(fig3_path):
214
+ os.remove(fig3_path)
215
+ assert not os.path.exists(fig3_path)
216
+ subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
217
+ ol_repo_git_show = ol_repo.git.show()
218
+ print("Git show in OL repo after moving fig3 to DVC:\n", ol_repo_git_show)
219
+ # The file should not have been deleted from Overleaf
220
+ assert "deleted file mode" not in ol_repo_git_show
221
+ assert "--- a/figs/fig3.txt" not in ol_repo_git_show
184
222
 
185
223
 
186
224
  def test_extract_title_from_tex(tmp_dir):
@@ -1,12 +1,19 @@
1
1
  """Tests for the ``conda`` module."""
2
2
 
3
3
  import os
4
+ import shutil
4
5
  import subprocess
5
6
 
6
7
  import pytest
7
8
 
8
9
  import calkit
9
- from calkit.conda import _check_list, _check_single, check_env
10
+ from calkit.conda import (
11
+ _check_list,
12
+ _check_single,
13
+ _get_pip_dependency_list,
14
+ _split_env_dependencies,
15
+ check_env,
16
+ )
10
17
 
11
18
  ENV_NAME = "main"
12
19
 
@@ -37,6 +44,25 @@ def test_check_list():
37
44
  assert not _check_list("pandas", installed, env_spec_dir=".", conda=False)
38
45
 
39
46
 
47
+ def test_split_env_dependencies():
48
+ dependencies = [
49
+ "python=3.12",
50
+ "pip",
51
+ "numpy=2",
52
+ {"pip": ["sqlalchemy==2.0.39"]},
53
+ ]
54
+ conda_deps, pip_deps = _split_env_dependencies(dependencies)
55
+ assert conda_deps == ["python=3.12", "pip", "numpy=2"]
56
+ assert pip_deps == ["sqlalchemy==2.0.39"]
57
+
58
+
59
+ def test_get_pip_dependency_list():
60
+ dependencies = ["python=3.12", "pip", {"pip": "sqlalchemy==2.0.39"}]
61
+ pip_deps = _get_pip_dependency_list(dependencies)
62
+ assert pip_deps == ["sqlalchemy==2.0.39"]
63
+ assert dependencies[-1]["pip"] == ["sqlalchemy==2.0.39"]
64
+
65
+
40
66
  def delete_env(name: str):
41
67
  subprocess.check_call(["conda", "env", "remove", "-y", "-n", name])
42
68
 
@@ -262,7 +288,7 @@ def test_check_prefix_env(tmp_dir, conda_env_prefix):
262
288
  )
263
289
 
264
290
 
265
- def test_check_editable(tmp_dir, conda_env_name):
291
+ def test_check_env_editable(tmp_dir, conda_env_name):
266
292
  subprocess.check_call(["calkit", "init"])
267
293
  # Create a dummy package named 'src' to install in editable mode
268
294
  os.makedirs("src", exist_ok=True)
@@ -306,6 +332,98 @@ setup(
306
332
  lock = calkit.ryaml.load(f)
307
333
  pip_deps = lock["dependencies"][-1]["pip"]
308
334
  assert "-e ." in pip_deps
335
+ # Now let's make sure we get proper output if the editable package is
336
+ # has an invalid pyproject.toml
337
+ os.remove("setup.py")
338
+ shutil.rmtree("src.egg-info")
339
+ toml_txt = """[build-system]
340
+ requires = ["setuptools>=61.0.0", "wheel", "setuptools-scm>=8"]
341
+ build-backend = "setuptools.build_meta"
342
+
343
+ [project]
344
+ name = "src-thing"
345
+ dynamic = ["version"]
346
+ authors = [
347
+ {name = "Someone"}
348
+ ]
349
+ description = "Test"
350
+
351
+ dependencies = [
352
+ "numpy>=1.21",
353
+ "scipy>=1.7",
354
+ "pandas>=1.5",
355
+ "matplotlib>=3.5",
356
+ "h5netcdf>=0.12",
357
+ "h5py>=3.0",
358
+ "xarray>=2023.0",
359
+ "streamlit>=1.0"
360
+ ]
361
+
362
+ [tool.setuptools]
363
+ package-dir = {"" = "src"}
364
+ packages = ["src-thing"]
365
+
366
+ [tool.setuptools.packages.find]
367
+ where = ["src"]
368
+
369
+ [tool.setuptools.package-data]
370
+ "src-thing" = [] # Explicitly state no package data
371
+
372
+ [tool.setuptools_scm]
373
+ local_scheme = "no-local-version"
374
+ fallback_version = "0+unknown"
375
+ """
376
+ with open("pyproject.toml", "w") as f:
377
+ f.write(toml_txt)
378
+ with pytest.raises(Exception, match="Failed to load pyproject.toml"):
379
+ res = check_env()
380
+ # Fix it and make sure it runs with relaxed mode
381
+ toml_txt = """[build-system]
382
+ requires = ["setuptools>=61.0.0", "wheel", "setuptools-scm>=8"]
383
+ build-backend = "setuptools.build_meta"
384
+
385
+ [project]
386
+ name = "src-thing"
387
+ dynamic = ["version"]
388
+ authors = [
389
+ {name = "Someone"}
390
+ ]
391
+ description = "Test"
392
+
393
+ dependencies = []
394
+
395
+ [tool.setuptools]
396
+ packages = ["src"]
397
+
398
+ [tool.setuptools_scm]
399
+ local_scheme = "no-local-version"
400
+ fallback_version = "0+unknown"
401
+ """
402
+ with open("pyproject.toml", "w") as f:
403
+ f.write(toml_txt)
404
+ res = check_env(relaxed=True)
405
+ assert res.env_exists
406
+ assert res.env_needs_rebuild
407
+ assert res.env_needs_export
408
+ # Make sure we can import the editable package
409
+ os.makedirs("subdir")
410
+ subprocess.check_call(
411
+ [
412
+ "conda",
413
+ "run",
414
+ "-n",
415
+ conda_env_name,
416
+ "python",
417
+ "-c",
418
+ "import src; print('src file:', src.__file__);",
419
+ ],
420
+ cwd="subdir",
421
+ )
422
+ # Check again and make sure we don't need a rebuild since the editable
423
+ # package is still valid
424
+ res = check_env(relaxed=True)
425
+ assert res.env_exists
426
+ assert not res.env_needs_rebuild
309
427
 
310
428
 
311
429
  def test_find_conda_exe():
@@ -2,8 +2,10 @@
2
2
 
3
3
  import subprocess
4
4
 
5
+ import git
5
6
  import pytest
6
7
 
8
+ import calkit
7
9
  import calkit.pipeline
8
10
  from calkit.environments import get_env_lock_fpath
9
11
  from calkit.pipeline import stages_are_similar
@@ -648,3 +650,48 @@ def test_shell_script_stage_allows_non_composite_slurm_env():
648
650
  "calkit slurm batch --name run --environment mycluster "
649
651
  "-- scripts/run.sh a b"
650
652
  )
653
+
654
+
655
+ def test_gitignore_updated_when_stage_output_renamed(tmp_dir):
656
+ """When a stage output path is renamed (e.g., from a capitalization change) the
657
+ old path should be removed from .gitignore and the new path should be
658
+ added.
659
+ """
660
+ subprocess.check_call(["calkit", "init"])
661
+ # Stage 1: initial calkit.yaml with output 'b_sparsity_plot.pdf' stored in DVC
662
+ ck_info = {
663
+ "pipeline": {
664
+ "stages": {
665
+ "plot": {
666
+ "kind": "command",
667
+ "environment": "_system",
668
+ "command": "touch b_sparsity_plot.pdf",
669
+ "outputs": [
670
+ {
671
+ "path": "b_sparsity_plot.pdf",
672
+ "storage": "dvc",
673
+ }
674
+ ],
675
+ }
676
+ }
677
+ }
678
+ }
679
+ with open("calkit.yaml", "w") as f:
680
+ calkit.ryaml.dump(ck_info, f)
681
+ subprocess.check_call(["calkit", "run"])
682
+ # Verify DVC has added the old output path to .gitignore
683
+ repo = git.Repo(".")
684
+ assert repo.ignored("b_sparsity_plot.pdf")
685
+ # Stage 2: rename output (capitalization change) to 'B_sparsity_plot.pdf'
686
+ ck_info["pipeline"]["stages"]["plot"]["command"] = (
687
+ "touch B_sparsity_plot.pdf"
688
+ )
689
+ ck_info["pipeline"]["stages"]["plot"]["outputs"] = [
690
+ {"path": "B_sparsity_plot.pdf", "storage": "dvc"}
691
+ ]
692
+ with open("calkit.yaml", "w") as f:
693
+ calkit.ryaml.dump(ck_info, f)
694
+ subprocess.check_call(["calkit", "run"])
695
+ # Old (stale) path should no longer be ignored by git
696
+ assert not repo.ignored("b_sparsity_plot.pdf")
697
+ assert repo.ignored("B_sparsity_plot.pdf")