calkit-python 0.39.0__tar.gz → 0.39.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. {calkit_python-0.39.0 → calkit_python-0.39.1}/PKG-INFO +1 -1
  2. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/__init__.py +38 -0
  3. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/main/core.py +22 -0
  4. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/dvc/zip.py +83 -45
  5. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/pipeline.py +107 -58
  6. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_pipeline.py +172 -1
  7. {calkit_python-0.39.0 → calkit_python-0.39.1}/.claude-plugin/marketplace.json +0 -0
  8. {calkit_python-0.39.0 → calkit_python-0.39.1}/.gitignore +0 -0
  9. {calkit_python-0.39.0 → calkit_python-0.39.1}/.pre-commit-config.yaml +0 -0
  10. {calkit_python-0.39.0 → calkit_python-0.39.1}/.prettierignore +0 -0
  11. {calkit_python-0.39.0 → calkit_python-0.39.1}/.python-version +0 -0
  12. {calkit_python-0.39.0 → calkit_python-0.39.1}/.vscode/launch.json +0 -0
  13. {calkit_python-0.39.0 → calkit_python-0.39.1}/.vscode/tasks.json +0 -0
  14. {calkit_python-0.39.0 → calkit_python-0.39.1}/.yarnrc.yml +0 -0
  15. {calkit_python-0.39.0 → calkit_python-0.39.1}/AGENTS.md +0 -0
  16. {calkit_python-0.39.0 → calkit_python-0.39.1}/CITATION.cff +0 -0
  17. {calkit_python-0.39.0 → calkit_python-0.39.1}/CODE_OF_CONDUCT.md +0 -0
  18. {calkit_python-0.39.0 → calkit_python-0.39.1}/CONTRIBUTING.md +0 -0
  19. {calkit_python-0.39.0 → calkit_python-0.39.1}/LICENSE +0 -0
  20. {calkit_python-0.39.0 → calkit_python-0.39.1}/Makefile +0 -0
  21. {calkit_python-0.39.0 → calkit_python-0.39.1}/README.md +0 -0
  22. {calkit_python-0.39.0 → calkit_python-0.39.1}/agent-plugin/.claude-plugin/plugin.json +0 -0
  23. {calkit_python-0.39.0 → calkit_python-0.39.1}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
  24. {calkit_python-0.39.0 → calkit_python-0.39.1}/agent-plugin/skills/conventions/SKILL.md +0 -0
  25. {calkit_python-0.39.0 → calkit_python-0.39.1}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
  26. {calkit_python-0.39.0 → calkit_python-0.39.1}/babel.config.js +0 -0
  27. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/__main__.py +0 -0
  28. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/calc.py +0 -0
  29. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/check.py +0 -0
  30. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/__init__.py +0 -0
  31. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/check.py +0 -0
  32. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/cloud.py +0 -0
  33. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/config.py +0 -0
  34. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/core.py +0 -0
  35. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/describe.py +0 -0
  36. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/dev.py +0 -0
  37. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/import_.py +0 -0
  38. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/latex.py +0 -0
  39. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/list.py +0 -0
  40. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/main/__init__.py +0 -0
  41. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/main/xr.py +0 -0
  42. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/new.py +0 -0
  43. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/notebooks.py +0 -0
  44. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/office.py +0 -0
  45. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/overleaf.py +0 -0
  46. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/slurm.py +0 -0
  47. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cli/update.py +0 -0
  48. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/cloud.py +0 -0
  49. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/conda.py +0 -0
  50. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/config.py +0 -0
  51. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/core.py +0 -0
  52. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/datasets.py +0 -0
  53. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/detect.py +0 -0
  54. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/docker.py +0 -0
  55. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/dvc/__init__.py +0 -0
  56. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/dvc/core.py +0 -0
  57. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/environments.py +0 -0
  58. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/fs.py +0 -0
  59. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/git.py +0 -0
  60. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/github.py +0 -0
  61. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/gui.py +0 -0
  62. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/invenio.py +0 -0
  63. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/julia.py +0 -0
  64. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/jupyter.py +0 -0
  65. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/jupyterlab/__init__.py +0 -0
  66. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/jupyterlab/routes.py +0 -0
  67. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/package.json +0 -0
  68. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  69. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  70. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  71. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  72. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  73. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  74. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  75. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  76. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  77. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/remoteEntry.65469af996e7a96aa983.js +0 -0
  78. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/style.js +0 -0
  79. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/labextension/static/third-party-licenses.json +0 -0
  80. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/licenses.py +0 -0
  81. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/magics.py +0 -0
  82. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/matlab.py +0 -0
  83. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/models/__init__.py +0 -0
  84. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/models/core.py +0 -0
  85. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/models/io.py +0 -0
  86. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/models/iteration.py +0 -0
  87. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/models/pipeline.py +0 -0
  88. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/notebooks.py +0 -0
  89. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/office.py +0 -0
  90. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/ops.py +0 -0
  91. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/overleaf.py +0 -0
  92. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/releases.py +0 -0
  93. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/server.py +0 -0
  94. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/__init__.py +0 -0
  95. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/core.py +0 -0
  96. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/__init__.py +0 -0
  97. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/article/paper.tex +0 -0
  98. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/core.py +0 -0
  99. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/jfm/jfm.bst +0 -0
  100. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/jfm/jfm.cls +0 -0
  101. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  102. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/jfm/paper.tex +0 -0
  103. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/templates/latex/jfm/upmath.sty +0 -0
  104. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/__init__.py +0 -0
  105. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/__init__.py +0 -0
  106. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/main/__init__.py +0 -0
  107. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/main/test_core.py +0 -0
  108. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/main/test_subprojects.py +0 -0
  109. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/main/test_xr.py +0 -0
  110. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_check.py +0 -0
  111. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_cloud.py +0 -0
  112. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_config.py +0 -0
  113. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_import.py +0 -0
  114. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_latex.py +0 -0
  115. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_list.py +0 -0
  116. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_new.py +0 -0
  117. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_notebooks.py +0 -0
  118. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_overleaf.py +0 -0
  119. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/cli/test_update.py +0 -0
  120. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/dvc/__init__.py +0 -0
  121. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/dvc/test_core.py +0 -0
  122. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/dvc/test_zip.py +0 -0
  123. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/jupyterlab/__init__.py +0 -0
  124. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/jupyterlab/test_routes.py +0 -0
  125. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/models/__init__.py +0 -0
  126. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/models/test_iteration.py +0 -0
  127. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/models/test_pipeline.py +0 -0
  128. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_calc.py +0 -0
  129. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_check.py +0 -0
  130. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_cloud.py +0 -0
  131. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_conda.py +0 -0
  132. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_core.py +0 -0
  133. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_detect.py +0 -0
  134. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_docker.py +0 -0
  135. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_environments.py +0 -0
  136. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_fs.py +0 -0
  137. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_git.py +0 -0
  138. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_invenio.py +0 -0
  139. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_julia.py +0 -0
  140. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_jupyter.py +0 -0
  141. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_magics.py +0 -0
  142. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_matlab.py +0 -0
  143. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_notebooks.py +0 -0
  144. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_releases.py +0 -0
  145. {calkit_python-0.39.0 → calkit_python-0.39.1}/calkit/tests/test_templates.py +0 -0
  146. {calkit_python-0.39.0 → calkit_python-0.39.1}/conftest.py +0 -0
  147. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/CNAME +0 -0
  148. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/ai-tools.md +0 -0
  149. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/apps.md +0 -0
  150. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/calculations.md +0 -0
  151. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/calkit-yaml.md +0 -0
  152. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/cli-reference.md +0 -0
  153. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/cloud-integration.md +0 -0
  154. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/datasets.md +0 -0
  155. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/dependencies.md +0 -0
  156. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/environments.md +0 -0
  157. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/examples.md +0 -0
  158. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/governance.md +0 -0
  159. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/help.md +0 -0
  160. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/c-to-the-k-white.svg +0 -0
  161. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/calkit-fragmentation-compendium.png +0 -0
  162. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/calkit-no-bg.png +0 -0
  163. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/connect-zenodo.png +0 -0
  164. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/jupyterlab/all-green.png +0 -0
  165. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  166. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/jupyterlab/new-env.png +0 -0
  167. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/jupyterlab/new-notebook.png +0 -0
  168. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  169. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/jupyterlab-params.png +0 -0
  170. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/plos-osi-code-2024-03.png +0 -0
  171. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/img/vscode-nb-params.png +0 -0
  172. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/index.md +0 -0
  173. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/installation.md +0 -0
  174. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/jupyterlab.md +0 -0
  175. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/local-server.md +0 -0
  176. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/notebooks.md +0 -0
  177. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/overleaf.md +0 -0
  178. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/pipeline/index.md +0 -0
  179. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/pipeline/manual-steps.md +0 -0
  180. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/pipeline/running-and-logging.md +0 -0
  181. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/pipeline/slurm.md +0 -0
  182. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/quickstart.md +0 -0
  183. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/references.md +0 -0
  184. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/releases.md +0 -0
  185. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/reproducibility.md +0 -0
  186. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/subprojects.md +0 -0
  187. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  188. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/conda-envs.md +0 -0
  189. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/existing-project.md +0 -0
  190. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/first-project.md +0 -0
  191. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/github-actions.md +0 -0
  192. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  193. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  194. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  195. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  196. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  197. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  198. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  199. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  200. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  201. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  202. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  203. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  204. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  205. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  206. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  207. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/create-project.png +0 -0
  208. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  209. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/excel-chart.png +0 -0
  210. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/excel-data.png +0 -0
  211. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  212. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/needs-clone.png +0 -0
  213. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/new-stage.png +0 -0
  214. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  215. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  216. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/status-more-rows.png +0 -0
  217. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  218. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/untracked-data.png +0 -0
  219. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/updated-publication.png +0 -0
  220. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  221. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/office/workflow-page.png +0 -0
  222. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/clone.png +0 -0
  223. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/create-project.png +0 -0
  224. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  225. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  226. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  227. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/new-token.png +0 -0
  228. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/reclone.png +0 -0
  229. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  230. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/quick-actions.png +0 -0
  231. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/run-proc.png +0 -0
  232. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
  233. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
  234. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
  235. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
  236. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
  237. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
  238. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
  239. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/index.md +0 -0
  240. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/jupyterlab.md +0 -0
  241. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/latex-codespaces.md +0 -0
  242. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/matlab.md +0 -0
  243. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/notebook-pipeline.md +0 -0
  244. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/office.md +0 -0
  245. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/openfoam.md +0 -0
  246. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/procedures.md +0 -0
  247. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/tutorials/vscode-slurm-notebook.md +0 -0
  248. {calkit_python-0.39.0 → calkit_python-0.39.1}/docs/version-control.md +0 -0
  249. {calkit_python-0.39.0 → calkit_python-0.39.1}/install.json +0 -0
  250. {calkit_python-0.39.0 → calkit_python-0.39.1}/jest.config.js +0 -0
  251. {calkit_python-0.39.0 → calkit_python-0.39.1}/jupyter-config/server-config/calkit.json +0 -0
  252. {calkit_python-0.39.0 → calkit_python-0.39.1}/mkdocs.yml +0 -0
  253. {calkit_python-0.39.0 → calkit_python-0.39.1}/package.json +0 -0
  254. {calkit_python-0.39.0 → calkit_python-0.39.1}/pyproject.toml +0 -0
  255. {calkit_python-0.39.0 → calkit_python-0.39.1}/schema/plugin.json +0 -0
  256. {calkit_python-0.39.0 → calkit_python-0.39.1}/scripts/generate-docs-references.py +0 -0
  257. {calkit_python-0.39.0 → calkit_python-0.39.1}/scripts/install.ps1 +0 -0
  258. {calkit_python-0.39.0 → calkit_python-0.39.1}/scripts/install.sh +0 -0
  259. {calkit_python-0.39.0 → calkit_python-0.39.1}/scripts/make-calk9.sh +0 -0
  260. {calkit_python-0.39.0 → calkit_python-0.39.1}/scripts/sync-docs.py +0 -0
  261. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/__tests__/useQueries.spec.ts +0 -0
  262. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/calkit-config.ts +0 -0
  263. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/cell-output-marker.ts +0 -0
  264. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/commit-dialog.tsx +0 -0
  265. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/environment-editor.tsx +0 -0
  266. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/notebook-registration.tsx +0 -0
  267. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/notebook-toolbar.tsx +0 -0
  268. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/pipeline-status-bar.tsx +0 -0
  269. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/project-info-editor.tsx +0 -0
  270. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/sidebar-settings.tsx +0 -0
  271. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/sidebar.tsx +0 -0
  272. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/components/stage-editor.tsx +0 -0
  273. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/feature-flags.ts +0 -0
  274. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/file-browser-menu.ts +0 -0
  275. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  276. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/hooks/useQueries.ts +0 -0
  277. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/icons.ts +0 -0
  278. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/index.ts +0 -0
  279. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/io-tracker.ts +0 -0
  280. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/pipeline-state.ts +0 -0
  281. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/queryClient.ts +0 -0
  282. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/request.ts +0 -0
  283. {calkit_python-0.39.0 → calkit_python-0.39.1}/src/shims-mainmenu.d.ts +0 -0
  284. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/base.css +0 -0
  285. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/cell-output-marker.css +0 -0
  286. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/environment-editor.css +0 -0
  287. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/environment-selector.css +0 -0
  288. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/img/calkit-no-bg.png +0 -0
  289. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/index.css +0 -0
  290. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/index.js +0 -0
  291. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/notebook-toolbar.css +0 -0
  292. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/pipeline-status-bar.css +0 -0
  293. {calkit_python-0.39.0 → calkit_python-0.39.1}/style/sidebar.css +0 -0
  294. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  295. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/nb-julia.ipynb +0 -0
  296. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/nb-params.ipynb +0 -0
  297. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/nb-subdir.ipynb +0 -0
  298. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/pipeline.ipynb +0 -0
  299. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/script.py +0 -0
  300. {calkit_python-0.39.0 → calkit_python-0.39.1}/test/test-log.log +0 -0
  301. {calkit_python-0.39.0 → calkit_python-0.39.1}/tsconfig.json +0 -0
  302. {calkit_python-0.39.0 → calkit_python-0.39.1}/tsconfig.test.json +0 -0
  303. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/.gitignore +0 -0
  304. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/README.md +0 -0
  305. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/jupyter_server_test_config.py +0 -0
  306. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/package.json +0 -0
  307. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/playwright.config.js +0 -0
  308. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/tests/calkit.spec.ts +0 -0
  309. {calkit_python-0.39.0 → calkit_python-0.39.1}/ui-tests/yarn.lock +0 -0
  310. {calkit_python-0.39.0 → calkit_python-0.39.1}/uv.lock +0 -0
  311. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/.gitignore +0 -0
  312. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/.vscodeignore +0 -0
  313. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/CHANGELOG.md +0 -0
  314. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/LICENSE +0 -0
  315. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/README.md +0 -0
  316. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/images/calkit-no-bg.png +0 -0
  317. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/package-lock.json +0 -0
  318. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/package.json +0 -0
  319. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/scripts/set-proposed-api.js +0 -0
  320. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/environments.ts +0 -0
  321. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/extension.ts +0 -0
  322. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/notebooks.ts +0 -0
  323. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/test/environments.test.ts +0 -0
  324. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
  325. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/test/notebooks.test.ts +0 -0
  326. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/src/types.ts +0 -0
  327. {calkit_python-0.39.0 → calkit_python-0.39.1}/vscode-ext/tsconfig.json +0 -0
  328. {calkit_python-0.39.0 → calkit_python-0.39.1}/yarn.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.39.0
