calkit-python 0.29.0__tar.gz → 0.30.0__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 (182) hide show
  1. {calkit_python-0.29.0 → calkit_python-0.30.0}/PKG-INFO +1 -1
  2. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/__init__.py +1 -1
  3. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/check.py +2 -17
  4. calkit_python-0.30.0/calkit/cli/config.py +240 -0
  5. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/main.py +32 -6
  6. calkit_python-0.30.0/calkit/cli/slurm.py +344 -0
  7. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/config.py +10 -5
  8. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/core.py +14 -0
  9. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/models/pipeline.py +65 -1
  10. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/models/test_pipeline.py +37 -4
  11. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_pipeline.py +37 -0
  12. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/environments.md +17 -2
  13. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/pipeline/index.md +34 -1
  14. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/pipeline/running-and-logging.md +1 -0
  15. calkit_python-0.30.0/docs/pipeline/slurm.md +63 -0
  16. {calkit_python-0.29.0 → calkit_python-0.30.0}/mkdocs.yml +1 -0
  17. calkit_python-0.29.0/calkit/cli/config.py +0 -126
  18. {calkit_python-0.29.0 → calkit_python-0.30.0}/.github/FUNDING.yml +0 -0
  19. {calkit_python-0.29.0 → calkit_python-0.30.0}/.github/workflows/docs.yml +0 -0
  20. {calkit_python-0.29.0 → calkit_python-0.30.0}/.github/workflows/format.yml +0 -0
  21. {calkit_python-0.29.0 → calkit_python-0.30.0}/.github/workflows/publish-test.yml +0 -0
  22. {calkit_python-0.29.0 → calkit_python-0.30.0}/.github/workflows/publish.yml +0 -0
  23. {calkit_python-0.29.0 → calkit_python-0.30.0}/.github/workflows/test.yml +0 -0
  24. {calkit_python-0.29.0 → calkit_python-0.30.0}/.gitignore +0 -0
  25. {calkit_python-0.29.0 → calkit_python-0.30.0}/.pre-commit-config.yaml +0 -0
  26. {calkit_python-0.29.0 → calkit_python-0.30.0}/.python-version +0 -0
  27. {calkit_python-0.29.0 → calkit_python-0.30.0}/CITATION.cff +0 -0
  28. {calkit_python-0.29.0 → calkit_python-0.30.0}/CONTRIBUTING.md +0 -0
  29. {calkit_python-0.29.0 → calkit_python-0.30.0}/LICENSE +0 -0
  30. {calkit_python-0.29.0 → calkit_python-0.30.0}/Makefile +0 -0
  31. {calkit_python-0.29.0 → calkit_python-0.30.0}/README.md +0 -0
  32. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/__main__.py +0 -0
  33. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/calc.py +0 -0
  34. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/check.py +0 -0
  35. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/__init__.py +0 -0
  36. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/cloud.py +0 -0
  37. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/core.py +0 -0
  38. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/describe.py +0 -0
  39. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/import_.py +0 -0
  40. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/list.py +0 -0
  41. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/new.py +0 -0
  42. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/notebooks.py +0 -0
  43. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/office.py +0 -0
  44. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/overleaf.py +0 -0
  45. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cli/update.py +0 -0
  46. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/cloud.py +0 -0
  47. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/conda.py +0 -0
  48. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/datasets.py +0 -0
  49. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/docker.py +0 -0
  50. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/dvc.py +0 -0
  51. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/environments.py +0 -0
  52. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/git.py +0 -0
  53. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/github.py +0 -0
  54. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/gui.py +0 -0
  55. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/jupyter.py +0 -0
  56. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/magics.py +0 -0
  57. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/matlab.py +0 -0
  58. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/models/__init__.py +0 -0
  59. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/models/core.py +0 -0
  60. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/models/io.py +0 -0
  61. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/models/iteration.py +0 -0
  62. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/notebooks.py +0 -0
  63. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/office.py +0 -0
  64. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/ops.py +0 -0
  65. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/pipeline.py +0 -0
  66. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/releases.py +0 -0
  67. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/server.py +0 -0
  68. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/__init__.py +0 -0
  69. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/core.py +0 -0
  70. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/__init__.py +0 -0
  71. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/article/paper.tex +0 -0
  72. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/core.py +0 -0
  73. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/jfm/jfm.bst +0 -0
  74. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/jfm/jfm.cls +0 -0
  75. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  76. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/jfm/paper.tex +0 -0
  77. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/templates/latex/jfm/upmath.sty +0 -0
  78. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/__init__.py +0 -0
  79. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/__init__.py +0 -0
  80. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/test_check.py +0 -0
  81. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/test_config.py +0 -0
  82. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/test_list.py +0 -0
  83. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/test_main.py +0 -0
  84. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/test_new.py +0 -0
  85. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/cli/test_notebooks.py +0 -0
  86. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/models/__init__.py +0 -0
  87. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/models/test_iteration.py +0 -0
  88. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_calc.py +0 -0
  89. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_check.py +0 -0
  90. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_conda.py +0 -0
  91. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_core.py +0 -0
  92. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_dvc.py +0 -0
  93. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_jupyter.py +0 -0
  94. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_magics.py +0 -0
  95. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_notebooks.py +0 -0
  96. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/tests/test_templates.py +0 -0
  97. {calkit_python-0.29.0 → calkit_python-0.30.0}/calkit/zenodo.py +0 -0
  98. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/CNAME +0 -0
  99. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/apps.md +0 -0
  100. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/calculations.md +0 -0
  101. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/calkit-yaml.md +0 -0
  102. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/cli-reference.md +0 -0
  103. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/cloud-integration.md +0 -0
  104. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/datasets.md +0 -0
  105. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/dependencies.md +0 -0
  106. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/examples.md +0 -0
  107. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/help.md +0 -0
  108. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/img/c-to-the-k-white.svg +0 -0
  109. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/img/calkit-no-bg.png +0 -0
  110. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/img/connect-zenodo.png +0 -0
  111. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/img/jupyterlab-params.png +0 -0
  112. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/img/vscode-nb-params.png +0 -0
  113. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/index.md +0 -0
  114. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/installation.md +0 -0
  115. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/local-server.md +0 -0
  116. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/notebooks.md +0 -0
  117. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/overleaf.md +0 -0
  118. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/pipeline/manual-steps.md +0 -0
  119. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/quickstart.md +0 -0
  120. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/references.md +0 -0
  121. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/releases.md +0 -0
  122. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  123. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/conda-envs.md +0 -0
  124. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/existing-project.md +0 -0
  125. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/first-project.md +0 -0
  126. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/github-actions.md +0 -0
  127. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  128. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  129. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  130. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  131. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  132. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  133. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  134. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  135. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  136. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  137. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  138. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  139. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  140. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  141. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  142. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/create-project.png +0 -0
  143. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  144. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/excel-chart.png +0 -0
  145. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/excel-data.png +0 -0
  146. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  147. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/needs-clone.png +0 -0
  148. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/new-stage.png +0 -0
  149. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  150. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  151. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/status-more-rows.png +0 -0
  152. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  153. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/untracked-data.png +0 -0
  154. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/updated-publication.png +0 -0
  155. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  156. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/office/workflow-page.png +0 -0
  157. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/clone.png +0 -0
  158. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/create-project.png +0 -0
  159. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  160. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  161. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  162. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/new-token.png +0 -0
  163. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/reclone.png +0 -0
  164. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  165. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/quick-actions.png +0 -0
  166. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/img/run-proc.png +0 -0
  167. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/index.md +0 -0
  168. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/latex-codespaces.md +0 -0
  169. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/matlab.md +0 -0
  170. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/notebook-pipeline.md +0 -0
  171. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/office.md +0 -0
  172. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/openfoam.md +0 -0
  173. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/tutorials/procedures.md +0 -0
  174. {calkit_python-0.29.0 → calkit_python-0.30.0}/docs/version-control.md +0 -0
  175. {calkit_python-0.29.0 → calkit_python-0.30.0}/pyproject.toml +0 -0
  176. {calkit_python-0.29.0 → calkit_python-0.30.0}/scripts/install.ps1 +0 -0
  177. {calkit_python-0.29.0 → calkit_python-0.30.0}/scripts/install.sh +0 -0
  178. {calkit_python-0.29.0 → calkit_python-0.30.0}/test/nb-params.ipynb +0 -0
  179. {calkit_python-0.29.0 → calkit_python-0.30.0}/test/nb-subdir.ipynb +0 -0
  180. {calkit_python-0.29.0 → calkit_python-0.30.0}/test/pipeline.ipynb +0 -0
  181. {calkit_python-0.29.0 → calkit_python-0.30.0}/test/test-log.log +0 -0
  182. {calkit_python-0.29.0 → calkit_python-0.30.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.29.0
3
+ Version: 0.30.0
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,4 +1,4 @@
1
- __version__ = "0.29.0"
1
+ __version__ = "0.30.0"
2
2
 
3
3
  from .core import * # noqa: F403, I001
4
4
  from . import git # noqa: F401
@@ -3,21 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import functools
6
- import hashlib
7
6
  import json
8
7
  import os
9
8
  import platform as _platform
10
9
  import subprocess
11
- import warnings
12
10
  from typing import Annotated
13
11
 
14
- from calkit.environments import get_env_lock_fpath
15
-
16
- # See https://github.com/calkit/calkit/issues/346
17
- with warnings.catch_warnings():
18
- warnings.filterwarnings("ignore", category=UserWarning)
19
- import checksumdir
20
-
21
12
  import dotenv
22
13
  import git
23
14
  import typer
@@ -27,6 +18,8 @@ import calkit.matlab
27
18
  import calkit.pipeline
28
19
  from calkit.check import check_reproducibility
29
20
  from calkit.cli import raise_error, warn
21
+ from calkit.core import get_md5
22
+ from calkit.environments import get_env_lock_fpath
30
23
 
31
24
  check_app = typer.Typer(no_args_is_help=True)
32
25
 
@@ -288,14 +281,6 @@ def check_docker_env(
288
281
  resp[key] = out[0].get(key)
289
282
  return resp
290
283
 
291
- def get_md5(path: str, exclude_files: list[str] | None = None) -> str:
292
- if os.path.isdir(path):
293
- return checksumdir.dirhash(dep, excluded_files=exclude_files)
294
- else:
295
- with open(path) as f:
296
- content = f.read()
297
- return hashlib.md5(content.encode()).hexdigest()
298
-
299
284
  outfile = open(os.devnull, "w") if quiet else None
300
285
  typer.echo(f"Checking for existing image with tag {tag}", file=outfile)
301
286
  # First call Docker inspect
@@ -0,0 +1,240 @@
1
+ """Config CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import glob
6
+ import os
7
+ import subprocess
8
+
9
+ import git
10
+ import typer
11
+ from git.exc import InvalidGitRepositoryError
12
+ from typing_extensions import Annotated
13
+
14
+ import calkit
15
+ from calkit import config
16
+ from calkit.cli.core import raise_error
17
+ from calkit.dvc import configure_remote, get_remotes, set_remote_auth
18
+
19
+ config_app = typer.Typer(no_args_is_help=True)
20
+
21
+
22
+ @config_app.command(name="set")
23
+ def set_config_value(key: str, value: str):
24
+ """Set a value in the config."""
25
+ keys = config.Settings.model_fields.keys()
26
+ if key not in keys:
27
+ raise_error(
28
+ f"Invalid config key: '{key}'; Valid keys are: {list(keys)}"
29
+ )
30
+ try:
31
+ cfg = config.read()
32
+ cfg = config.Settings.model_validate(cfg.model_dump() | {key: value})
33
+ except Exception as e:
34
+ raise_error(f"Failed to set {key} in config: {e}")
35
+ cfg.write()
36
+
37
+
38
+ @config_app.command(name="get")
39
+ def get_config_value(key: str) -> None:
40
+ """Get and print a value from the config."""
41
+ cfg = config.read().model_dump()
42
+ if key not in cfg:
43
+ raise_error(
44
+ f"Invalid config key: '{key}'; Valid keys are: {list(cfg.keys())}"
45
+ )
46
+ val = cfg[key]
47
+ if val is not None:
48
+ print(val)
49
+ else:
50
+ print()
51
+
52
+
53
+ @config_app.command(name="unset")
54
+ def unset_config_value(key: str):
55
+ """Unset a value in the config, returning it to default."""
56
+ model_fields = config.Settings.model_fields
57
+ if key not in model_fields:
58
+ raise_error(
59
+ f"Invalid config key: '{key}'; "
60
+ f"Valid keys: {list(model_fields.keys())}"
61
+ )
62
+ try:
63
+ cfg = config.read()
64
+ setattr(cfg, key, model_fields[key].default)
65
+ except Exception as e:
66
+ raise_error(f"Failed to unset {key} in config: {e}")
67
+ cfg.write()
68
+
69
+
70
+ @config_app.command(name="setup-remote", help="Alias for 'remote'.")
71
+ @config_app.command(name="remote")
72
+ def setup_remote(
73
+ no_commit: Annotated[
74
+ bool,
75
+ typer.Option(
76
+ "--no-commit", help="Do not commit changes to DVC config."
77
+ ),
78
+ ] = False,
79
+ ):
80
+ """Setup the Calkit cloud as the default DVC remote and store a token in
81
+ the local config.
82
+ """
83
+ try:
84
+ configure_remote()
85
+ set_remote_auth()
86
+ except subprocess.CalledProcessError:
87
+ if not os.path.isfile(".dvc/config"):
88
+ raise_error(
89
+ "DVC remote config failed; have you run `calkit init`?"
90
+ )
91
+ raise_error(
92
+ "Failed to configure DVC remote; check DVC config for errors"
93
+ )
94
+ except InvalidGitRepositoryError:
95
+ raise_error("Current directory is not a Git repository")
96
+ except (ValueError, RuntimeError) as e:
97
+ raise_error(f"Failed to set up DVC remote: {e}")
98
+ if not no_commit:
99
+ repo = git.Repo()
100
+ repo.git.add(".dvc/config")
101
+ if ".dvc/config" in calkit.git.get_staged_files():
102
+ typer.echo("Committing changes to DVC config")
103
+ repo.git.commit([".dvc/config", "-m", "Set DVC remote"])
104
+
105
+
106
+ @config_app.command(name="setup-remote-auth", help="Alias for 'remote-auth'.")
107
+ @config_app.command(name="remote-auth")
108
+ def setup_remote_auth():
109
+ """Store a Calkit cloud token in the local DVC config for all Calkit
110
+ remotes.
111
+ """
112
+ try:
113
+ remotes = get_remotes()
114
+ except Exception:
115
+ raise_error("Cannot list DVC remotes; check DVC config for errors")
116
+ for name, url in remotes.items():
117
+ if name == "calkit" or name.startswith("calkit:"):
118
+ typer.echo(f"Setting up authentication for DVC remote: {name}")
119
+ set_remote_auth(remote_name=name)
120
+
121
+
122
+ @config_app.command(name="list")
123
+ def list_config_keys():
124
+ """List keys in the config."""
125
+ cfg = config.read()
126
+ for key in cfg.model_dump():
127
+ typer.echo(key)
128
+
129
+
130
+ @config_app.command(name="github-ssh")
131
+ def config_github_ssh():
132
+ """Walk through the process of adding an SSH key to GitHub."""
133
+ typer.echo("Checking if you can already connect to GitHub via SSH")
134
+ # First check if we can already connect to GitHub
135
+ ssh_test_cmd = ["ssh", "-T", "git@github.com"]
136
+ p = subprocess.run(ssh_test_cmd, capture_output=True, text=True)
137
+ if "successfully authenticated" in p.stderr:
138
+ typer.echo("You can already connect to GitHub via SSH")
139
+ go_on = typer.confirm("Do you want to add a new SSH key anyway?")
140
+ if not go_on:
141
+ raise typer.Exit()
142
+ # If we can, ask the user if they still want to add a new key
143
+ # First check if the user has any SSH keys
144
+ ssh_dir = os.path.expanduser("~/.ssh")
145
+ existing_pub_keys = glob.glob(os.path.join(ssh_dir, "*.pub"))
146
+ # If not run ssh-keygen
147
+ if existing_pub_keys:
148
+ # Ask the user if they want to use an existing key or create a new one
149
+ typer.echo("Existing SSH public keys found:")
150
+ for i, key in enumerate(existing_pub_keys):
151
+ typer.echo(f"{i + 1}: {key}")
152
+ use_existing = typer.confirm("Do you want to use one of these keys?")
153
+ if use_existing:
154
+ key_choice = typer.prompt(
155
+ "Enter the number of the key to use", type=int
156
+ )
157
+ if 1 <= key_choice <= len(existing_pub_keys):
158
+ key_path = existing_pub_keys[key_choice - 1][:-4]
159
+ else:
160
+ typer.echo("Invalid choice")
161
+ # Keep asking until they give a valid choice
162
+ while True:
163
+ key_choice = typer.prompt(
164
+ "Enter the number of the key to use", type=int
165
+ )
166
+ if 1 <= key_choice <= len(existing_pub_keys):
167
+ key_path = existing_pub_keys[key_choice - 1][:-4]
168
+ break
169
+ else:
170
+ typer.echo("Invalid choice, please try again.")
171
+ else:
172
+ key_path = typer.prompt(
173
+ "Enter the path to save the new SSH key",
174
+ default=os.path.join(ssh_dir, "id_ed25519"),
175
+ )
176
+ else:
177
+ typer.echo("No existing SSH keys found")
178
+ key_path = typer.prompt(
179
+ "Enter the path to save the new SSH key",
180
+ default=os.path.join(ssh_dir, "id_ed25519"),
181
+ )
182
+ # Get the user's email from their Git config, and ask them if they want to
183
+ # use that or a different one
184
+ try:
185
+ user_git_email = git.Git().config("--get", "user.email").strip()
186
+ except Exception:
187
+ user_git_email = typer.prompt(
188
+ "No email found in Git config; enter email for SSH key"
189
+ )
190
+ git.Git().config("--global", "user.email", user_git_email)
191
+ # Do the same for user name even though we don't need it
192
+ try:
193
+ user_git_name = git.Git().config("--get", "user.name").strip()
194
+ except Exception:
195
+ user_git_name = typer.prompt(
196
+ "No name found in Git config; enter name for SSH key"
197
+ )
198
+ git.Git().config("--global", "user.name", user_git_name)
199
+ keygen_cmd = [
200
+ "ssh-keygen",
201
+ "-t",
202
+ "ed25519",
203
+ "-C",
204
+ user_git_email,
205
+ "-f",
206
+ key_path,
207
+ ]
208
+ subprocess.run(keygen_cmd)
209
+ # Start the SSH agent in the background
210
+ typer.echo("Checking that the SSH agent is running")
211
+ ssh_agent_cmd = subprocess.run(
212
+ ["ssh-agent", "-s"], capture_output=True, text=True
213
+ ).stdout
214
+ p = subprocess.run(ssh_agent_cmd, shell=True)
215
+ if p.returncode != 0:
216
+ raise_error("Failed to start ssh-agent")
217
+ # Add the SSH key to the ssh-agent
218
+ typer.echo(f"Adding SSH key to ssh-agent: {key_path}")
219
+ cmd = ["ssh-add", key_path]
220
+ p = subprocess.run(cmd)
221
+ if p.returncode != 0:
222
+ raise_error("Failed to add SSH key to ssh-agent; please try again")
223
+ # Now add to GitHub
224
+ gh_ssh_url = "https://github.com/settings/ssh/new"
225
+ typer.echo(
226
+ "Add the new SSH key to your GitHub account by visiting:\n"
227
+ f"{gh_ssh_url}"
228
+ )
229
+ with open(key_path + ".pub", "r") as f:
230
+ pub_key = f.read()
231
+ typer.echo(f"Paste this into the public key field:\n\n{pub_key}\n")
232
+ typer.confirm("Press Enter when done", default=True)
233
+ typer.echo("Testing SSH connection to GitHub")
234
+ p = subprocess.run(ssh_test_cmd, capture_output=True, text=True)
235
+ if "successfully authenticated" in p.stderr:
236
+ typer.echo("Successfully connected to GitHub via SSH!")
237
+ else:
238
+ raise_error(
239
+ "Failed to connect to GitHub via SSH; please check your setup"
240
+ )
@@ -51,6 +51,7 @@ from calkit.cli.new import new_app
51
51
  from calkit.cli.notebooks import notebooks_app
52
52
  from calkit.cli.office import office_app
53
53
  from calkit.cli.overleaf import overleaf_app
54
+ from calkit.cli.slurm import slurm_app
54
55
  from calkit.cli.update import update_app
55
56
  from calkit.environments import get_env_lock_fpath
56
57
  from calkit.models import Procedure
@@ -77,6 +78,7 @@ app.add_typer(update_app, name="update", help="Update objects.")
77
78
  app.add_typer(check_app, name="check", help="Check things.")
78
79
  app.add_typer(overleaf_app, name="overleaf", help="Interact with Overleaf.")
79
80
  app.add_typer(cloud_app, name="cloud", help="Interact with a Calkit Cloud.")
81
+ app.add_typer(slurm_app, name="slurm", help="Work with SLURM.")
80
82
 
81
83
  # Constants for version control auto-ignore
82
84
  AUTO_IGNORE_SUFFIXES = [".DS_Store", ".env", ".pyc", ".synctex.gz"]
@@ -187,8 +189,11 @@ def clone(
187
189
  no_dvc_pull: Annotated[
188
190
  bool, typer.Option("--no-dvc-pull", help="Do not pull DVC objects.")
189
191
  ] = False,
190
- recursive: Annotated[
191
- bool, typer.Option("--recursive", help="Recursively clone submodules.")
192
+ non_recursive: Annotated[
193
+ bool,
194
+ typer.Option(
195
+ "--no-recursive", help="Do not recursively clone submodules."
196
+ ),
192
197
  ] = False,
193
198
  ):
194
199
  """Clone a Git repo and by default configure and pull from the DVC
@@ -219,7 +224,7 @@ def clone(
219
224
  url = url.replace("https://github.com/", "git@github.com:")
220
225
  # Git clone
221
226
  cmd = ["git", "clone", url]
222
- if recursive:
227
+ if not non_recursive:
223
228
  cmd.append("--recursive")
224
229
  if location is not None:
225
230
  cmd.append(location)
@@ -552,6 +557,9 @@ def save(
552
557
  help="Additional DVC args to pass when pushing.",
553
558
  ),
554
559
  ] = [],
560
+ no_recursive: Annotated[
561
+ bool, typer.Option("--no-recursive", help="Do not push to submodules.")
562
+ ] = False,
555
563
  verbose: Annotated[
556
564
  bool, typer.Option("--verbose", "-v", help="Print verbose output.")
557
565
  ] = False,
@@ -606,7 +614,10 @@ def save(
606
614
  if verbose and not any_dvc:
607
615
  typer.echo("Not pushing to DVC since no DVC files were staged")
608
616
  push(
609
- no_dvc=not any_dvc, git_args=git_push_args, dvc_args=dvc_push_args
617
+ no_dvc=not any_dvc,
618
+ git_args=git_push_args,
619
+ dvc_args=dvc_push_args,
620
+ no_recursive=no_recursive,
610
621
  )
611
622
 
612
623
 
@@ -629,6 +640,12 @@ def pull(
629
640
  help="Force pull, potentially overwriting local changes.",
630
641
  ),
631
642
  ] = False,
643
+ no_recursive: Annotated[
644
+ bool,
645
+ typer.Option(
646
+ "--no-recursive", help="Do not recursively pull from submodules."
647
+ ),
648
+ ] = False,
632
649
  ):
633
650
  """Pull with both Git and DVC."""
634
651
  typer.echo("Git pulling")
@@ -638,7 +655,10 @@ def pull(
638
655
  if "-f" not in dvc_args and "--force" not in dvc_args:
639
656
  dvc_args.append("-f")
640
657
  try:
641
- subprocess.check_call(["git", "pull"] + git_args)
658
+ git_cmd = ["git", "pull"]
659
+ if not no_recursive and "--recurse-submodules" not in git_args:
660
+ git_cmd.append("--recurse-submodules")
661
+ subprocess.check_call(git_cmd + git_args)
642
662
  except subprocess.CalledProcessError:
643
663
  raise_error("Git pull failed")
644
664
  typer.echo("DVC pulling")
@@ -667,11 +687,17 @@ def push(
667
687
  list[str],
668
688
  typer.Option("--dvc-arg", help="Additional DVC args."),
669
689
  ] = [],
690
+ no_recursive: Annotated[
691
+ bool, typer.Option("--no-recursive", help="Do not push to submodules.")
692
+ ] = False,
670
693
  ):
671
694
  """Push with both Git and DVC."""
672
695
  typer.echo("Pushing to Git remote")
673
696
  try:
674
- subprocess.check_call(["git", "push"] + git_args)
697
+ git_cmd = ["git", "push"]
698
+ if not no_recursive and "--recurse-submodules" not in git_args:
699
+ git_cmd.append("--recurse-submodules=on-demand")
700
+ subprocess.check_call(git_cmd + git_args)
675
701
  except subprocess.CalledProcessError:
676
702
  raise_error("Git push failed")
677
703
  if not no_dvc: