calkit-python 0.35.5__tar.gz → 0.35.7__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 (286) hide show
  1. {calkit_python-0.35.5 → calkit_python-0.35.7}/PKG-INFO +1 -1
  2. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/main/core.py +52 -21
  3. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/new.py +74 -21
  4. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/update.py +27 -2
  5. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/dvc.py +9 -8
  6. calkit_python-0.35.7/calkit/git.py +297 -0
  7. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/jupyterlab/routes.py +2 -4
  8. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/pipeline.py +310 -15
  9. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/releases.py +81 -10
  10. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/server.py +3 -3
  11. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_new.py +91 -0
  12. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_git.py +59 -10
  13. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_pipeline.py +135 -5
  14. calkit_python-0.35.7/calkit/tests/test_releases.py +150 -0
  15. calkit_python-0.35.5/calkit/git.py +0 -197
  16. calkit_python-0.35.5/calkit/tests/test_releases.py +0 -44
  17. {calkit_python-0.35.5 → calkit_python-0.35.7}/.gitignore +0 -0
  18. {calkit_python-0.35.5 → calkit_python-0.35.7}/.pre-commit-config.yaml +0 -0
  19. {calkit_python-0.35.5 → calkit_python-0.35.7}/.prettierignore +0 -0
  20. {calkit_python-0.35.5 → calkit_python-0.35.7}/.python-version +0 -0
  21. {calkit_python-0.35.5 → calkit_python-0.35.7}/.yarnrc.yml +0 -0
  22. {calkit_python-0.35.5 → calkit_python-0.35.7}/CITATION.cff +0 -0
  23. {calkit_python-0.35.5 → calkit_python-0.35.7}/CODE_OF_CONDUCT.md +0 -0
  24. {calkit_python-0.35.5 → calkit_python-0.35.7}/CONTRIBUTING.md +0 -0
  25. {calkit_python-0.35.5 → calkit_python-0.35.7}/LICENSE +0 -0
  26. {calkit_python-0.35.5 → calkit_python-0.35.7}/Makefile +0 -0
  27. {calkit_python-0.35.5 → calkit_python-0.35.7}/README.md +0 -0
  28. {calkit_python-0.35.5 → calkit_python-0.35.7}/babel.config.js +0 -0
  29. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/__init__.py +0 -0
  30. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/__main__.py +0 -0
  31. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/calc.py +0 -0
  32. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/check.py +0 -0
  33. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/__init__.py +0 -0
  34. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/check.py +0 -0
  35. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/cloud.py +0 -0
  36. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/config.py +0 -0
  37. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/core.py +0 -0
  38. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/describe.py +0 -0
  39. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/import_.py +0 -0
  40. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/latex.py +0 -0
  41. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/list.py +0 -0
  42. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/main/__init__.py +0 -0
  43. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/main/xr.py +0 -0
  44. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/notebooks.py +0 -0
  45. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/office.py +0 -0
  46. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/overleaf.py +0 -0
  47. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cli/slurm.py +0 -0
  48. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/cloud.py +0 -0
  49. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/conda.py +0 -0
  50. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/config.py +0 -0
  51. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/core.py +0 -0
  52. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/datasets.py +0 -0
  53. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/detect.py +0 -0
  54. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/docker.py +0 -0
  55. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/environments.py +0 -0
  56. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/fs.py +0 -0
  57. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/github.py +0 -0
  58. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/gui.py +0 -0
  59. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/invenio.py +0 -0
  60. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/julia.py +0 -0
  61. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/jupyter.py +0 -0
  62. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/jupyterlab/__init__.py +0 -0
  63. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/package.json +0 -0
  64. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  65. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  66. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  67. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  68. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  69. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/909.651be47ca47390b78a92.js +0 -0
  70. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  71. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  72. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  73. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/remoteEntry.c091821b3d7f2d287a67.js +0 -0
  74. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/style.js +0 -0
  75. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/labextension/static/third-party-licenses.json +0 -0
  76. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/licenses.py +0 -0
  77. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/magics.py +0 -0
  78. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/matlab.py +0 -0
  79. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/models/__init__.py +0 -0
  80. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/models/core.py +0 -0
  81. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/models/io.py +0 -0
  82. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/models/iteration.py +0 -0
  83. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/models/pipeline.py +0 -0
  84. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/notebooks.py +0 -0
  85. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/office.py +0 -0
  86. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/ops.py +0 -0
  87. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/overleaf.py +0 -0
  88. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/__init__.py +0 -0
  89. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/core.py +0 -0
  90. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/__init__.py +0 -0
  91. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/article/paper.tex +0 -0
  92. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/core.py +0 -0
  93. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/jfm/jfm.bst +0 -0
  94. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/jfm/jfm.cls +0 -0
  95. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  96. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/jfm/paper.tex +0 -0
  97. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/templates/latex/jfm/upmath.sty +0 -0
  98. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/__init__.py +0 -0
  99. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/__init__.py +0 -0
  100. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/main/__init__.py +0 -0
  101. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/main/test_core.py +0 -0
  102. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/main/test_xr.py +0 -0
  103. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_check.py +0 -0
  104. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_config.py +0 -0
  105. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_import.py +0 -0
  106. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_latex.py +0 -0
  107. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_list.py +0 -0
  108. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_notebooks.py +0 -0
  109. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/cli/test_overleaf.py +0 -0
  110. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/jupyterlab/__init__.py +0 -0
  111. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/jupyterlab/test_routes.py +0 -0
  112. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/models/__init__.py +0 -0
  113. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/models/test_iteration.py +0 -0
  114. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/models/test_pipeline.py +0 -0
  115. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_calc.py +0 -0
  116. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_check.py +0 -0
  117. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_conda.py +0 -0
  118. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_core.py +0 -0
  119. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_detect.py +0 -0
  120. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_docker.py +0 -0
  121. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_dvc.py +0 -0
  122. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_environments.py +0 -0
  123. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_fs.py +0 -0
  124. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_invenio.py +0 -0
  125. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_julia.py +0 -0
  126. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_jupyter.py +0 -0
  127. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_magics.py +0 -0
  128. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_matlab.py +0 -0
  129. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_notebooks.py +0 -0
  130. {calkit_python-0.35.5 → calkit_python-0.35.7}/calkit/tests/test_templates.py +0 -0
  131. {calkit_python-0.35.5 → calkit_python-0.35.7}/conftest.py +0 -0
  132. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/CNAME +0 -0
  133. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/apps.md +0 -0
  134. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/calculations.md +0 -0
  135. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/calkit-yaml.md +0 -0
  136. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/cli-reference.md +0 -0
  137. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/cloud-integration.md +0 -0
  138. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/datasets.md +0 -0
  139. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/dependencies.md +0 -0
  140. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/environments.md +0 -0
  141. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/examples.md +0 -0
  142. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/governance.md +0 -0
  143. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/help.md +0 -0
  144. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/c-to-the-k-white.svg +0 -0
  145. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/calkit-fragmentation-compendium.png +0 -0
  146. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/calkit-no-bg.png +0 -0
  147. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/connect-zenodo.png +0 -0
  148. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/jupyterlab/all-green.png +0 -0
  149. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  150. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/jupyterlab/new-env.png +0 -0
  151. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/jupyterlab/new-notebook.png +0 -0
  152. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  153. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/jupyterlab-params.png +0 -0
  154. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/plos-osi-code-2024-03.png +0 -0
  155. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/img/vscode-nb-params.png +0 -0
  156. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/index.md +0 -0
  157. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/installation.md +0 -0
  158. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/jupyterlab.md +0 -0
  159. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/local-server.md +0 -0
  160. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/notebooks.md +0 -0
  161. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/overleaf.md +0 -0
  162. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/pipeline/index.md +0 -0
  163. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/pipeline/manual-steps.md +0 -0
  164. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/pipeline/running-and-logging.md +0 -0
  165. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/pipeline/slurm.md +0 -0
  166. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/quickstart.md +0 -0
  167. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/references.md +0 -0
  168. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/releases.md +0 -0
  169. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/reproducibility.md +0 -0
  170. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  171. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/conda-envs.md +0 -0
  172. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/existing-project.md +0 -0
  173. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/first-project.md +0 -0
  174. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/github-actions.md +0 -0
  175. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  176. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  177. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  178. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  179. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  180. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  181. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  182. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  183. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  184. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  185. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  186. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  187. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  188. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  189. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  190. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/create-project.png +0 -0
  191. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  192. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/excel-chart.png +0 -0
  193. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/excel-data.png +0 -0
  194. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  195. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/needs-clone.png +0 -0
  196. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/new-stage.png +0 -0
  197. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  198. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  199. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/status-more-rows.png +0 -0
  200. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  201. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/untracked-data.png +0 -0
  202. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/updated-publication.png +0 -0
  203. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  204. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/office/workflow-page.png +0 -0
  205. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/clone.png +0 -0
  206. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/create-project.png +0 -0
  207. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  208. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  209. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  210. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/new-token.png +0 -0
  211. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/reclone.png +0 -0
  212. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  213. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/quick-actions.png +0 -0
  214. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/img/run-proc.png +0 -0
  215. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/index.md +0 -0
  216. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/jupyterlab.md +0 -0
  217. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/latex-codespaces.md +0 -0
  218. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/matlab.md +0 -0
  219. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/notebook-pipeline.md +0 -0
  220. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/office.md +0 -0
  221. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/openfoam.md +0 -0
  222. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/tutorials/procedures.md +0 -0
  223. {calkit_python-0.35.5 → calkit_python-0.35.7}/docs/version-control.md +0 -0
  224. {calkit_python-0.35.5 → calkit_python-0.35.7}/install.json +0 -0
  225. {calkit_python-0.35.5 → calkit_python-0.35.7}/jest.config.js +0 -0
  226. {calkit_python-0.35.5 → calkit_python-0.35.7}/jupyter-config/server-config/calkit.json +0 -0
  227. {calkit_python-0.35.5 → calkit_python-0.35.7}/mkdocs.yml +0 -0
  228. {calkit_python-0.35.5 → calkit_python-0.35.7}/package.json +0 -0
  229. {calkit_python-0.35.5 → calkit_python-0.35.7}/pyproject.toml +0 -0
  230. {calkit_python-0.35.5 → calkit_python-0.35.7}/schema/plugin.json +0 -0
  231. {calkit_python-0.35.5 → calkit_python-0.35.7}/scripts/generate-cli-reference.py +0 -0
  232. {calkit_python-0.35.5 → calkit_python-0.35.7}/scripts/install.ps1 +0 -0
  233. {calkit_python-0.35.5 → calkit_python-0.35.7}/scripts/install.sh +0 -0
  234. {calkit_python-0.35.5 → calkit_python-0.35.7}/scripts/make-calk9.sh +0 -0
  235. {calkit_python-0.35.5 → calkit_python-0.35.7}/scripts/sync-docs.py +0 -0
  236. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/__tests__/useQueries.spec.ts +0 -0
  237. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/calkit-config.ts +0 -0
  238. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/cell-output-marker.ts +0 -0
  239. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/commit-dialog.tsx +0 -0
  240. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/environment-editor.tsx +0 -0
  241. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/notebook-registration.tsx +0 -0
  242. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/notebook-toolbar.tsx +0 -0
  243. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/pipeline-status-bar.tsx +0 -0
  244. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/project-info-editor.tsx +0 -0
  245. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/sidebar-settings.tsx +0 -0
  246. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/sidebar.tsx +0 -0
  247. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/components/stage-editor.tsx +0 -0
  248. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/feature-flags.ts +0 -0
  249. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/file-browser-menu.ts +0 -0
  250. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  251. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/hooks/useQueries.ts +0 -0
  252. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/icons.ts +0 -0
  253. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/index.ts +0 -0
  254. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/io-tracker.ts +0 -0
  255. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/pipeline-state.ts +0 -0
  256. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/queryClient.ts +0 -0
  257. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/request.ts +0 -0
  258. {calkit_python-0.35.5 → calkit_python-0.35.7}/src/shims-mainmenu.d.ts +0 -0
  259. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/base.css +0 -0
  260. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/cell-output-marker.css +0 -0
  261. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/environment-editor.css +0 -0
  262. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/environment-selector.css +0 -0
  263. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/img/calkit-no-bg.png +0 -0
  264. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/index.css +0 -0
  265. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/index.js +0 -0
  266. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/notebook-toolbar.css +0 -0
  267. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/pipeline-status-bar.css +0 -0
  268. {calkit_python-0.35.5 → calkit_python-0.35.7}/style/sidebar.css +0 -0
  269. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  270. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/nb-julia.ipynb +0 -0
  271. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/nb-params.ipynb +0 -0
  272. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/nb-subdir.ipynb +0 -0
  273. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/pipeline.ipynb +0 -0
  274. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/script.py +0 -0
  275. {calkit_python-0.35.5 → calkit_python-0.35.7}/test/test-log.log +0 -0
  276. {calkit_python-0.35.5 → calkit_python-0.35.7}/tsconfig.json +0 -0
  277. {calkit_python-0.35.5 → calkit_python-0.35.7}/tsconfig.test.json +0 -0
  278. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/.gitignore +0 -0
  279. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/README.md +0 -0
  280. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/jupyter_server_test_config.py +0 -0
  281. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/package.json +0 -0
  282. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/playwright.config.js +0 -0
  283. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/tests/calkit.spec.ts +0 -0
  284. {calkit_python-0.35.5 → calkit_python-0.35.7}/ui-tests/yarn.lock +0 -0
  285. {calkit_python-0.35.5 → calkit_python-0.35.7}/uv.lock +0 -0
  286. {calkit_python-0.35.5 → calkit_python-0.35.7}/yarn.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.35.5
3
+ Version: 0.35.7
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -55,7 +55,7 @@ from calkit.cli.office import office_app
55
55
  from calkit.cli.overleaf import overleaf_app
56
56
  from calkit.cli.slurm import slurm_app
57
57
  from calkit.cli.update import update_app
58
- from calkit.dvc import register_ck_scheme, run_dvc_command
58
+ from calkit.dvc import get_dvc_repo, run_dvc_command
59
59
  from calkit.environments import get_env_lock_fpath
60
60
  from calkit.models import Procedure
61
61
 
@@ -252,10 +252,12 @@ def get_status(
252
252
  ):
253
253
  """View status (project, version control, and/or pipeline)."""
254
254
  ck_info = calkit.load_calkit_info()
255
- try:
256
- calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
257
- except Exception as e:
258
- warn(f"Failed to compile pipeline: {e.__class__.__name__}: {e}")
255
+ # If there's anything in ck_info and this isn't a Git repo, initialize one
256
+ if ck_info:
257
+ try:
258
+ git.Repo()
259
+ except InvalidGitRepositoryError:
260
+ git.Repo.init()
259
261
  valid_categories = ["project", "git", "dvc", "pipeline"]
260
262
  if categories is not None:
261
263
  for category in categories:
@@ -266,11 +268,22 @@ def get_status(
266
268
  )
267
269
  else:
268
270
  categories = valid_categories
269
- # Clean all notebooks in the pipeline
270
- try:
271
- calkit.notebooks.clean_all_in_pipeline(ck_info=ck_info)
272
- except Exception as e:
273
- warn(f"Failed to clean notebooks: {e.__class__.__name__}: {e}")
271
+ pipeline_status = None
272
+ if "pipeline" in categories or "dvc" in categories:
273
+ pipeline_status = calkit.pipeline.get_status(
274
+ ck_info=ck_info,
275
+ targets=targets,
276
+ check_environments=True,
277
+ clean_notebooks=True,
278
+ compile_to_dvc=True,
279
+ )
280
+ for error in pipeline_status.errors:
281
+ warn(error)
282
+ if pipeline_status.failed_environment_checks:
283
+ warn(
284
+ "Failed pipeline environment checks for: "
285
+ + ", ".join(pipeline_status.failed_environment_checks)
286
+ )
274
287
  if "project" in categories:
275
288
  print_sep("Project")
276
289
  # Print latest status
@@ -307,10 +320,32 @@ def get_status(
307
320
  typer.echo()
308
321
  if "pipeline" in categories or "dvc" in categories:
309
322
  print_sep("Pipeline")
310
- dvc_pipeline_cmd = ["status"]
311
- if targets:
312
- dvc_pipeline_cmd += targets
313
- run_dvc_command(dvc_pipeline_cmd)
323
+ # Nicely format the results from pipeline status
324
+ if pipeline_status and pipeline_status.errors:
325
+ typer.echo("Pipeline status unavailable due to errors.")
326
+ elif pipeline_status and pipeline_status.is_stale:
327
+ for stage_name in pipeline_status.stale_stage_names:
328
+ stale_stage = pipeline_status.stale_stages.get(stage_name)
329
+ if stale_stage is None:
330
+ continue
331
+ typer.echo(f"{typer.style(stage_name, fg='yellow')}:")
332
+ # Show stale outputs for this stage
333
+ if stale_stage.stale_outputs:
334
+ typer.echo(" stale outputs:")
335
+ for output_path in stale_stage.stale_outputs:
336
+ typer.echo(f" - {output_path}")
337
+ # Show modified outputs from this stage
338
+ if stale_stage.modified_outputs:
339
+ typer.echo(" modified outputs:")
340
+ for output_path in stale_stage.modified_outputs:
341
+ typer.echo(f" - {output_path}")
342
+ # Show modified inputs making the stage stale
343
+ if stale_stage.modified_inputs:
344
+ typer.echo(" modified inputs:")
345
+ for input_path in stale_stage.modified_inputs:
346
+ typer.echo(f" - {input_path}")
347
+ elif pipeline_status:
348
+ typer.echo("Pipeline is up to date.")
314
349
 
315
350
 
316
351
  @app.command(name="diff")
@@ -403,8 +438,7 @@ def add(
403
438
  raise_error("Not currently in a Git repo; run `calkit init` first")
404
439
  repo = git.Repo()
405
440
  try:
406
- register_ck_scheme()
407
- dvc_repo = dvc.repo.Repo()
441
+ dvc_repo = get_dvc_repo()
408
442
  except NotDvcRepoError:
409
443
  warn("DVC not initialized yet; initializing")
410
444
  dvc_repo = dvc.repo.Repo.init()
@@ -1139,10 +1173,8 @@ def run(
1139
1173
  os.environ.pop("CALKIT_PIPELINE_RUNNING", None)
1140
1174
  raise_error(f"Pipeline compilation failed: {e}")
1141
1175
  # Initialize DVC repo if necessary
1142
- # Register the ck:// scheme before accessing DVC repo
1143
- register_ck_scheme()
1144
1176
  try:
1145
- dvc.repo.Repo()
1177
+ get_dvc_repo()
1146
1178
  except Exception:
1147
1179
  if not quiet:
1148
1180
  typer.echo("Initializing DVC repo")
@@ -1203,8 +1235,7 @@ def run(
1203
1235
  git_staged_files_before = calkit.git.get_staged_files(repo=repo)
1204
1236
  git_untracked_files_before = calkit.git.get_untracked_files(repo=repo)
1205
1237
  # Get status of DVC repo before running
1206
- register_ck_scheme()
1207
- dvc_repo = dvc.repo.Repo()
1238
+ dvc_repo = get_dvc_repo()
1208
1239
  dvc_status_before = dvc_repo.status()
1209
1240
  dvc_data_status_before = dvc_repo.data_status()
1210
1241
  dvc_data_status_before.pop("git", None) # Remove git status
@@ -17,6 +17,7 @@ from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError
17
17
  from typing_extensions import Annotated
18
18
 
19
19
  import calkit
20
+ import calkit.pipeline
20
21
  from calkit.cli import raise_error, warn
21
22
  from calkit.cli.check import check_environment
22
23
  from calkit.cli.update import update_devcontainer
@@ -2523,6 +2524,26 @@ def new_release(
2523
2524
  repo = git.Repo()
2524
2525
  if name in repo.tags:
2525
2526
  raise_error(f"Git tag with name '{name}' already exists")
2527
+ # Check that the pipeline is up-to-date
2528
+ typer.echo("Checking pipeline is up-to-date for release")
2529
+ status = calkit.pipeline.get_status(
2530
+ ck_info=ck_info,
2531
+ check_environments=True,
2532
+ clean_notebooks=True,
2533
+ compile_to_dvc=True,
2534
+ )
2535
+ if status.errors:
2536
+ raise_error("Pipeline checks failed: " + "; ".join(status.errors))
2537
+ if status.failed_environment_checks:
2538
+ raise_error(
2539
+ "Pipeline environment checks failed for: "
2540
+ + ", ".join(status.failed_environment_checks)
2541
+ )
2542
+ if status.is_stale:
2543
+ raise_error(
2544
+ "Pipeline is not up-to-date; out-of-date stages: "
2545
+ + ", ".join(status.stale_stage_names)
2546
+ )
2526
2547
  release_dir = f".calkit/releases/{name}"
2527
2548
  release_files_dir = release_dir + "/files"
2528
2549
  os.makedirs(release_files_dir, exist_ok=True)
@@ -2543,8 +2564,14 @@ def new_release(
2543
2564
  all_paths = calkit.releases.ls_files()
2544
2565
  typer.echo(f"Adding files to {zip_path}")
2545
2566
  with zipfile.ZipFile(zip_path, "w") as zipf:
2546
- for fpath in all_paths:
2547
- zipf.write(fpath)
2567
+ calkit.releases.add_paths_to_zip(zipf, all_paths)
2568
+ typer.echo("Checking extracted project release archive")
2569
+ try:
2570
+ calkit.releases.check_project_release_archive(
2571
+ zip_path, verbose=verbose
2572
+ )
2573
+ except Exception as e:
2574
+ raise_error(str(e))
2548
2575
  title = ck_info.get("title")
2549
2576
  if title is None:
2550
2577
  warn("Project has no title")
@@ -2888,10 +2915,12 @@ def new_release(
2888
2915
  + new_lines[first_content_line_index:]
2889
2916
  )
2890
2917
  readme_txt = "\n".join(new_lines)
2891
- with open("README.md", "w") as f:
2892
- f.write(readme_txt)
2893
2918
  if not dry_run:
2894
- repo.git.add("README.md")
2919
+ with open("README.md", "w") as f:
2920
+ f.write(readme_txt)
2921
+ repo.git.add("README.md")
2922
+ else:
2923
+ typer.echo(f"Would have updated README.md to:\n{readme_txt}")
2895
2924
  # Create Git tag
2896
2925
  git_tag_message = release_description
2897
2926
  if git_tag_message is None:
@@ -2919,13 +2948,15 @@ def new_release(
2919
2948
  ck_info["releases"] = releases
2920
2949
  # Create CITATION.cff file
2921
2950
  if release_type == "project":
2922
- typer.echo("Writing CITATION.cff")
2923
2951
  cff = calkit.releases.create_citation_cff(
2924
2952
  ck_info=ck_info, release_name=name, release_date=release_date
2925
2953
  )
2926
- with open("CITATION.cff", "w") as f:
2927
- calkit.ryaml.dump(cff, f)
2928
- if not dry_run:
2954
+ if dry_run:
2955
+ typer.echo(f"Would write CITATION.cff:\n{cff}")
2956
+ else:
2957
+ typer.echo("Writing CITATION.cff")
2958
+ with open("CITATION.cff", "w") as f:
2959
+ calkit.ryaml.dump(cff, f)
2929
2960
  repo.git.add("CITATION.cff")
2930
2961
  # Add to references so it can be cited
2931
2962
  typer.echo("Adding BibTeX entry to references")
@@ -2944,28 +2975,50 @@ def new_release(
2944
2975
  reflib = bibtexparser.load(f)
2945
2976
  else:
2946
2977
  reflib = bibtexparser.bibdatabase.BibDatabase()
2978
+ bibtex_doi = doi
2979
+ if bibtex_doi is None and dry_run:
2980
+ mock_suffix = "".join(
2981
+ ch if (ch.isalnum() or ch in ".-_") else "-"
2982
+ for ch in name.lower()
2983
+ )
2984
+ bibtex_doi = f"10.0000/calkit-dry-run.{mock_suffix}"
2947
2985
  invenio_bibtex = calkit.releases.create_bibtex(
2948
2986
  authors=authors,
2949
2987
  release_date=release_date,
2950
2988
  title=title, # type: ignore
2951
- doi=doi, # type: ignore
2989
+ doi=bibtex_doi,
2952
2990
  record_id=record_id, # type: ignore
2953
2991
  service=to, # type: ignore
2954
2992
  )
2955
- new_entry = bibtexparser.loads(invenio_bibtex).entries[0]
2993
+ new_entries = bibtexparser.loads(invenio_bibtex).entries
2994
+ if not new_entries:
2995
+ raise ValueError("Failed to parse generated BibTeX entry")
2996
+ new_entry = new_entries[0]
2956
2997
  # Search through entries for one with the same DOI, and replace if
2957
2998
  # there is a match
2958
- existing_index = None
2959
- for n, entry in enumerate(reflib.entries):
2960
- if entry.get("doi") == doi:
2961
- typer.echo("Found matching DOI in existing references")
2962
- existing_index = n
2963
- if existing_index is not None:
2964
- _ = reflib.entries.pop(existing_index)
2999
+ new_doi = new_entry.get("doi")
3000
+ matching_indices = []
3001
+ if new_doi:
3002
+ matching_indices = [
3003
+ n
3004
+ for n, entry in enumerate(reflib.entries)
3005
+ if entry.get("doi") == new_doi
3006
+ ]
3007
+ if matching_indices:
3008
+ typer.echo(
3009
+ "Found matching DOI in existing references "
3010
+ f"({len(matching_indices)} entr"
3011
+ f"{'y' if len(matching_indices) == 1 else 'ies'}); "
3012
+ "replacing"
3013
+ )
3014
+ for n in reversed(matching_indices):
3015
+ _ = reflib.entries.pop(n)
2965
3016
  reflib.entries.append(new_entry)
2966
- with open(ref_path, "w") as f:
2967
- bibtexparser.dump(reflib, f)
2968
- if not dry_run:
3017
+ if dry_run:
3018
+ typer.echo(f"Would write updated references to {ref_path}")
3019
+ else:
3020
+ with open(ref_path, "w") as f:
3021
+ bibtexparser.dump(reflib, f)
2969
3022
  repo.git.add(ref_path)
2970
3023
  except Exception as e:
2971
3024
  warn(f"Failed to add to references: {e}")
@@ -12,6 +12,7 @@ import typer
12
12
  from typing_extensions import Annotated
13
13
 
14
14
  import calkit
15
+ import calkit.pipeline
15
16
  from calkit.cli import raise_error
16
17
 
17
18
  update_app = typer.Typer(no_args_is_help=True)
@@ -156,6 +157,26 @@ def update_release(
156
157
  record_id = release.get("record_id")
157
158
  if record_id is None:
158
159
  raise_error("Release has no record ID")
160
+ if publish or reupload:
161
+ typer.echo("Checking pipeline is up-to-date for release update")
162
+ status = calkit.pipeline.get_status(
163
+ ck_info=ck_info,
164
+ check_environments=True,
165
+ clean_notebooks=True,
166
+ compile_to_dvc=True,
167
+ )
168
+ if status.errors:
169
+ raise_error("Pipeline checks failed: " + "; ".join(status.errors))
170
+ if status.failed_environment_checks:
171
+ raise_error(
172
+ "Pipeline environment checks failed for: "
173
+ + ", ".join(status.failed_environment_checks)
174
+ )
175
+ if status.is_stale:
176
+ raise_error(
177
+ "Pipeline is not up-to-date; out-of-date stages: "
178
+ + ", ".join(status.stale_stage_names)
179
+ )
159
180
  if publish:
160
181
  try:
161
182
  calkit.invenio.post(
@@ -245,8 +266,12 @@ def update_release(
245
266
  all_paths = calkit.releases.ls_files()
246
267
  typer.echo(f"Adding files to {zip_path}")
247
268
  with zipfile.ZipFile(zip_path, "w") as zipf:
248
- for fpath in all_paths:
249
- zipf.write(fpath)
269
+ calkit.releases.add_paths_to_zip(zipf, all_paths)
270
+ typer.echo("Checking project release archive")
271
+ try:
272
+ calkit.releases.check_project_release_archive(zip_path)
273
+ except Exception as e:
274
+ raise_error(str(e))
250
275
  try:
251
276
  files_in_record = [
252
277
  entry["key"]
@@ -9,6 +9,7 @@ import os
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ import dvc.repo
12
13
  import git
13
14
  from dvc.utils.objects import cached_property
14
15
  from dvc_objects.fs.base import ObjectFileSystem
@@ -234,6 +235,12 @@ def run_dvc_cli(argv: list[str] | None = None) -> int:
234
235
  return dvc_main(argv)
235
236
 
236
237
 
238
+ def get_dvc_repo(wdir: str | None = None) -> dvc.repo.Repo:
239
+ """Return a DVC repo with ``ck://`` scheme support registered."""
240
+ register_ck_scheme()
241
+ return dvc.repo.Repo(wdir)
242
+
243
+
237
244
  def run_dvc_command(argv: list[str], cwd: str | None = None) -> int:
238
245
  """Run a DVC command, optionally in a specific working directory.
239
246
 
@@ -367,14 +374,10 @@ def get_remotes(wdir: str | None = None) -> dict[str, str]:
367
374
  """Get a dictionary of DVC remotes, keyed by name, with URL as the
368
375
  value.
369
376
  """
370
- import dvc.repo
371
377
  from dvc.exceptions import NotDvcRepoError
372
378
 
373
- # Ensure DVC recognizes ck:// remotes when validating config.
374
- register_ck_scheme()
375
- repo_path = wdir or "."
376
379
  try:
377
- repo = dvc.repo.Repo(repo_path)
380
+ repo = get_dvc_repo(wdir)
378
381
  except NotDvcRepoError:
379
382
  return {}
380
383
  try:
@@ -403,9 +406,7 @@ def list_files(wdir: str | None = None, recursive=True) -> list[dict]:
403
406
  """Return a list with all files in DVC, including their path and md5
404
407
  checksum.
405
408
  """
406
- import dvc.repo
407
-
408
- dvc_repo = dvc.repo.Repo(wdir)
409
+ dvc_repo = get_dvc_repo(wdir)
409
410
  return dvc_repo.ls(".", dvc_only=True, recursive=recursive)
410
411
 
411
412
 
@@ -0,0 +1,297 @@
1
+ """Git-related functionality."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from os import PathLike
7
+ from pathlib import Path
8
+
9
+ import git
10
+
11
+
12
+ def get_staged_files(
13
+ path: str | None = None, repo: git.Repo | None = None
14
+ ) -> list[str]:
15
+ """Get a list of staged files for the repo at ``path`` or the provided
16
+ repo.
17
+ """
18
+ if repo is None:
19
+ repo = git.Repo(path)
20
+ cmd = ["--staged", "--name-only"]
21
+ if path is not None:
22
+ cmd.append(path)
23
+ diff = repo.git.diff(cmd)
24
+ paths = diff.split("\n")
25
+ return [p for p in paths if p]
26
+
27
+
28
+ def get_changed_files(
29
+ path: str | None = None, repo: git.Repo | None = None
30
+ ) -> list[str]:
31
+ """Get a list of files that have been changed but not staged."""
32
+ if repo is None:
33
+ repo = git.Repo(path)
34
+ return [
35
+ item.a_path
36
+ for item in repo.index.diff(None)
37
+ if item.a_path is not None
38
+ ]
39
+
40
+
41
+ def get_untracked_files(
42
+ path: str | None = None, repo: git.Repo | None = None
43
+ ) -> list[str]:
44
+ """Get a list of untracked files."""
45
+ if repo is None:
46
+ repo = git.Repo(path)
47
+ return repo.untracked_files
48
+
49
+
50
+ def get_staged_files_with_status(
51
+ path: str | None = None, repo: git.Repo | None = None
52
+ ) -> list[dict]:
53
+ if repo is None:
54
+ repo = git.Repo(path)
55
+ cmd = ["--staged", "--name-status"]
56
+ if path is not None:
57
+ cmd.append(path)
58
+ diff = repo.git.diff(cmd)
59
+ paths = diff.split("\n")
60
+ res = []
61
+ for pathi in paths:
62
+ # Make sure line is not empty, e.g., a trailing newline
63
+ if pathi:
64
+ status, p = pathi.split("\t")
65
+ res.append({"status": status, "path": p})
66
+ return res
67
+
68
+
69
+ def ls_files(repo: git.Repo, *args, **kwargs) -> list[str]:
70
+ """Get a list of all files tracked by git."""
71
+ output = repo.git.ls_files(*args, **kwargs)
72
+ return [f for f in output.split("\n") if f]
73
+
74
+
75
+ def _resolve_repo_and_ignore_path(
76
+ repo: git.Repo, path: str | PathLike
77
+ ) -> tuple[git.Repo, str]:
78
+ """Resolve which repo should own ignore rules for ``path``."""
79
+ # Normalize target path to absolute from the current repo root.
80
+ repo_root = Path(repo.working_dir).resolve()
81
+ path_obj = Path(path)
82
+ if path_obj.is_absolute():
83
+ abs_path = path_obj.resolve()
84
+ else:
85
+ abs_path = (repo_root / path_obj).resolve()
86
+ # If the path is inside a submodule, use that repo and relative path.
87
+ for submodule in repo.submodules:
88
+ submodule_root = (repo_root / submodule.path).resolve()
89
+ if abs_path == submodule_root:
90
+ continue
91
+ if abs_path.is_relative_to(submodule_root):
92
+ sub_repo = submodule.module()
93
+ rel_path = abs_path.relative_to(submodule_root).as_posix()
94
+ return sub_repo, rel_path
95
+ # Fall back to a repo-relative path when possible.
96
+ try:
97
+ rel_path = abs_path.relative_to(repo_root).as_posix()
98
+ except ValueError:
99
+ rel_path = path_obj.as_posix()
100
+ return repo, rel_path
101
+
102
+
103
+ def _get_matching_gitignore_details(
104
+ repo: git.Repo, path: str
105
+ ) -> tuple[Path | None, str | None]:
106
+ """Return the repo-local gitignore file and pattern matching ``path``."""
107
+ try:
108
+ check_ignore = repo.git.check_ignore("-v", "--", path)
109
+ except git.GitCommandError:
110
+ return None, None
111
+ line = check_ignore.splitlines()[0]
112
+ try:
113
+ source_info, _ = line.split("\t", 1)
114
+ source_path, _, pattern = source_info.rsplit(":", 2)
115
+ except ValueError:
116
+ return None, None
117
+ if not source_path.endswith(".gitignore"):
118
+ return None, pattern
119
+ gitignore_path = (Path(repo.working_dir) / source_path).resolve()
120
+ try:
121
+ gitignore_path.relative_to(Path(repo.working_dir).resolve())
122
+ except ValueError:
123
+ return None, pattern
124
+ return gitignore_path, pattern
125
+
126
+
127
+ def ensure_path_is_ignored(
128
+ repo: git.Repo, path: str | PathLike
129
+ ) -> None | bool:
130
+ """Ensure that the given path is ignored by Git.
131
+
132
+ Returns True if ``.gitignore`` was modified.
133
+ """
134
+ # Resolve whether the ignore rule belongs to this repo or a submodule.
135
+ target_repo, target_path = _resolve_repo_and_ignore_path(repo, path)
136
+ # No-op if Git already ignores this path.
137
+ if target_repo.ignored(target_path):
138
+ return
139
+ # Read gitignore first to check if the path is already ignored
140
+ # If not, we don't want to add a line for it since it was added
141
+ # TODO: Add an option to remove cached (`git rm --cached`)
142
+ gitignore_path = os.path.join(target_repo.working_dir, ".gitignore")
143
+ if os.path.isfile(gitignore_path):
144
+ with open(gitignore_path) as f:
145
+ gitignore_txt = f.read()
146
+ lines = gitignore_txt.splitlines()
147
+ if target_path in lines:
148
+ return
149
+ with open(gitignore_path, "a") as f:
150
+ if (
151
+ os.path.isfile(gitignore_path)
152
+ and os.path.getsize(gitignore_path) > 0
153
+ ):
154
+ f.write("\n")
155
+ f.write(f"{target_path}\n")
156
+ return True
157
+
158
+
159
+ def ensure_path_is_not_ignored(
160
+ repo: git.Repo, path: str | PathLike
161
+ ) -> None | bool:
162
+ """Ensure a path is not ignored by Git."""
163
+ # Resolve whether the unignore rule belongs to this repo or a submodule.
164
+ target_repo, target_path = _resolve_repo_and_ignore_path(repo, path)
165
+ # No-op if Git does not ignore this path.
166
+ if not target_repo.ignored(target_path):
167
+ return
168
+ matching_gitignore_path, matched_pattern = _get_matching_gitignore_details(
169
+ target_repo, target_path
170
+ )
171
+ if matching_gitignore_path is not None:
172
+ gitignore_path = matching_gitignore_path.as_posix()
173
+ path_for_gitignore = (
174
+ (Path(target_repo.working_dir) / target_path)
175
+ .resolve()
176
+ .relative_to(matching_gitignore_path.parent.resolve())
177
+ .as_posix()
178
+ )
179
+ else:
180
+ gitignore_path = os.path.join(target_repo.working_dir, ".gitignore")
181
+ path_for_gitignore = target_path
182
+ if not os.path.isfile(gitignore_path):
183
+ with open(gitignore_path, "w") as f:
184
+ f.write(f"!{path_for_gitignore}\n")
185
+ return True
186
+ with open(gitignore_path) as f:
187
+ gitignore_txt = f.read()
188
+ lines = gitignore_txt.splitlines()
189
+ direct_rule_variants = [path_for_gitignore, f"/{path_for_gitignore}"]
190
+ if matched_pattern is not None and matched_pattern.startswith("/"):
191
+ no_ignore_line = f"!/{path_for_gitignore}"
192
+ else:
193
+ no_ignore_line = f"!{path_for_gitignore}"
194
+ path_parts = Path(path_for_gitignore).parts
195
+
196
+ def ancestor_requires_recursive_unignore() -> bool:
197
+ """Return True if any ancestor-level ignore rule would block this path.
198
+
199
+ This includes explicit directory ignores (e.g. 'dir/' or '/dir/')
200
+ as well as ancestor-based glob patterns like 'dir/*' or '/dir/*',
201
+ i.e., any rule that would prevent reaching the nested path without
202
+ adding recursive unignore patterns.
203
+ """
204
+ for i in range(1, len(path_parts)):
205
+ ancestor = "/".join(path_parts[:i])
206
+ if (
207
+ ancestor in lines
208
+ or f"/{ancestor}" in lines
209
+ or f"{ancestor}/" in lines
210
+ or f"/{ancestor}/" in lines
211
+ or f"{ancestor}/*" in lines
212
+ or f"/{ancestor}/*" in lines
213
+ ):
214
+ return True
215
+ return False
216
+
217
+ if len(path_parts) == 1:
218
+ # Simple (non-nested) path: remove the direct ignore rule, or add a
219
+ # negation if the ignore comes from a glob or other pattern
220
+ direct_rule = next(
221
+ (rule for rule in direct_rule_variants if rule in lines), None
222
+ )
223
+ if direct_rule is not None:
224
+ lines.remove(direct_rule)
225
+ else:
226
+ # Remove any stale negation and re-append at the end so it takes
227
+ # precedence over any later re-ignore rule
228
+ if no_ignore_line in lines:
229
+ lines.remove(no_ignore_line)
230
+ lines.append(no_ignore_line)
231
+ else:
232
+ # Nested path: only apply recursive un-ignore rules when an ancestor
233
+ # directory is explicitly ignored
234
+ # Otherwise, remove a direct ignore
235
+ # rule for this path or add a simple negation if needed
236
+ removed_direct_rule = False
237
+ direct_rule = next(
238
+ (rule for rule in direct_rule_variants if rule in lines), None
239
+ )
240
+ if direct_rule is not None:
241
+ lines.remove(direct_rule)
242
+ removed_direct_rule = True
243
+ if ancestor_requires_recursive_unignore():
244
+ # Git will not traverse into a directory excluded by a "dir/"
245
+ # pattern, so a bare "!dir/sub/file" negation has no effect.
246
+ # We need to:
247
+ # 1. Convert any "ancestor/" (or "ancestor") exclude to
248
+ # "ancestor/*" so git traverses the directory while still
249
+ # ignoring direct children by default.
250
+ # 2. Add "!ancestor/" rules for intermediate directories.
251
+ # 3. Add "ancestor/*" re-ignore rules for each intermediate dir.
252
+ # 4. Add "!target_path" for the specific file.
253
+ for i in range(1, len(path_parts)):
254
+ ancestor = "/".join(path_parts[:i])
255
+ reignore_glob = f"{ancestor}/*"
256
+ if f"{ancestor}/" in lines:
257
+ idx = lines.index(f"{ancestor}/")
258
+ lines[idx] = reignore_glob
259
+ elif f"/{ancestor}/" in lines:
260
+ idx = lines.index(f"/{ancestor}/")
261
+ lines[idx] = f"/{ancestor}/*"
262
+ elif ancestor in lines:
263
+ idx = lines.index(ancestor)
264
+ lines[idx] = reignore_glob
265
+ elif f"/{ancestor}" in lines:
266
+ idx = lines.index(f"/{ancestor}")
267
+ lines[idx] = f"/{ancestor}/*"
268
+ no_ignore_dir = f"!{ancestor}/"
269
+ anchored_no_ignore_dir = f"!/{ancestor}/"
270
+ # The first ancestor does not need an explicit un-ignore once
271
+ # converted to "ancestor/*". Deeper ancestors do.
272
+ if i > 1:
273
+ # Remove stale entry and re-append so it takes precedence
274
+ if no_ignore_dir in lines:
275
+ lines.remove(no_ignore_dir)
276
+ elif anchored_no_ignore_dir in lines:
277
+ lines.remove(anchored_no_ignore_dir)
278
+ lines.append(no_ignore_dir)
279
+ if (
280
+ reignore_glob not in lines
281
+ and f"/{ancestor}/*" not in lines
282
+ ):
283
+ lines.append(reignore_glob)
284
+ # Remove stale negation and re-append at the end so it takes
285
+ # precedence over any later re-ignore rule
286
+ if no_ignore_line in lines:
287
+ lines.remove(no_ignore_line)
288
+ lines.append(no_ignore_line)
289
+ elif not removed_direct_rule:
290
+ # The path may be ignored by a non-directory pattern (e.g., glob);
291
+ # remove stale negation and append at end so it takes precedence
292
+ if no_ignore_line in lines:
293
+ lines.remove(no_ignore_line)
294
+ lines.append(no_ignore_line)
295
+ with open(gitignore_path, "w") as f:
296
+ f.write(os.linesep.join(lines))
297
+ return True