calkit-python 0.41.1__tar.gz → 0.41.3__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 (335) hide show
  1. {calkit_python-0.41.1 → calkit_python-0.41.3}/.gitignore +1 -0
  2. {calkit_python-0.41.1 → calkit_python-0.41.3}/AGENTS.md +5 -0
  3. {calkit_python-0.41.1 → calkit_python-0.41.3}/PKG-INFO +1 -1
  4. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/core.py +8 -3
  5. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/main/core.py +310 -15
  6. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/scheduler.py +277 -58
  7. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/core.py +18 -0
  8. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/dependencies.py +3 -11
  9. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/dvc/core.py +42 -0
  10. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/environments.py +6 -0
  11. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/jupyterlab/routes.py +6 -3
  12. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/models/pipeline.py +16 -0
  13. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/pipeline.py +162 -5
  14. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/server.py +3 -5
  15. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/main/test_core.py +424 -0
  16. calkit_python-0.41.3/calkit/tests/cli/test_scheduler.py +163 -0
  17. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_environments.py +34 -0
  18. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_pipeline.py +225 -4
  19. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/hpc.md +61 -0
  20. {calkit_python-0.41.1 → calkit_python-0.41.3}/pyproject.toml +5 -0
  21. {calkit_python-0.41.1 → calkit_python-0.41.3}/uv.lock +44 -44
  22. {calkit_python-0.41.1 → calkit_python-0.41.3}/.claude-plugin/marketplace.json +0 -0
  23. {calkit_python-0.41.1 → calkit_python-0.41.3}/.pre-commit-config.yaml +0 -0
  24. {calkit_python-0.41.1 → calkit_python-0.41.3}/.prettierignore +0 -0
  25. {calkit_python-0.41.1 → calkit_python-0.41.3}/.python-version +0 -0
  26. {calkit_python-0.41.1 → calkit_python-0.41.3}/.vscode/launch.json +0 -0
  27. {calkit_python-0.41.1 → calkit_python-0.41.3}/.vscode/tasks.json +0 -0
  28. {calkit_python-0.41.1 → calkit_python-0.41.3}/.yarnrc.yml +0 -0
  29. {calkit_python-0.41.1 → calkit_python-0.41.3}/CITATION.cff +0 -0
  30. {calkit_python-0.41.1 → calkit_python-0.41.3}/CODE_OF_CONDUCT.md +0 -0
  31. {calkit_python-0.41.1 → calkit_python-0.41.3}/CONTRIBUTING.md +0 -0
  32. {calkit_python-0.41.1 → calkit_python-0.41.3}/LICENSE +0 -0
  33. {calkit_python-0.41.1 → calkit_python-0.41.3}/Makefile +0 -0
  34. {calkit_python-0.41.1 → calkit_python-0.41.3}/README.md +0 -0
  35. {calkit_python-0.41.1 → calkit_python-0.41.3}/agent-plugin/.claude-plugin/plugin.json +0 -0
  36. {calkit_python-0.41.1 → calkit_python-0.41.3}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
  37. {calkit_python-0.41.1 → calkit_python-0.41.3}/agent-plugin/skills/conventions/SKILL.md +0 -0
  38. {calkit_python-0.41.1 → calkit_python-0.41.3}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
  39. {calkit_python-0.41.1 → calkit_python-0.41.3}/babel.config.js +0 -0
  40. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/__init__.py +0 -0
  41. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/__main__.py +0 -0
  42. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/calc.py +0 -0
  43. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/check.py +0 -0
  44. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/__init__.py +0 -0
  45. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/check.py +0 -0
  46. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/cloud.py +0 -0
  47. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/config.py +0 -0
  48. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/describe.py +0 -0
  49. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/dev.py +0 -0
  50. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/import_.py +0 -0
  51. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/latex.py +0 -0
  52. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/list.py +0 -0
  53. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/main/__init__.py +0 -0
  54. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/main/xr.py +0 -0
  55. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/new.py +0 -0
  56. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/notebooks.py +0 -0
  57. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/office.py +0 -0
  58. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/overleaf.py +0 -0
  59. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cli/update.py +0 -0
  60. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/cloud.py +0 -0
  61. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/conda.py +0 -0
  62. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/config.py +0 -0
  63. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/datasets.py +0 -0
  64. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/detect.py +0 -0
  65. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/docker.py +0 -0
  66. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/dvc/__init__.py +0 -0
  67. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/dvc/zip.py +0 -0
  68. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/fs.py +0 -0
  69. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/git.py +0 -0
  70. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/github.py +0 -0
  71. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/gui.py +0 -0
  72. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/install.py +0 -0
  73. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/invenio.py +0 -0
  74. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/julia.py +0 -0
  75. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/jupyter.py +0 -0
  76. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/jupyterlab/__init__.py +0 -0
  77. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/package.json +0 -0
  78. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  79. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  80. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  81. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  82. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  83. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  84. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  85. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  86. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  87. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/remoteEntry.65469af996e7a96aa983.js +0 -0
  88. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/style.js +0 -0
  89. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/labextension/static/third-party-licenses.json +0 -0
  90. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/licenses.py +0 -0
  91. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/magics.py +0 -0
  92. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/matlab.py +0 -0
  93. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/models/__init__.py +0 -0
  94. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/models/core.py +0 -0
  95. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/models/io.py +0 -0
  96. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/models/iteration.py +0 -0
  97. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/notebooks.py +0 -0
  98. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/office.py +0 -0
  99. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/ops.py +0 -0
  100. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/overleaf.py +0 -0
  101. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/releases.py +0 -0
  102. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/__init__.py +0 -0
  103. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/core.py +0 -0
  104. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/__init__.py +0 -0
  105. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/article/paper.tex +0 -0
  106. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/core.py +0 -0
  107. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/jfm/jfm.bst +0 -0
  108. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/jfm/jfm.cls +0 -0
  109. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  110. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/jfm/paper.tex +0 -0
  111. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/templates/latex/jfm/upmath.sty +0 -0
  112. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/__init__.py +0 -0
  113. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/__init__.py +0 -0
  114. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/main/__init__.py +0 -0
  115. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/main/test_subprojects.py +0 -0
  116. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/main/test_xr.py +0 -0
  117. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_check.py +0 -0
  118. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_cloud.py +0 -0
  119. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_config.py +0 -0
  120. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_import.py +0 -0
  121. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_latex.py +0 -0
  122. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_list.py +0 -0
  123. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_new.py +0 -0
  124. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_notebooks.py +0 -0
  125. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_overleaf.py +0 -0
  126. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/cli/test_update.py +0 -0
  127. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/dvc/__init__.py +0 -0
  128. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/dvc/test_core.py +0 -0
  129. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/dvc/test_zip.py +0 -0
  130. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/jupyterlab/__init__.py +0 -0
  131. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/jupyterlab/test_routes.py +0 -0
  132. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/models/__init__.py +0 -0
  133. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/models/test_iteration.py +0 -0
  134. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/models/test_pipeline.py +0 -0
  135. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_calc.py +0 -0
  136. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_check.py +0 -0
  137. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_cloud.py +0 -0
  138. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_conda.py +0 -0
  139. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_core.py +0 -0
  140. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_dependencies.py +0 -0
  141. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_detect.py +0 -0
  142. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_docker.py +0 -0
  143. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_fs.py +0 -0
  144. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_git.py +0 -0
  145. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_install.py +0 -0
  146. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_invenio.py +0 -0
  147. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_julia.py +0 -0
  148. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_jupyter.py +0 -0
  149. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_magics.py +0 -0
  150. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_matlab.py +0 -0
  151. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_notebooks.py +0 -0
  152. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_releases.py +0 -0
  153. {calkit_python-0.41.1 → calkit_python-0.41.3}/calkit/tests/test_templates.py +0 -0
  154. {calkit_python-0.41.1 → calkit_python-0.41.3}/conftest.py +0 -0
  155. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/CNAME +0 -0
  156. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/ai-tools.md +0 -0
  157. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/apps.md +0 -0
  158. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/calculations.md +0 -0
  159. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/calkit-yaml.md +0 -0
  160. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/cli-reference.md +0 -0
  161. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/cloud-integration.md +0 -0
  162. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/datasets.md +0 -0
  163. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/dependencies.md +0 -0
  164. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/environments.md +0 -0
  165. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/examples.md +0 -0
  166. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/governance.md +0 -0
  167. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/help.md +0 -0
  168. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/c-to-the-k-white.svg +0 -0
  169. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/calkit-fragmentation-compendium.png +0 -0
  170. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/calkit-no-bg.png +0 -0
  171. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/connect-zenodo.png +0 -0
  172. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/jupyterlab/all-green.png +0 -0
  173. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  174. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/jupyterlab/new-env.png +0 -0
  175. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/jupyterlab/new-notebook.png +0 -0
  176. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  177. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/jupyterlab-params.png +0 -0
  178. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/plos-osi-code-2024-03.png +0 -0
  179. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/img/vscode-nb-params.png +0 -0
  180. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/index.md +0 -0
  181. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/installation.md +0 -0
  182. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/jupyterlab.md +0 -0
  183. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/local-server.md +0 -0
  184. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/notebooks.md +0 -0
  185. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/overleaf.md +0 -0
  186. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/pipeline/index.md +0 -0
  187. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/pipeline/manual-steps.md +0 -0
  188. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/pipeline/running-and-logging.md +0 -0
  189. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/quickstart.md +0 -0
  190. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/references.md +0 -0
  191. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/releases.md +0 -0
  192. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/reproducibility.md +0 -0
  193. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/subprojects.md +0 -0
  194. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  195. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/conda-envs.md +0 -0
  196. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/existing-project.md +0 -0
  197. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/first-project.md +0 -0
  198. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/github-actions.md +0 -0
  199. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  200. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  201. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  202. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  203. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  204. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  205. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  206. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  207. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  208. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  209. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  210. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  211. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  212. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  213. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  214. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/create-project.png +0 -0
  215. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  216. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/excel-chart.png +0 -0
  217. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/excel-data.png +0 -0
  218. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  219. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/needs-clone.png +0 -0
  220. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/new-stage.png +0 -0
  221. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  222. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  223. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/status-more-rows.png +0 -0
  224. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  225. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/untracked-data.png +0 -0
  226. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/updated-publication.png +0 -0
  227. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  228. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/office/workflow-page.png +0 -0
  229. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/clone.png +0 -0
  230. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/create-project.png +0 -0
  231. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  232. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  233. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  234. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/new-token.png +0 -0
  235. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/reclone.png +0 -0
  236. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  237. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/quick-actions.png +0 -0
  238. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/run-proc.png +0 -0
  239. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
  240. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
  241. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
  242. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
  243. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
  244. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
  245. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
  246. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/index.md +0 -0
  247. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/jupyterlab.md +0 -0
  248. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/latex-codespaces.md +0 -0
  249. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/matlab.md +0 -0
  250. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/notebook-pipeline.md +0 -0
  251. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/office.md +0 -0
  252. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/openfoam.md +0 -0
  253. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/procedures.md +0 -0
  254. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/tutorials/vscode-slurm-notebook.md +0 -0
  255. {calkit_python-0.41.1 → calkit_python-0.41.3}/docs/version-control.md +0 -0
  256. {calkit_python-0.41.1 → calkit_python-0.41.3}/flake.lock +0 -0
  257. {calkit_python-0.41.1 → calkit_python-0.41.3}/flake.nix +0 -0
  258. {calkit_python-0.41.1 → calkit_python-0.41.3}/install.json +0 -0
  259. {calkit_python-0.41.1 → calkit_python-0.41.3}/jest.config.js +0 -0
  260. {calkit_python-0.41.1 → calkit_python-0.41.3}/jupyter-config/server-config/calkit.json +0 -0
  261. {calkit_python-0.41.1 → calkit_python-0.41.3}/mkdocs.yml +0 -0
  262. {calkit_python-0.41.1 → calkit_python-0.41.3}/package.json +0 -0
  263. {calkit_python-0.41.1 → calkit_python-0.41.3}/schema/plugin.json +0 -0
  264. {calkit_python-0.41.1 → calkit_python-0.41.3}/scripts/generate-docs-references.py +0 -0
  265. {calkit_python-0.41.1 → calkit_python-0.41.3}/scripts/install.ps1 +0 -0
  266. {calkit_python-0.41.1 → calkit_python-0.41.3}/scripts/install.sh +0 -0
  267. {calkit_python-0.41.1 → calkit_python-0.41.3}/scripts/make-calk9.sh +0 -0
  268. {calkit_python-0.41.1 → calkit_python-0.41.3}/scripts/sync-docs.py +0 -0
  269. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/__tests__/useQueries.spec.ts +0 -0
  270. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/calkit-config.ts +0 -0
  271. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/cell-output-marker.ts +0 -0
  272. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/commit-dialog.tsx +0 -0
  273. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/environment-editor.tsx +0 -0
  274. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/notebook-registration.tsx +0 -0
  275. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/notebook-toolbar.tsx +0 -0
  276. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/pipeline-status-bar.tsx +0 -0
  277. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/project-info-editor.tsx +0 -0
  278. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/sidebar-settings.tsx +0 -0
  279. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/sidebar.tsx +0 -0
  280. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/components/stage-editor.tsx +0 -0
  281. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/feature-flags.ts +0 -0
  282. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/file-browser-menu.ts +0 -0
  283. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  284. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/hooks/useQueries.ts +0 -0
  285. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/icons.ts +0 -0
  286. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/index.ts +0 -0
  287. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/io-tracker.ts +0 -0
  288. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/pipeline-state.ts +0 -0
  289. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/queryClient.ts +0 -0
  290. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/request.ts +0 -0
  291. {calkit_python-0.41.1 → calkit_python-0.41.3}/src/shims-mainmenu.d.ts +0 -0
  292. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/base.css +0 -0
  293. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/cell-output-marker.css +0 -0
  294. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/environment-editor.css +0 -0
  295. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/environment-selector.css +0 -0
  296. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/img/calkit-no-bg.png +0 -0
  297. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/index.css +0 -0
  298. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/index.js +0 -0
  299. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/notebook-toolbar.css +0 -0
  300. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/pipeline-status-bar.css +0 -0
  301. {calkit_python-0.41.1 → calkit_python-0.41.3}/style/sidebar.css +0 -0
  302. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  303. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/nb-julia.ipynb +0 -0
  304. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/nb-params.ipynb +0 -0
  305. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/nb-subdir.ipynb +0 -0
  306. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/pipeline.ipynb +0 -0
  307. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/script.py +0 -0
  308. {calkit_python-0.41.1 → calkit_python-0.41.3}/test/test-log.log +0 -0
  309. {calkit_python-0.41.1 → calkit_python-0.41.3}/tsconfig.json +0 -0
  310. {calkit_python-0.41.1 → calkit_python-0.41.3}/tsconfig.test.json +0 -0
  311. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/.gitignore +0 -0
  312. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/README.md +0 -0
  313. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/jupyter_server_test_config.py +0 -0
  314. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/package.json +0 -0
  315. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/playwright.config.js +0 -0
  316. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/tests/calkit.spec.ts +0 -0
  317. {calkit_python-0.41.1 → calkit_python-0.41.3}/ui-tests/yarn.lock +0 -0
  318. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/.gitignore +0 -0
  319. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/.vscodeignore +0 -0
  320. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/CHANGELOG.md +0 -0
  321. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/LICENSE +0 -0
  322. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/README.md +0 -0
  323. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/images/calkit-no-bg.png +0 -0
  324. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/package-lock.json +0 -0
  325. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/package.json +0 -0
  326. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/scripts/set-proposed-api.js +0 -0
  327. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/environments.ts +0 -0
  328. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/extension.ts +0 -0
  329. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/notebooks.ts +0 -0
  330. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/test/environments.test.ts +0 -0
  331. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
  332. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/test/notebooks.test.ts +0 -0
  333. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/src/types.ts +0 -0
  334. {calkit_python-0.41.1 → calkit_python-0.41.3}/vscode-ext/tsconfig.json +0 -0
  335. {calkit_python-0.41.1 → calkit_python-0.41.3}/yarn.lock +0 -0
@@ -20,3 +20,4 @@ dist
20
20
  *.vsix
21
21
  vscode-ext/out
22
22
  .claude
23
+ TODO.md
@@ -14,6 +14,11 @@ To run tests, use `uv run pytest`.
14
14
 
15
15
  To sync the docs and format all the code, run `make format`.
16
16
 
17
+ Before finishing a change, type-check it. The CLI gate is mypy
18
+ (`uv run mypy <changed files>`, or `make check` for the full suite), and the
19
+ VS Code editor uses Pylance (Pyright). Keep new code clean under both, and do
20
+ not introduce new type errors.
21
+
17
22
  Wrap prose at natural breakpoints in phrases or punctuation to keep max
18
23
  line length below 80 characters.
19
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.41.1
3
+ Version: 0.41.3
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -32,11 +32,16 @@ class AliasGroup(TyperGroup):
32
32
 
33
33
 
34
34
  def complete_stage_names(
35
- ctx: "click.Context",
36
- param: "click.Parameter",
35
+ ctx: "click.Context | None",
36
+ param: "click.Parameter | None",
37
37
  incomplete: str,
38
38
  ) -> list:
39
- """Return pipeline stage names for shell tab-completion."""
39
+ """Return pipeline stage names for shell tab-completion.
40
+
41
+ ``ctx`` and ``param`` are part of the Click completion-callback signature
42
+ but are unused here, so they accept ``None`` (e.g. when called directly in
43
+ tests).
44
+ """
40
45
  if not os.path.isfile("calkit.yaml"):
41
46
  return []
42
47
  try:
@@ -421,7 +421,15 @@ def get_status(
421
421
  else:
422
422
  categories = valid_categories
423
423
  pipeline_status = None
424
+ running_status = None
424
425
  if "pipeline" in categories or "dvc" in categories:
426
+ # If a run is in progress it holds the DVC lock, so computing the full
427
+ # status would just error out (and would mutate files like dvc.yaml or
428
+ # notebooks mid-run). Report the running stage from the run log instead.
429
+ running_status = _get_running_pipeline_status()
430
+ if running_status is None and (
431
+ "pipeline" in categories or "dvc" in categories
432
+ ):
425
433
  # Sync zips so the zip files reflect current workspace state before
426
434
  # reporting status
427
435
  calkit.dvc.zip.sync_all(direction="to-zip")
@@ -501,7 +509,9 @@ def get_status(
501
509
  except Exception as e:
502
510
  status_dict["dvc"] = {"error": f"{e.__class__.__name__}: {e}"}
503
511
  if "pipeline" in categories or "dvc" in categories:
504
- if pipeline_status is None:
512
+ if running_status is not None:
513
+ status_dict["pipeline"] = running_status
514
+ elif pipeline_status is None:
505
515
  status_dict["pipeline"] = None
506
516
  else:
507
517
  status_dict["pipeline"] = pipeline_status.model_dump(
@@ -550,6 +560,9 @@ def get_status(
550
560
  typer.echo(_format_dvc_data_status(raw, zip_path_map))
551
561
  if "pipeline" in categories or "dvc" in categories:
552
562
  print_sep("Pipeline")
563
+ if running_status is not None:
564
+ _print_running_pipeline_status(running_status)
565
+ return
553
566
  # Nicely format the results from pipeline status
554
567
  if pipeline_status and pipeline_status.errors:
555
568
  warn("Pipeline status unavailable due to errors:")
@@ -566,6 +579,8 @@ def get_status(
566
579
  if stale_stage is None:
567
580
  continue
568
581
  typer.echo(f" {typer.style(stage_name, fg='yellow')}:")
582
+ if stale_stage.always_run:
583
+ typer.echo(" always runs")
569
584
  if stale_stage.modified_command:
570
585
  typer.echo(" modified command")
571
586
  # Show stale outputs for this stage
@@ -583,8 +598,16 @@ def get_status(
583
598
  typer.echo(" modified inputs:")
584
599
  for input_path in stale_stage.modified_inputs:
585
600
  typer.echo(f" {input_path}")
601
+ if pipeline_status.always_run_stage_names:
602
+ typer.echo("Always-run stages:")
603
+ for stage_name in pipeline_status.always_run_stage_names:
604
+ typer.echo(f" {typer.style(stage_name, fg='cyan')}")
586
605
  elif pipeline_status:
587
606
  typer.echo("Pipeline is up to date.")
607
+ if pipeline_status.always_run_stage_names:
608
+ typer.echo("Always-run stages:")
609
+ for stage_name in pipeline_status.always_run_stage_names:
610
+ typer.echo(f" {typer.style(stage_name, fg='cyan')}")
588
611
 
589
612
 
590
613
  @app.command(name="diff")
@@ -1304,11 +1327,12 @@ def _stage_run_info_from_log_content(log_content: str) -> dict:
1304
1327
  # If we were already running a stage, add its end time
1305
1328
  add_stage_info(current_stage_name, "end_time", timestamp)
1306
1329
  add_stage_info(current_stage_name, "status", "completed")
1307
- # This is a stage run
1330
+ # This is a stage run. The line is ``Running stage '<name>':``;
1331
+ # strip only the trailing ``:`` delimiter and surrounding quotes
1332
+ # so colons inside the name (e.g. ``sub1/dvc.yaml:stage-a`` for an
1333
+ # inline subproject target) are preserved.
1308
1334
  current_stage_name = (
1309
- message.removeprefix("Running stage ")
1310
- .replace("'", "")
1311
- .replace(":", "")
1335
+ message.removeprefix("Running stage ").rstrip(":").strip("'")
1312
1336
  )
1313
1337
  current_stage_status = "running"
1314
1338
  add_stage_info(current_stage_name, "start_time", timestamp)
@@ -1344,6 +1368,239 @@ def _stage_run_info_from_log_content(log_content: str) -> dict:
1344
1368
  return res
1345
1369
 
1346
1370
 
1371
+ def _prune_run_logs(
1372
+ logs_dir: str, keep: int = 10, protect: str | None = None
1373
+ ) -> None:
1374
+ """Keep only the most recent ``keep`` run logs in ``logs_dir``.
1375
+
1376
+ Run logs are named by their start timestamp, so sorting by name orders
1377
+ them by time; the oldest beyond ``keep`` are removed so the private log
1378
+ directory doesn't grow without bound. ``protect`` (the active run's log
1379
+ filename) is never deleted, guarding against clock skew or odd names that
1380
+ could otherwise sort the live log into the prune set.
1381
+ """
1382
+ if not os.path.isdir(logs_dir):
1383
+ return
1384
+ logs = sorted(f for f in os.listdir(logs_dir) if f.endswith(".log"))
1385
+ for fname in logs[:-keep]:
1386
+ if fname == protect:
1387
+ continue
1388
+ try:
1389
+ os.remove(os.path.join(logs_dir, fname))
1390
+ except OSError:
1391
+ pass
1392
+
1393
+
1394
+ def _get_latest_run_log_content() -> str | None:
1395
+ """Return the contents of the most recent run log, or ``None``.
1396
+
1397
+ Looks in the private ``.calkit/local/logs`` directory (always written)
1398
+ and the tracked ``.calkit/logs`` directory, choosing the latest ``.log``
1399
+ file across both by name (logs are named by start timestamp).
1400
+ """
1401
+ candidates = []
1402
+ for d in [
1403
+ os.path.join(".calkit", "local", "logs"),
1404
+ os.path.join(".calkit", "logs"),
1405
+ ]:
1406
+ if os.path.isdir(d):
1407
+ candidates += [
1408
+ os.path.join(d, f) for f in os.listdir(d) if f.endswith(".log")
1409
+ ]
1410
+ if not candidates:
1411
+ return None
1412
+ latest = max(candidates, key=os.path.basename)
1413
+ try:
1414
+ with open(latest) as f:
1415
+ return f.read()
1416
+ except OSError:
1417
+ return None
1418
+
1419
+
1420
+ def _format_run_elapsed(start_iso: str) -> str:
1421
+ """Format the time elapsed since ``start_iso`` (a UTC ISO timestamp)."""
1422
+ try:
1423
+ start = datetime.fromisoformat(start_iso)
1424
+ except ValueError:
1425
+ return "?"
1426
+ total = int((calkit.utcnow(remove_tz=True) - start).total_seconds())
1427
+ total = max(total, 0)
1428
+ hours, rem = divmod(total, 3600)
1429
+ minutes, seconds = divmod(rem, 60)
1430
+ if hours:
1431
+ return f"{hours}h{minutes}m{seconds}s"
1432
+ if minutes:
1433
+ return f"{minutes}m{seconds}s"
1434
+ return f"{seconds}s"
1435
+
1436
+
1437
+ def _stage_target_from_cmd(cmd: str) -> str | None:
1438
+ """Extract a DVC stage target from a ``dvc repro`` command string.
1439
+
1440
+ Concurrent scheduler items (an ``iterate_over`` sweep) run as separate
1441
+ ``dvc repro --single-item <stage>`` processes whose stage name is the
1442
+ trailing positional argument recorded in the DVC lock. This lets the
1443
+ sweep items be named even before the main run log exists. Returns
1444
+ ``None`` for commands without an explicit target (e.g. a full-pipeline
1445
+ ``dvc repro`` or the parent ``calkit run``).
1446
+ """
1447
+ tokens = cmd.split()
1448
+ if "repro" not in tokens:
1449
+ return None
1450
+ after = tokens[tokens.index("repro") + 1 :]
1451
+ targets = [t for t in after if not t.startswith("-")]
1452
+ return targets[-1] if targets else None
1453
+
1454
+
1455
+ def _get_running_pipeline_status() -> dict | None:
1456
+ """Return live pipeline run progress, or ``None`` if no run is running.
1457
+
1458
+ A run is in progress when a live process holds DVC's rwlock. The most
1459
+ recent run log is then parsed to report which stages have finished and
1460
+ which is currently running. Concurrently-run scheduler items execute in
1461
+ their own processes before the run log exists, so their stage names are
1462
+ also recovered from the lock's command strings.
1463
+ """
1464
+ processes = calkit.dvc.get_running_pipeline_processes()
1465
+ if not processes:
1466
+ return None
1467
+ # Stage targets in the lock commands identify concurrently-run scheduler
1468
+ # items (an iterate_over sweep). These run in their own processes during a
1469
+ # prepass, before the current run's log exists, so the latest log on disk
1470
+ # is from a previous run; report only the lock's items in that case to
1471
+ # avoid mixing in stale stages.
1472
+ concurrent_stages: list[str] = []
1473
+ for proc in processes:
1474
+ target = _stage_target_from_cmd(proc.get("cmd", ""))
1475
+ if target is not None and target not in concurrent_stages:
1476
+ concurrent_stages.append(target)
1477
+ if concurrent_stages:
1478
+ return {
1479
+ "running": True,
1480
+ "processes": processes,
1481
+ "stages": {},
1482
+ "running_stages": concurrent_stages,
1483
+ }
1484
+ content = _get_latest_run_log_content()
1485
+ stages = (
1486
+ _stage_run_info_from_log_content(content)
1487
+ if content is not None
1488
+ else {}
1489
+ )
1490
+ running_stages = [
1491
+ name for name, info in stages.items() if "status" not in info
1492
+ ]
1493
+ return {
1494
+ "running": True,
1495
+ "processes": processes,
1496
+ "stages": stages,
1497
+ "running_stages": running_stages,
1498
+ }
1499
+
1500
+
1501
+ def _print_running_pipeline_status(running_status: dict) -> None:
1502
+ """Print a human-readable summary of an in-progress pipeline run."""
1503
+ processes = running_status["processes"]
1504
+ pids = ", ".join(str(p["pid"]) for p in processes)
1505
+ typer.echo(f"Run in progress (PID {pids})")
1506
+ stages = running_status["stages"]
1507
+ finished = [
1508
+ name
1509
+ for name, info in stages.items()
1510
+ if info.get("status") in ("completed", "skipped")
1511
+ ]
1512
+ running_stages = running_status["running_stages"]
1513
+ if finished:
1514
+ typer.echo(f" completed: {', '.join(finished)}")
1515
+ if running_stages:
1516
+ for name in running_stages:
1517
+ start = stages.get(name, {}).get("start_time")
1518
+ label = typer.style(name, fg="green")
1519
+ if start:
1520
+ typer.echo(
1521
+ f" running: {label} "
1522
+ f"({_format_run_elapsed(start)})"
1523
+ )
1524
+ else:
1525
+ typer.echo(f" running: {label}")
1526
+ elif not finished:
1527
+ typer.echo(" starting up...")
1528
+
1529
+
1530
+ def _concurrent_scheduler_prepass(
1531
+ ck_info: dict,
1532
+ targets: list[str],
1533
+ keep_going: bool,
1534
+ quiet: bool,
1535
+ ) -> None:
1536
+ """Submit iterated scheduler-stage jobs concurrently before ``dvc repro``.
1537
+
1538
+ DVC's ``repro`` runs matrix items serially, so each scheduler item would
1539
+ otherwise submit-and-wait one at a time. Here we build each eligible
1540
+ stage's upstreams (serially, to avoid an rwlock race), then fan its items
1541
+ out as concurrent ``dvc repro --single-item`` processes---all at once,
1542
+ leaving it to the cluster's scheduler to queue them. The trailing
1543
+ ``dvc repro`` then sees the items as up-to-date and records them,
1544
+ preserving granular per-item caching so a failed sweep resumes only the
1545
+ failed items. Not used under --force (the caller runs those serially).
1546
+ """
1547
+ import sys
1548
+
1549
+ import calkit.pipeline
1550
+
1551
+ eligible = calkit.pipeline.get_concurrent_scheduler_stages(ck_info)
1552
+ if targets:
1553
+ eligible = [name for name in eligible if name in targets]
1554
+ if not eligible:
1555
+ return
1556
+ for stage_name in eligible:
1557
+ item_targets, upstream_targets = (
1558
+ calkit.pipeline.get_matrix_item_targets(stage_name)
1559
+ )
1560
+ if not item_targets:
1561
+ continue
1562
+ # Each item holds a local polling process for the job's lifetime, so
1563
+ # cap the fan-out to avoid exhausting local resources; a sweep larger
1564
+ # than this should be split into multiple runs.
1565
+ max_jobs = 100
1566
+ if len(item_targets) > max_jobs:
1567
+ raise_error(
1568
+ f"Stage '{stage_name}' would submit {len(item_targets)} jobs "
1569
+ f"at once, exceeding the limit of {max_jobs}. Each concurrent "
1570
+ "submission holds a local process, so reduce the sweep size "
1571
+ "or split it across multiple runs."
1572
+ )
1573
+ if not quiet:
1574
+ calkit.echo(
1575
+ f"🧵 Submitting {len(item_targets)} '{stage_name}' jobs"
1576
+ )
1577
+ # Build shared upstreams first; if two items raced to build the same
1578
+ # stale dependency, one would fail with an rwlock "busy" error. Go
1579
+ # through `calkit dvc` so the ck:// remote scheme is registered.
1580
+ if upstream_targets:
1581
+ up_cmd = [sys.executable, "-m", "calkit", "dvc", "repro"]
1582
+ up_cmd += upstream_targets
1583
+ if subprocess.run(up_cmd).returncode != 0:
1584
+ raise_error(
1585
+ f"Failed to build dependencies for stage '{stage_name}'"
1586
+ )
1587
+ results = calkit.pipeline.reproduce_targets_concurrently(
1588
+ item_targets, max_workers=len(item_targets)
1589
+ )
1590
+ failed = [t for t, rc in results.items() if rc != 0]
1591
+ if failed:
1592
+ msg = (
1593
+ f"{len(failed)} of {len(item_targets)} '{stage_name}' jobs "
1594
+ f"failed: {', '.join(failed)}"
1595
+ )
1596
+ if keep_going:
1597
+ warn(msg)
1598
+ else:
1599
+ raise_error(
1600
+ msg + ". Successful jobs are cached; rerun to resume."
1601
+ )
1602
+
1603
+
1347
1604
  @app.command(name="run")
1348
1605
  def run(
1349
1606
  targets: Annotated[
@@ -1767,24 +2024,48 @@ def run(
1767
2024
  if downstream is not None:
1768
2025
  args.append("--downstream")
1769
2026
  args += downstream
2027
+ # Pre-submit iterated scheduler-stage jobs concurrently; the main repro
2028
+ # below then records them. Skipped for --dry so the dry plan stays intact,
2029
+ # and for --force, which re-runs every item: those go serially through the
2030
+ # main repro so we don't both pre-run and re-run each job. Also skipped
2031
+ # when a selector narrows the run (--downstream/--pipeline/--recursive/
2032
+ # --glob/--all-pipelines): positional ``targets`` is empty then, so the
2033
+ # prepass can't tell which sweeps will actually run and would otherwise
2034
+ # submit all of them.
2035
+ run_is_narrowed = bool(
2036
+ downstream or pipeline or recursive or glob or all_pipelines
2037
+ )
2038
+ if dvc_stages and not dry and not force and not run_is_narrowed:
2039
+ _concurrent_scheduler_prepass(
2040
+ ck_info=ck_info,
2041
+ targets=targets,
2042
+ keep_going=keep_going,
2043
+ quiet=quiet,
2044
+ )
1770
2045
  start_time_no_tz = calkit.utcnow(remove_tz=True)
1771
2046
  start_time = calkit.utcnow(remove_tz=False)
1772
2047
  run_id = uuid.uuid4().hex
1773
- # Always log output, but only save systems/run data if specified
1774
- log_fpath = os.path.join(
1775
- ".calkit",
1776
- "logs",
2048
+ log_fname = (
1777
2049
  start_time_no_tz.isoformat(timespec="seconds").replace(":", "-")
1778
2050
  + "-"
1779
2051
  + run_id
1780
- + ".log",
2052
+ + ".log"
1781
2053
  )
2054
+ # Always write the run log under the gitignored .calkit/local/logs so
2055
+ # `calkit status` can report which stage is running while the pipeline
2056
+ # holds the DVC lock. With --log, the log is additionally saved to the
2057
+ # tracked .calkit/logs directory along with run information.
2058
+ local_logs_dir = os.path.join(calkit.ensure_local_dir(), "logs")
2059
+ os.makedirs(local_logs_dir, exist_ok=True)
2060
+ log_fpath = os.path.join(local_logs_dir, log_fname)
1782
2061
  if verbose:
1783
2062
  typer.echo(f"Starting run ID: {run_id}")
1784
2063
  typer.echo(f"Saving logs to {log_fpath}")
1785
- os.makedirs(os.path.dirname(log_fpath), exist_ok=True)
1786
2064
  # Create a file handler for dvc.stage.run logger
1787
2065
  file_handler = logging.FileHandler(log_fpath, mode="w")
2066
+ # Keep the private log directory bounded; the new log counts toward the
2067
+ # cap and is protected so it can never be pruned out from under this run.
2068
+ _prune_run_logs(local_logs_dir, keep=10, protect=log_fname)
1788
2069
  file_handler.setLevel(logging.DEBUG)
1789
2070
  formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
1790
2071
  formatter.converter = time.gmtime # Use UTC time for asctime
@@ -1794,7 +2075,14 @@ def run(
1794
2075
  dvc.repo.reproduce.logger.setLevel(logging.ERROR)
1795
2076
  # Disable other misc DVC output
1796
2077
  dvc.ui.ui.write = lambda *args, **kwargs: None
1797
- res = dvc_cli_main(["repro"] + args)
2078
+ # Tell `calkit scheduler batch` to resubmit completed jobs under --force;
2079
+ # otherwise it skips jobs it sees as already done.
2080
+ if force:
2081
+ os.environ["CALKIT_FORCE"] = "1"
2082
+ try:
2083
+ res = dvc_cli_main(["repro"] + args)
2084
+ finally:
2085
+ os.environ.pop("CALKIT_FORCE", None)
1798
2086
  failed = failed or res != 0
1799
2087
  # Parse log to get timing and which stages ran
1800
2088
  with open(log_fpath, "r") as f:
@@ -1877,8 +2165,12 @@ def run(
1877
2165
  os.makedirs(os.path.dirname(run_info_fpath), exist_ok=True)
1878
2166
  with open(run_info_fpath, "w") as f:
1879
2167
  json.dump(run_info, f, indent=2)
1880
- else:
1881
- os.remove(log_fpath)
2168
+ # Also keep the raw log in the tracked .calkit/logs directory
2169
+ saved_log_fpath = os.path.join(".calkit", "logs", log_fname)
2170
+ os.makedirs(os.path.dirname(saved_log_fpath), exist_ok=True)
2171
+ shutil.copy2(log_fpath, saved_log_fpath)
2172
+ # The private log under .calkit/local/logs is retained either way so the
2173
+ # last run's status stays inspectable; it is gitignored.
1882
2174
  os.environ.pop("CALKIT_PIPELINE_RUNNING", None)
1883
2175
  if failed:
1884
2176
  raise_error("Pipeline failed")
@@ -2139,7 +2431,10 @@ def run_in_env(
2139
2431
  elif env["kind"] == "pixi":
2140
2432
  env_cmd = []
2141
2433
  if "name" in env:
2142
- env_cmd = ["--environment", env["name"]]
2434
+ env_cmd += ["--environment", env["name"]]
2435
+ env_path = env.get("path")
2436
+ if env_path and os.path.dirname(env_path):
2437
+ env_cmd += ["--manifest-path", os.path.abspath(env_path)]
2143
2438
  cmd = ["pixi", "run"] + env_cmd + cmd
2144
2439
  if verbose:
2145
2440
  typer.echo(f"Running command: {cmd}")