3
+ Version: 0.39.1
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -1,9 +1,47 @@
1
+ from __future__ import annotations
2
+
1
3
  from importlib.metadata import version as _version
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  __version__ = _version("calkit-python")
4
7
 
5
8
  from .core import * # noqa: F403, I001
6
9
 
10
+ if TYPE_CHECKING:
11
+ from calkit import ( # noqa: F401
12
+ calc,
13
+ check,
14
+ cloud,
15
+ conda,
16
+ config,
17
+ datasets,
18
+ detect,
19
+ docker,
20
+ dvc,
21
+ environments,
22
+ fs,
23
+ git,
24
+ github,
25
+ gui,
26
+ invenio,
27
+ julia,
28
+ jupyter,
29
+ licenses,
30
+ magics,
31
+ matlab,
32
+ models,
33
+ notebooks,
34
+ office,
35
+ ops,
36
+ overleaf,
37
+ pipeline,
38
+ releases,
39
+ server,
40
+ templates,
41
+ )
42
+
43
+ from .notebooks import declare_notebook # noqa: F401
44
+
7
45
  # Lazy-load submodules to speed up imports
8
46
  _SUBMODULES = {
9
47
  "git",
@@ -1053,10 +1053,32 @@ def pull(
1053
1053
  if calkit.dvc.detect_calkit_remote_type(name, url) == "http":
1054
1054
  typer.echo(f"Checking authentication for DVC remote: {name}")
1055
1055
  calkit.dvc.set_remote_auth(remote_name=name)
1056
+ if (
1057
+ not no_recursive
1058
+ and "--recursive" not in dvc_args
1059
+ and "-R" not in dvc_args
1060
+ ):
1061
+ dvc_args.append("--recursive")
1056
1062
  result = calkit.dvc.run_dvc_command(["pull"] + dvc_args)
1057
1063
  if result != 0:
1058
1064
  raise_error("DVC pull failed")
1059
1065
  calkit.dvc.zip.sync_all(direction="to-workspace")
1066
+ if not no_recursive:
1067
+ # Pull DVC in isolated subprojects (those with their own .dvc folder)
1068
+ ck_info = calkit.load_calkit_info()
1069
+ for sp in ck_info.get("subprojects", []):
1070
+ if not isinstance(sp, dict) or not sp.get("path"):
1071
+ continue
1072
+ sp_path = sp["path"]
1073
+ if not os.path.isdir(os.path.join(sp_path, ".dvc")):
1074
+ continue
1075
+ typer.echo(f"DVC pulling subproject: {sp_path}")
1076
+ sp_result = calkit.dvc.run_dvc_command(
1077
+ ["pull"] + dvc_args, cwd=sp_path
1078
+ )
1079
+ if sp_result != 0:
1080
+ raise_error(f"DVC pull failed for subproject: {sp_path}")
1081
+ calkit.dvc.zip.sync_all(direction="to-workspace", wdir=sp_path)
1060
1082
 
1061
1083
 
1062
1084
  @app.command(name="push")
@@ -46,14 +46,19 @@ ZIP_COMPRESS_LEVEL = 1
46
46
  ZIP_USE_SYSTEM_CLI = False
47
47
 
48
48
 
49
- def _check_local_dir() -> Path:
50
- if not os.path.isdir(LOCAL_DIR):
51
- os.makedirs(LOCAL_DIR, exist_ok=True)
52
- gitignore_path = os.path.join(LOCAL_DIR, ".gitignore")
49
+ def _local_dir(wdir: str | None = None) -> str:
50
+ return os.path.join(wdir, LOCAL_DIR) if wdir else LOCAL_DIR
51
+
52
+
53
+ def _check_local_dir(wdir: str | None = None) -> Path:
54
+ local_dir = _local_dir(wdir)
55
+ if not os.path.isdir(local_dir):
56
+ os.makedirs(local_dir, exist_ok=True)
57
+ gitignore_path = os.path.join(local_dir, ".gitignore")
53
58
  if not os.path.isfile(gitignore_path):
54
59
  with open(gitignore_path, "w") as f:
55
60
  f.write("*\n")
56
- return Path(LOCAL_DIR)
61
+ return Path(local_dir)
57
62
 
58
63
 
59
64
  def is_zip_candidate(path: str) -> bool:
@@ -111,10 +116,15 @@ def make_zip_path(workspace_path: str) -> str:
111
116
  return os.path.join(ZIPS_DIR, "files", workspace_path + ".zip")
112
117
 
113
118
 
114
- def get_zip_path_map() -> dict[str, str]:
119
+ def _path_map_path(wdir: str | None = None) -> str:
120
+ return os.path.join(wdir, PATH_MAP_PATH) if wdir else PATH_MAP_PATH
121
+
122
+
123
+ def get_zip_path_map(wdir: str | None = None) -> dict[str, str]:
115
124
  """Get a mapping of input paths to zip paths."""
116
- if os.path.isfile(PATH_MAP_PATH):
117
- with open(PATH_MAP_PATH, "r") as f:
125
+ path = _path_map_path(wdir)
126
+ if os.path.isfile(path):
127
+ with open(path, "r") as f:
118
128
  return json.load(f)
119
129
  return {}
120
130
 
@@ -207,19 +217,22 @@ def calc_dir_sig(path: str) -> str:
207
217
  return f"{file_count}-{total_size}-{latest_mtime}"
208
218
 
209
219
 
210
- def get_hash(path: str, alg="md5") -> str | None:
220
+ def get_hash(path: str, alg="md5", wdir: str | None = None) -> str | None:
211
221
  """Get the hash of a path, using/updating the cache if applicable."""
212
222
  try:
213
223
  st = os.stat(path)
214
224
  except OSError:
215
225
  return None
216
- _check_local_dir()
226
+ _check_local_dir(wdir)
217
227
  # Normalize path as posix
218
228
  path = Path(path).as_posix()
219
229
  mtime = st.st_mtime_ns
220
230
  size = st.st_size
221
231
  dir_sig = calc_dir_sig(path)
222
- with SqliteDict(HASH_CACHE_PATH) as cache:
232
+ hash_cache_path = (
233
+ os.path.join(wdir, HASH_CACHE_PATH) if wdir else HASH_CACHE_PATH
234
+ )
235
+ with SqliteDict(hash_cache_path) as cache:
223
236
  record = None
224
237
  raw = cache.get(path)
225
238
  if raw is not None:
@@ -250,38 +263,44 @@ def get_hash(path: str, alg="md5") -> str | None:
250
263
  return hash_val
251
264
 
252
265
 
253
- def get_sync_record(workspace_path: str) -> SyncRecord | None:
266
+ def _sync_records_path(wdir: str | None = None) -> str:
267
+ return os.path.join(wdir, SYNC_RECORDS_PATH) if wdir else SYNC_RECORDS_PATH
268
+
269
+
270
+ def get_sync_record(
271
+ workspace_path: str, wdir: str | None = None
272
+ ) -> SyncRecord | None:
254
273
  """Get a sync record for a given workspace path."""
255
- _check_local_dir()
256
- with SqliteDict(SYNC_RECORDS_PATH) as db:
274
+ _check_local_dir(wdir)
275
+ with SqliteDict(_sync_records_path(wdir)) as db:
257
276
  raw = db.get(workspace_path)
258
277
  if raw is not None:
259
278
  return SyncRecord.model_validate(raw)
260
279
  return None
261
280
 
262
281
 
263
- def write_sync_record(record: SyncRecord):
282
+ def write_sync_record(record: SyncRecord, wdir: str | None = None):
264
283
  """Write a sync record."""
265
- _check_local_dir()
266
- with SqliteDict(SYNC_RECORDS_PATH) as db:
284
+ _check_local_dir(wdir)
285
+ with SqliteDict(_sync_records_path(wdir)) as db:
267
286
  db[record.workspace_path] = record.model_dump()
268
287
  db.commit()
269
288
 
270
289
 
271
- def delete_sync_record(workspace_path: str):
290
+ def delete_sync_record(workspace_path: str, wdir: str | None = None):
272
291
  """Delete a sync record."""
273
- _check_local_dir()
274
- with SqliteDict(SYNC_RECORDS_PATH) as db:
292
+ _check_local_dir(wdir)
293
+ with SqliteDict(_sync_records_path(wdir)) as db:
275
294
  if workspace_path in db:
276
295
  del db[workspace_path]
277
296
  db.commit()
278
297
 
279
298
 
280
- def cleanup_sync_records():
299
+ def cleanup_sync_records(wdir: str | None = None):
281
300
  """Remove sync records for paths no longer in the path map."""
282
- _check_local_dir()
283
- pm = get_zip_path_map()
284
- with SqliteDict(SYNC_RECORDS_PATH) as db:
301
+ _check_local_dir(wdir)
302
+ pm = get_zip_path_map(wdir)
303
+ with SqliteDict(_sync_records_path(wdir)) as db:
285
304
  stale = [k for k in db if k not in pm]
286
305
  if stale:
287
306
  for k in stale:
@@ -289,8 +308,8 @@ def cleanup_sync_records():
289
308
  db.commit()
290
309
 
291
310
 
292
- def get_zip_path(workspace_path: str) -> str:
293
- pm = get_zip_path_map()
311
+ def get_zip_path(workspace_path: str, wdir: str | None = None) -> str:
312
+ pm = get_zip_path_map(wdir)
294
313
  workspace_path = Path(workspace_path).as_posix()
295
314
  if workspace_path in pm:
296
315
  return pm[workspace_path]
@@ -408,14 +427,15 @@ class SyncStatus(BaseModel):
408
427
 
409
428
 
410
429
  def get_sync_status(
411
- workspace_path: str, zip_path: str | None = None
430
+ workspace_path: str,
431
+ zip_path: str | None = None,
432
+ wdir: str | None = None,
412
433
  ) -> SyncStatus:
413
- # First get cached information and see if we need to rehash
414
- workspace_hash = get_hash(workspace_path)
434
+ workspace_hash = get_hash(workspace_path, wdir=wdir)
415
435
  if zip_path is None:
416
- zip_path = get_zip_path(workspace_path)
417
- zip_hash = get_hash(zip_path)
418
- last_sync_record = get_sync_record(workspace_path)
436
+ zip_path = get_zip_path(workspace_path, wdir=wdir)
437
+ zip_hash = get_hash(zip_path, wdir=wdir)
438
+ last_sync_record = get_sync_record(workspace_path, wdir=wdir)
419
439
  if last_sync_record is not None:
420
440
  workspace_changed = workspace_hash != last_sync_record.workspace_hash
421
441
  zip_changed = zip_hash != last_sync_record.zip_hash
@@ -439,9 +459,23 @@ def sync_one(
439
459
  workspace_path: str,
440
460
  zip_path: str | None = None,
441
461
  direction: Literal["to-zip", "to-workspace", "both"] = "both",
462
+ wdir: str | None = None,
442
463
  ) -> SyncRecord | None:
443
464
  """Process a single zip."""
444
- status = get_sync_status(workspace_path, zip_path)
465
+ if wdir:
466
+ workspace_path = os.path.join(wdir, workspace_path)
467
+ if zip_path is not None:
468
+ zip_path = os.path.join(wdir, zip_path)
469
+ return _sync_one(workspace_path, zip_path, direction, wdir=wdir)
470
+
471
+
472
+ def _sync_one(
473
+ workspace_path: str,
474
+ zip_path: str | None = None,
475
+ direction: Literal["to-zip", "to-workspace", "both"] = "both",
476
+ wdir: str | None = None,
477
+ ) -> SyncRecord | None:
478
+ status = get_sync_status(workspace_path, zip_path, wdir=wdir)
445
479
  workspace_changed = status.workspace_changed
446
480
  zip_changed = status.zip_changed
447
481
  workspace_hash = status.workspace_hash
@@ -454,7 +488,7 @@ def sync_one(
454
488
  # Both deleted — clear the stale sync record so a future recreated side
455
489
  # is treated as a fresh first sync rather than a spurious conflict
456
490
  if workspace_deleted and zip_deleted:
457
- delete_sync_record(workspace_path)
491
+ delete_sync_record(workspace_path, wdir=wdir)
458
492
  return None
459
493
  # Neither side exists and no sync record — nothing to do (e.g., a
460
494
  # pipeline output that hasn't been produced yet on a fresh run)
@@ -478,28 +512,28 @@ def sync_one(
478
512
  if os.path.exists(zip_path):
479
513
  typer.echo(f"Deleting '{zip_path}' (workspace was deleted)")
480
514
  os.remove(zip_path)
481
- delete_sync_record(workspace_path)
515
+ delete_sync_record(workspace_path, wdir=wdir)
482
516
  return None
483
517
  # Propagate zip deletion to workspace
484
518
  if zip_deleted and direction in ["to-workspace", "both"]:
485
519
  if os.path.exists(workspace_path):
486
520
  typer.echo(f"Deleting '{workspace_path}' (zip was deleted)")
487
521
  shutil.rmtree(workspace_path)
488
- delete_sync_record(workspace_path)
522
+ delete_sync_record(workspace_path, wdir=wdir)
489
523
  return None
490
524
  # Zip was deleted but workspace exists and direction is to-zip:
491
525
  # restore zip
492
526
  if zip_deleted and direction == "to-zip":
493
527
  typer.echo(f"Rezipping '{workspace_path}' (zip was deleted)")
494
528
  zip_(workspace_path=workspace_path, zip_path=zip_path)
495
- run_dvc_command(["add", zip_path])
496
- zip_hash = get_hash(zip_path)
529
+ run_dvc_command(["add", zip_path], cwd=wdir)
530
+ zip_hash = get_hash(zip_path, wdir=wdir)
497
531
  # Workspace was deleted but zip exists and direction is to-workspace:
498
532
  # restore workspace
499
533
  if workspace_deleted and direction == "to-workspace":
500
534
  typer.echo(f"Unzipping to '{workspace_path}' (workspace was deleted)")
501
535
  unzip(workspace_path=workspace_path, zip_path=zip_path)
502
- workspace_hash = get_hash(workspace_path)
536
+ workspace_hash = get_hash(workspace_path, wdir=wdir)
503
537
  # If hashes have changed since last check, we need to synchronize the
504
538
  # path with its zip file (unzip if zip is newer, rezip if path is newer)
505
539
  # If both have changed, we have a conflict and the user needs to decide
@@ -514,13 +548,13 @@ def sync_one(
514
548
  if workspace_changed and (direction in ["to-zip", "both"]):
515
549
  typer.echo(f"Zipping '{workspace_path}' (workspace has changed)")
516
550
  zip_(workspace_path=workspace_path, zip_path=zip_path)
517
- run_dvc_command(["add", zip_path])
518
- zip_hash = get_hash(zip_path)
551
+ run_dvc_command(["add", zip_path], cwd=wdir)
552
+ zip_hash = get_hash(zip_path, wdir=wdir)
519
553
  # If we unzip, we need to update the hash
520
554
  if zip_changed and (direction in ["to-workspace", "both"]):
521
555
  typer.echo(f"Unzipping to '{workspace_path}' (zip has changed)")
522
556
  unzip(workspace_path=workspace_path, zip_path=zip_path)
523
- workspace_hash = get_hash(workspace_path)
557
+ workspace_hash = get_hash(workspace_path, wdir=wdir)
524
558
  assert workspace_hash is not None and zip_hash is not None
525
559
  record = SyncRecord(
526
560
  workspace_path=workspace_path,
@@ -529,7 +563,7 @@ def sync_one(
529
563
  workspace_hash=workspace_hash,
530
564
  zip_hash=zip_hash,
531
565
  )
532
- write_sync_record(record)
566
+ write_sync_record(record, wdir=wdir)
533
567
  return record
534
568
 
535
569
 
@@ -547,11 +581,15 @@ def sync_some(
547
581
  )
548
582
 
549
583
 
550
- def sync_all(direction: Literal["to-zip", "to-workspace", "both"] = "both"):
584
+ def sync_all(
585
+ direction: Literal["to-zip", "to-workspace", "both"] = "both",
586
+ wdir: str | None = None,
587
+ ):
551
588
  """Process all project zips."""
552
- for workspace_path, zip_path in get_zip_path_map().items():
589
+ for workspace_path, zip_path in get_zip_path_map(wdir=wdir).items():
553
590
  sync_one(
554
591
  workspace_path=workspace_path,
555
592
  zip_path=zip_path,
556
593
  direction=direction,
594
+ wdir=wdir,
557
595
  )
@@ -311,6 +311,98 @@ def _expand_matrix(input_dict: dict[str, list]) -> list[dict]:
311
311
  return final_list
312
312
 
313
313
 
314
+ def _paths_overlap(a: str, b: str) -> bool:
315
+ """Return True if path `a` and path `b` overlap in the file tree.
316
+
317
+ Two paths overlap if they are equal or one is an ancestor directory of the
318
+ other, e.g. ``data/`` overlaps with ``data/file.csv``.
319
+ """
320
+ a = a.rstrip("/")
321
+ b = b.rstrip("/")
322
+ if a == b:
323
+ return True
324
+ return b.startswith(a + "/") or a.startswith(b + "/")
325
+
326
+
327
+ def collapse_dvc_stages(
328
+ stages: dict,
329
+ cmd: str | None = None,
330
+ wdir: str | None = None,
331
+ desc: str | None = None,
332
+ ) -> dict:
333
+ """Collapse a dict of DVC stages into a single wrapper stage.
334
+
335
+ Collects all deps and outs from ``stages``, expands matrix templates, and
336
+ returns a complete DVC stage dict. ``cmd``, ``wdir``, and ``desc`` are
337
+ optional; when provided they are included in the returned dict.
338
+
339
+ Rules applied:
340
+ - A dep that tree-overlaps any out is dropped (e.g. a folder dep that
341
+ contains output files would cause a DVC conflict).
342
+ - An out that tree-overlaps any remaining dep is also dropped (covers
343
+ exact-match aliasing and folder/child relationships).
344
+ - Outputs are wrapped with ``{cache: false, persist: true}`` so the parent
345
+ doesn't double-cache files already managed by the subproject's DVC.
346
+ """
347
+ all_outs: set[str] = set()
348
+ all_deps: set[str] = set()
349
+ for stage_cfg in stages.values():
350
+ matrix = stage_cfg.get("matrix")
351
+ replacements = _expand_matrix(matrix) if matrix else [{}]
352
+ for out in stage_cfg.get("outs", []):
353
+ raw = out if isinstance(out, str) else list(out.keys())[0]
354
+ for r in replacements:
355
+ expanded = raw
356
+ for var, val in r.items():
357
+ expanded = expanded.replace(f"${{item.{var}}}", str(val))
358
+ all_outs.add(Path(expanded).as_posix())
359
+ for dep in stage_cfg.get("deps", []):
360
+ if not isinstance(dep, str):
361
+ continue
362
+ for r in replacements:
363
+ expanded = dep
364
+ for var, val in r.items():
365
+ expanded = expanded.replace(f"${{item.{var}}}", str(val))
366
+ all_deps.add(Path(expanded).as_posix())
367
+ external_deps = all_deps - all_outs
368
+ outs_raw = sorted(all_outs)
369
+ # Track whether any dep is dropped because it is a strict folder ancestor
370
+ # of an output (not an exact match — those are already removed by the set
371
+ # subtraction above). When this happens the wrapper cannot fully express
372
+ # the input boundary, so we mark it always_changed.
373
+ # A dep is a strict ancestor of an out when the out path starts with the
374
+ # dep path followed by "/". The reverse (dep inside an output folder) is
375
+ # just a normal internal path drop and does not require always_changed.
376
+ has_ancestor_dep_drop = any(
377
+ o.startswith(d.rstrip("/") + "/")
378
+ for d in external_deps
379
+ for o in outs_raw
380
+ )
381
+ deps = sorted(
382
+ d
383
+ for d in external_deps
384
+ if not any(_paths_overlap(d, o) for o in outs_raw)
385
+ )
386
+ dep_set = set(deps)
387
+ outs = [
388
+ {o: {"cache": False, "persist": True}}
389
+ for o in outs_raw
390
+ if not any(_paths_overlap(o, d) for d in dep_set)
391
+ ]
392
+ stage: dict = {}
393
+ if cmd is not None:
394
+ stage["cmd"] = cmd
395
+ if wdir is not None:
396
+ stage["wdir"] = wdir
397
+ if has_ancestor_dep_drop:
398
+ stage["always_changed"] = True
399
+ stage["deps"] = deps
400
+ stage["outs"] = outs
401
+ if desc is not None:
402
+ stage["desc"] = desc
403
+ return stage
404
+
405
+
314
406
  def get_status(
315
407
  ck_info: dict | None = None,
316
408
  targets: list[str] | None = None,
@@ -617,6 +709,8 @@ def to_dvc(
617
709
  dvc.yaml files in the tree, so no synthetic parent stages are needed.
618
710
 
619
711
  Returns a dictionary of DVC stages for the `stages` key of a dvc.yaml.
712
+ Returns ``{}`` when the project has no pipeline and no subprojects (e.g.
713
+ a subproject that is a library with no defined pipeline stages).
620
714
  """
621
715
  import calkit.dvc.zip
622
716
  from calkit.environments import get_env_lock_fpath
@@ -624,7 +718,7 @@ def to_dvc(
624
718
  if ck_info is None:
625
719
  ck_info = calkit.load_calkit_info(wdir=wdir)
626
720
  if "pipeline" not in ck_info and "subprojects" not in ck_info:
627
- raise ValueError("No pipeline or subprojects found in calkit.yaml")
721
+ return {}
628
722
  # Compile subproject pipelines recursively.
629
723
  # For isolated subprojects (those with their own .dvc/ directory), DVC
630
724
  # won't cross the .dvc/ boundary during --all-pipelines discovery, so we
@@ -661,67 +755,22 @@ def to_dvc(
661
755
  stacklevel=2,
662
756
  )
663
757
  continue
664
- # Collect all outputs and all deps from the subproject's compiled stages.
665
- # For matrix stages the template strings (${item.foo}) must be expanded
666
- # so the wrapper stage has concrete paths.
667
- all_sp_outs: set[str] = set()
668
- all_sp_deps: set[str] = set()
669
- for stage_cfg in sp_dvc_stages.values():
670
- matrix = stage_cfg.get("matrix")
671
- replacements = _expand_matrix(matrix) if matrix else [{}]
672
- for out in stage_cfg.get("outs", []):
673
- raw = out if isinstance(out, str) else list(out.keys())[0]
674
- for r in replacements:
675
- expanded = raw
676
- for var, val in r.items():
677
- expanded = expanded.replace(
678
- f"${{item.{var}}}", str(val)
679
- )
680
- # Normalize to strip leading "./" so "data.txt" and
681
- # "./data.txt" are treated as the same path.
682
- all_sp_outs.add(Path(expanded).as_posix())
683
- for dep in stage_cfg.get("deps", []):
684
- if not isinstance(dep, str):
685
- continue
686
- for r in replacements:
687
- expanded = dep
688
- for var, val in r.items():
689
- expanded = expanded.replace(
690
- f"${{item.{var}}}", str(val)
691
- )
692
- all_sp_deps.add(Path(expanded).as_posix())
693
- # External deps are inputs the subproject reads from outside itself.
694
- external_deps = all_sp_deps - all_sp_outs
695
- # The wrapper stage has `wdir: sp`, so DVC resolves deps/outs relative
696
- # to that directory — do NOT prefix with the subproject path.
697
- wrapper_deps = sorted(
698
- d for d in external_deps if not d.startswith(".calkit/env-locks/")
699
- )
700
- # Wrap outputs with cache: false + persist: true so the parent doesn't
701
- # double-cache files already managed by the subproject's DVC, and the
702
- # nested dvc repro run is responsible for file persistence.
703
- wrapper_outs_raw = sorted(
704
- o for o in all_sp_outs if not o.startswith(".calkit/env-locks/")
705
- )
706
- # Defensive deduplication: if any output path also appears as a dep
707
- # (which can happen due to path aliasing), keep it as a dep only.
708
- wrapper_dep_set = set(wrapper_deps)
709
- wrapper_outs = [
710
- {o: {"cache": False, "persist": True}}
711
- for o in wrapper_outs_raw
712
- if o not in wrapper_dep_set
713
- ]
714
758
  sp_stage_name = Path(sp).name
715
- wrapper_stages[sp_stage_name] = {
716
- "cmd": "calkit dvc repro",
717
- "wdir": sp,
718
- "deps": wrapper_deps,
719
- "outs": wrapper_outs,
720
- "desc": (
759
+ wrapper = collapse_dvc_stages(
760
+ sp_dvc_stages,
761
+ cmd="calkit dvc repro",
762
+ wdir=sp,
763
+ desc=(
721
764
  f"Automatically generated wrapper for subproject '{sp}'. "
722
765
  "Changes made here will be overwritten."
723
766
  ),
724
- }
767
+ )
768
+ # calkit.yaml is the source of truth for the subproject pipeline;
769
+ # tracking it ensures the wrapper goes stale when the pipeline
770
+ # definition changes.
771
+ if "calkit.yaml" not in wrapper["deps"]:
772
+ wrapper["deps"] = sorted(wrapper["deps"] + ["calkit.yaml"])
773
+ wrapper_stages[sp_stage_name] = wrapper
725
774
  if "pipeline" not in ck_info:
726
775
  if write and wrapper_stages:
727
776
  dvc_yaml_fpath = (