calkit-python 0.26.0__tar.gz → 0.26.2__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 (165) hide show
  1. {calkit_python-0.26.0 → calkit_python-0.26.2}/PKG-INFO +1 -1
  2. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/__init__.py +1 -1
  3. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/check.py +49 -0
  4. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/main.py +11 -0
  5. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/new.py +152 -6
  6. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/notebooks.py +10 -4
  7. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/magics.py +15 -0
  8. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/pipeline.py +3 -0
  9. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/cli/test_main.py +1 -0
  10. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/notebooks.md +4 -2
  11. {calkit_python-0.26.0 → calkit_python-0.26.2}/.github/FUNDING.yml +0 -0
  12. {calkit_python-0.26.0 → calkit_python-0.26.2}/.github/workflows/docs.yml +0 -0
  13. {calkit_python-0.26.0 → calkit_python-0.26.2}/.github/workflows/format.yml +0 -0
  14. {calkit_python-0.26.0 → calkit_python-0.26.2}/.github/workflows/publish-test.yml +0 -0
  15. {calkit_python-0.26.0 → calkit_python-0.26.2}/.github/workflows/publish.yml +0 -0
  16. {calkit_python-0.26.0 → calkit_python-0.26.2}/.github/workflows/test.yml +0 -0
  17. {calkit_python-0.26.0 → calkit_python-0.26.2}/.gitignore +0 -0
  18. {calkit_python-0.26.0 → calkit_python-0.26.2}/.pre-commit-config.yaml +0 -0
  19. {calkit_python-0.26.0 → calkit_python-0.26.2}/.python-version +0 -0
  20. {calkit_python-0.26.0 → calkit_python-0.26.2}/CONTRIBUTING.md +0 -0
  21. {calkit_python-0.26.0 → calkit_python-0.26.2}/LICENSE +0 -0
  22. {calkit_python-0.26.0 → calkit_python-0.26.2}/Makefile +0 -0
  23. {calkit_python-0.26.0 → calkit_python-0.26.2}/README.md +0 -0
  24. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/__main__.py +0 -0
  25. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/calc.py +0 -0
  26. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/check.py +0 -0
  27. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/__init__.py +0 -0
  28. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/cloud.py +0 -0
  29. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/config.py +0 -0
  30. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/core.py +0 -0
  31. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/describe.py +0 -0
  32. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/import_.py +0 -0
  33. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/list.py +0 -0
  34. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/office.py +0 -0
  35. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/overleaf.py +0 -0
  36. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cli/update.py +0 -0
  37. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/cloud.py +0 -0
  38. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/conda.py +0 -0
  39. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/config.py +0 -0
  40. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/core.py +0 -0
  41. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/datasets.py +0 -0
  42. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/docker.py +0 -0
  43. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/dvc.py +0 -0
  44. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/environments.py +0 -0
  45. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/git.py +0 -0
  46. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/github.py +0 -0
  47. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/gui.py +0 -0
  48. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/jupyter.py +0 -0
  49. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/matlab.py +0 -0
  50. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/models/__init__.py +0 -0
  51. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/models/core.py +0 -0
  52. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/models/io.py +0 -0
  53. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/models/iteration.py +0 -0
  54. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/models/pipeline.py +0 -0
  55. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/notebooks.py +0 -0
  56. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/office.py +0 -0
  57. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/ops.py +0 -0
  58. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/releases.py +0 -0
  59. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/server.py +0 -0
  60. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/__init__.py +0 -0
  61. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/core.py +0 -0
  62. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/__init__.py +0 -0
  63. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/article/paper.tex +0 -0
  64. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/core.py +0 -0
  65. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/jfm/jfm.bst +0 -0
  66. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/jfm/jfm.cls +0 -0
  67. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  68. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/jfm/paper.tex +0 -0
  69. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/templates/latex/jfm/upmath.sty +0 -0
  70. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/__init__.py +0 -0
  71. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/cli/__init__.py +0 -0
  72. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/cli/test_config.py +0 -0
  73. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/cli/test_list.py +0 -0
  74. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/cli/test_new.py +0 -0
  75. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/models/__init__.py +0 -0
  76. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/models/test_pipeline.py +0 -0
  77. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_calc.py +0 -0
  78. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_check.py +0 -0
  79. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_conda.py +0 -0
  80. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_core.py +0 -0
  81. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_dvc.py +0 -0
  82. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_jupyter.py +0 -0
  83. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_magics.py +0 -0
  84. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_notebooks.py +0 -0
  85. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_pipeline.py +0 -0
  86. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/tests/test_templates.py +0 -0
  87. {calkit_python-0.26.0 → calkit_python-0.26.2}/calkit/zenodo.py +0 -0
  88. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/CNAME +0 -0
  89. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/apps.md +0 -0
  90. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/calculations.md +0 -0
  91. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/calkit-yaml.md +0 -0
  92. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/cli-reference.md +0 -0
  93. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/cloud-integration.md +0 -0
  94. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/datasets.md +0 -0
  95. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/dependencies.md +0 -0
  96. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/environments.md +0 -0
  97. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/examples.md +0 -0
  98. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/help.md +0 -0
  99. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/img/c-to-the-k-white.svg +0 -0
  100. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/img/calkit-no-bg.png +0 -0
  101. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/img/connect-zenodo.png +0 -0
  102. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/index.md +0 -0
  103. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/installation.md +0 -0
  104. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/local-server.md +0 -0
  105. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/overleaf.md +0 -0
  106. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/pipeline/index.md +0 -0
  107. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/pipeline/manual-steps.md +0 -0
  108. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/pipeline/running-and-logging.md +0 -0
  109. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/references.md +0 -0
  110. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/releases.md +0 -0
  111. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  112. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/conda-envs.md +0 -0
  113. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/existing-project.md +0 -0
  114. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/first-project.md +0 -0
  115. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  116. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  117. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  118. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  119. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  120. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  121. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  122. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  123. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  124. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  125. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  126. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  127. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  128. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  129. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/create-project.png +0 -0
  130. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  131. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/excel-chart.png +0 -0
  132. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/excel-data.png +0 -0
  133. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  134. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/needs-clone.png +0 -0
  135. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/new-stage.png +0 -0
  136. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  137. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  138. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/status-more-rows.png +0 -0
  139. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  140. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/untracked-data.png +0 -0
  141. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/updated-publication.png +0 -0
  142. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  143. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/office/workflow-page.png +0 -0
  144. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/clone.png +0 -0
  145. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/create-project.png +0 -0
  146. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  147. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  148. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  149. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/new-token.png +0 -0
  150. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/reclone.png +0 -0
  151. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  152. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/img/run-proc.png +0 -0
  153. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/index.md +0 -0
  154. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/latex-codespaces.md +0 -0
  155. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/matlab.md +0 -0
  156. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/notebook-pipeline.md +0 -0
  157. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/office.md +0 -0
  158. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/openfoam.md +0 -0
  159. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/tutorials/procedures.md +0 -0
  160. {calkit_python-0.26.0 → calkit_python-0.26.2}/docs/version-control.md +0 -0
  161. {calkit_python-0.26.0 → calkit_python-0.26.2}/mkdocs.yml +0 -0
  162. {calkit_python-0.26.0 → calkit_python-0.26.2}/pyproject.toml +0 -0
  163. {calkit_python-0.26.0 → calkit_python-0.26.2}/test/pipeline.ipynb +0 -0
  164. {calkit_python-0.26.0 → calkit_python-0.26.2}/test/test-log.log +0 -0
  165. {calkit_python-0.26.0 → calkit_python-0.26.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.26.0
3
+ Version: 0.26.2
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.26.0"
1
+ __version__ = "0.26.2"
2
2
 
3
3
  from .core import * # noqa: F403, I001
4
4
  from . import git # noqa: F401
@@ -79,6 +79,11 @@ def check_environment(
79
79
  ),
80
80
  platform=env.get("platform"),
81
81
  deps=env.get("deps", []),
82
+ env_vars=env.get("env_vars", []),
83
+ ports=env.get("ports", []),
84
+ gpus=env.get("gpus"),
85
+ user=env.get("user"),
86
+ wdir=env.get("wdir"),
82
87
  quiet=not verbose,
83
88
  )
84
89
  elif env["kind"] == "conda":
@@ -164,6 +169,14 @@ def check_docker_env(
164
169
  str | None,
165
170
  typer.Option("--platform", help="Which platform(s) to build for."),
166
171
  ] = None,
172
+ user: Annotated[
173
+ str | None,
174
+ typer.Option("--user", help="Which user to run the container as."),
175
+ ] = None,
176
+ wdir: Annotated[
177
+ str | None,
178
+ typer.Option("--wdir", help="Working directory inside the container."),
179
+ ] = None,
167
180
  deps: Annotated[
168
181
  list[str],
169
182
  typer.Option(
@@ -172,6 +185,30 @@ def check_docker_env(
172
185
  help="Declare an explicit dependency for this Docker image.",
173
186
  ),
174
187
  ] = [],
188
+ env_vars: Annotated[
189
+ list[str],
190
+ typer.Option(
191
+ "--env-var",
192
+ "-e",
193
+ help="Declare an explicit environment variable for the container.",
194
+ ),
195
+ ] = [],
196
+ ports: Annotated[
197
+ list[str],
198
+ typer.Option(
199
+ "--port",
200
+ "-p",
201
+ help="Declare an explicit port for the container.",
202
+ ),
203
+ ] = [],
204
+ gpus: Annotated[
205
+ str | None,
206
+ typer.Option(
207
+ "--gpus",
208
+ "-g",
209
+ help="Declare an explicit GPU requirement for the container.",
210
+ ),
211
+ ] = None,
175
212
  quiet: Annotated[
176
213
  bool, typer.Option("--quiet", "-q", help="Be quiet.")
177
214
  ] = False,
@@ -277,6 +314,18 @@ def check_docker_env(
277
314
  inspect = get_docker_inspect()
278
315
  inspect["DockerfileMD5"] = dockerfile_md5
279
316
  inspect["DepsMD5s"] = deps_md5s
317
+ if platform is not None:
318
+ inspect["Platform"] = platform
319
+ if wdir is not None:
320
+ inspect["WorkDir"] = wdir
321
+ if user is not None:
322
+ inspect["User"] = user
323
+ if env_vars:
324
+ inspect["EnvVars"] = env_vars
325
+ if ports:
326
+ inspect["Ports"] = ports
327
+ if gpus:
328
+ inspect["GPUs"] = gpus
280
329
  lock_dir = os.path.dirname(lock_fpath)
281
330
  if lock_dir:
282
331
  os.makedirs(lock_dir, exist_ok=True)
@@ -1198,6 +1198,17 @@ def run_in_env(
1198
1198
  ]
1199
1199
  if platform:
1200
1200
  docker_cmd += ["--platform", platform]
1201
+ env_vars = env.get("env-vars", {})
1202
+ if env_vars:
1203
+ for key, value in env_vars.items():
1204
+ if isinstance(value, str):
1205
+ value = os.path.expandvars(value)
1206
+ docker_cmd += ["-e", f"{key}={value}"]
1207
+ if (gpus := env.get("gpus")) is not None:
1208
+ docker_cmd += ["--gpus", gpus]
1209
+ if ports := env.get("ports"):
1210
+ for port in ports:
1211
+ docker_cmd += ["-p", port]
1201
1212
  docker_user = env.get("user")
1202
1213
  if docker_user is None:
1203
1214
  try:
@@ -506,7 +506,14 @@ def new_question(
506
506
  def new_notebook(
507
507
  path: Annotated[str, typer.Argument(help="Notebook path (relative)")],
508
508
  title: Annotated[str, typer.Option("--title")],
509
- description: Annotated[str, typer.Option("--description")] = None,
509
+ description: Annotated[str | None, typer.Option("--description")] = None,
510
+ stage_name: Annotated[
511
+ str | None,
512
+ typer.Option(
513
+ "--stage",
514
+ help="Name of the pipeline stage that runs this notebook.",
515
+ ),
516
+ ] = None,
510
517
  commit: Annotated[bool, typer.Option("--commit")] = False,
511
518
  ):
512
519
  """Add a new notebook."""
@@ -516,8 +523,7 @@ def new_notebook(
516
523
  raise ValueError("Path is not a file")
517
524
  if not path.endswith(".ipynb"):
518
525
  raise ValueError("Path does not have .ipynb extension")
519
- # TODO: Add option to create stages that run `calkit nb clean` and
520
- # `calkit nb execute`
526
+ # TODO: Add option to create stage if one doesn't exist
521
527
  ck_info = calkit.load_calkit_info()
522
528
  notebooks = ck_info.get("notebooks", [])
523
529
  paths = [f.get("path") for f in notebooks]
@@ -526,6 +532,12 @@ def new_notebook(
526
532
  obj = dict(path=path, title=title)
527
533
  if description is not None:
528
534
  obj["description"] = description
535
+ if stage_name is not None:
536
+ if stage_name not in ck_info.get("pipeline", {}).get("stages", {}):
537
+ raise ValueError(
538
+ f"Stage '{stage_name}' does not exist in the pipeline"
539
+ )
540
+ obj["stage"] = stage_name
529
541
  notebooks.append(obj)
530
542
  ck_info["notebooks"] = notebooks
531
543
  with open("calkit.yaml", "w") as f:
@@ -533,7 +545,7 @@ def new_notebook(
533
545
  if commit:
534
546
  repo = git.Repo()
535
547
  repo.git.add("calkit.yaml")
536
- repo.git.commit(["-m", f"Add notebook {path}"])
548
+ repo.git.commit(["calkit.yaml", "-m", f"Add notebook {path}"])
537
549
 
538
550
 
539
551
  @new_app.command("docker-env")
@@ -574,6 +586,30 @@ def new_docker_env(
574
586
  "--add-layer", help="Add a layer (options: miniforge, foampy)."
575
587
  ),
576
588
  ] = [],
589
+ env_vars: Annotated[
590
+ list[str],
591
+ typer.Option(
592
+ "--env-var", help="Environment variables to set in the container."
593
+ ),
594
+ ] = [],
595
+ gpus: Annotated[str | None, typer.Option("--gpus")] = None,
596
+ args: Annotated[
597
+ list[str],
598
+ typer.Option(
599
+ "--arg",
600
+ help="Arguments to use when running container.",
601
+ ),
602
+ ] = [],
603
+ deps: Annotated[
604
+ list[str],
605
+ typer.Option(
606
+ "--dep",
607
+ help=(
608
+ "Path to add as a dependency, i.e., "
609
+ "a file that gets added to the container."
610
+ ),
611
+ ),
612
+ ] = [],
577
613
  wdir: Annotated[
578
614
  str, typer.Option("--wdir", help="Working directory.")
579
615
  ] = "/work",
@@ -587,6 +623,16 @@ def new_docker_env(
587
623
  str | None,
588
624
  typer.Option("--platform", help="Which platform(s) to build for."),
589
625
  ] = None,
626
+ ports: Annotated[
627
+ list[str],
628
+ typer.Option(
629
+ "--port",
630
+ help=(
631
+ "Ports to expose in the container, e.g., '8080:80'. "
632
+ "Can be specified multiple times."
633
+ ),
634
+ ),
635
+ ] = [],
590
636
  description: Annotated[
591
637
  str | None, typer.Option("--description", help="Description.")
592
638
  ] = None,
@@ -615,8 +661,11 @@ def new_docker_env(
615
661
  if path is not None and base and os.path.isfile(path) and not overwrite:
616
662
  raise_error("Output path already exists (use -f to overwrite)")
617
663
  if image_name is None:
618
- typer.echo("No image name specified; using environment name")
619
- image_name = name
664
+ typer.echo(
665
+ "No image name specified; using project and environment name"
666
+ )
667
+ _, project_name = calkit.detect_project_name().split("/")
668
+ image_name = f"{project_name}-{name}"
620
669
  repo = git.Repo()
621
670
  if base and path is not None:
622
671
  txt = "FROM " + base + "\n\n"
@@ -658,6 +707,21 @@ def new_docker_env(
658
707
  env["platform"] = platform
659
708
  if user:
660
709
  env["user"] = user
710
+ if gpus:
711
+ env["gpus"] = gpus
712
+ if env_vars:
713
+ env["env_vars"] = {}
714
+ for var in env_vars:
715
+ if "=" not in var:
716
+ raise_error(f"Invalid environment variable format: {var}")
717
+ key, value = var.split("=", 1)
718
+ env["env_vars"][key] = value
719
+ if args:
720
+ env["args"] = args
721
+ if deps:
722
+ env["deps"] = deps
723
+ if ports:
724
+ env["ports"] = ports
661
725
  envs[name] = env
662
726
  ck_info["environments"] = envs
663
727
  with open("calkit.yaml", "w") as f:
@@ -1788,6 +1852,88 @@ def new_latex_stage(
1788
1852
  )
1789
1853
 
1790
1854
 
1855
+ class NotebookStorage(str, Enum):
1856
+ git = "git"
1857
+ dvc = "dvc"
1858
+ none = None
1859
+
1860
+
1861
+ @new_app.command(name="jupyter-notebook-stage")
1862
+ def new_jupyter_notebook_stage(
1863
+ name: StageArgs.name,
1864
+ environment: StageArgs.environment,
1865
+ notebook_path: Annotated[
1866
+ str, typer.Option("--notebook-path", help="Path to notebook.")
1867
+ ],
1868
+ inputs: StageArgs.inputs = [],
1869
+ outputs: StageArgs.outputs = [],
1870
+ outs_git: StageArgs.outs_git = [],
1871
+ outs_git_no_delete: StageArgs.outs_git_no_delete = [],
1872
+ outs_no_delete: StageArgs.outs_no_delete = [],
1873
+ outs_no_store: StageArgs.outs_no_store = [],
1874
+ outs_no_store_no_delete: StageArgs.outs_no_store_no_delete = [],
1875
+ html_storage: Annotated[
1876
+ NotebookStorage,
1877
+ typer.Option(
1878
+ "--html-storage",
1879
+ help=("In what system to store the HTML output of the notebook."),
1880
+ ),
1881
+ ] = NotebookStorage.dvc,
1882
+ cleaned_ipynb_storage: Annotated[
1883
+ NotebookStorage,
1884
+ typer.Option(
1885
+ "--cleaned-ipynb-storage",
1886
+ help=(
1887
+ "In what system to store the cleaned ipynb output of "
1888
+ "the notebook."
1889
+ ),
1890
+ ),
1891
+ ] = NotebookStorage.git,
1892
+ executed_ipynb_storage: Annotated[
1893
+ NotebookStorage,
1894
+ typer.Option(
1895
+ "--executed-ipynb-storage",
1896
+ help=(
1897
+ "In what system to store the executed ipynb output of "
1898
+ "the notebook."
1899
+ ),
1900
+ ),
1901
+ ] = NotebookStorage.dvc,
1902
+ overwrite: StageArgs.overwrite = False,
1903
+ no_check: StageArgs.no_check = False,
1904
+ no_commit: StageArgs.no_commit = False,
1905
+ ):
1906
+ """Add a stage to the pipeline that runs a Jupyter notebook."""
1907
+ ck_outs = _to_ck_outs(
1908
+ outputs=outputs,
1909
+ outs_git=outs_git,
1910
+ outs_git_no_delete=outs_git_no_delete,
1911
+ outs_no_delete=outs_no_delete,
1912
+ outs_no_store=outs_no_store,
1913
+ outs_no_store_no_delete=outs_no_store_no_delete,
1914
+ )
1915
+ try:
1916
+ stage = calkit.models.pipeline.JupyterNotebookStage(
1917
+ kind="jupyter-notebook",
1918
+ environment=environment,
1919
+ inputs=inputs, # type: ignore
1920
+ outputs=ck_outs,
1921
+ notebook_path=notebook_path,
1922
+ html_storage=html_storage.value,
1923
+ cleaned_ipynb_storage=cleaned_ipynb_storage.value,
1924
+ executed_ipynb_storage=executed_ipynb_storage.value,
1925
+ )
1926
+ except Exception as e:
1927
+ raise_error(f"Invalid stage specification: {e}")
1928
+ _save_stage(
1929
+ stage=stage,
1930
+ name=name,
1931
+ overwrite=overwrite,
1932
+ no_check=no_check,
1933
+ no_commit=no_commit,
1934
+ )
1935
+
1936
+
1791
1937
  @new_app.command(name="release")
1792
1938
  def new_release(
1793
1939
  name: Annotated[
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
  import subprocess
7
+ from pathlib import PurePosixPath
7
8
 
8
9
  import typer
9
10
  from typing_extensions import Annotated
@@ -78,7 +79,8 @@ def execute_notebook(
78
79
  )
79
80
  folder = os.path.dirname(fpath_out_exec)
80
81
  os.makedirs(folder, exist_ok=True)
81
- fpath_out_exec = os.path.abspath(fpath_out_exec)
82
+ fname = os.path.basename(fpath_out_exec)
83
+ fpath_out_exec = PurePosixPath(fpath_out_exec).as_posix()
82
84
  cmd = [
83
85
  "jupyter",
84
86
  "nbconvert",
@@ -86,8 +88,10 @@ def execute_notebook(
86
88
  "--execute",
87
89
  "--to",
88
90
  "notebook",
91
+ "--output-dir",
92
+ PurePosixPath(folder).as_posix(),
89
93
  "--output",
90
- fpath_out_exec,
94
+ fname,
91
95
  ]
92
96
  run_in_env(cmd=cmd, env_name=env_name, no_check=no_check)
93
97
  for to_fmt in to:
@@ -101,7 +105,7 @@ def execute_notebook(
101
105
  raise_error(f"Invalid output format: '{to}'")
102
106
  folder = os.path.dirname(fpath_out)
103
107
  os.makedirs(folder, exist_ok=True)
104
- fpath_out = os.path.abspath(fpath_out)
108
+ fname_out = os.path.basename(fpath_out)
105
109
  # Now convert without executing or checking the environment
106
110
  cmd = [
107
111
  "jupyter",
@@ -109,8 +113,10 @@ def execute_notebook(
109
113
  fpath_out_exec,
110
114
  "--to",
111
115
  to_fmt,
116
+ "--output-dir",
117
+ PurePosixPath(folder).as_posix(),
112
118
  "--output",
113
- fpath_out,
119
+ fname_out,
114
120
  ]
115
121
  typer.echo(f"Exporting {to_fmt}")
116
122
  run_in_env(cmd=cmd, env_name=env_name, no_check=True)
@@ -13,6 +13,20 @@ from IPython.core.magic import Magics, cell_magic, magics_class
13
13
 
14
14
  import calkit
15
15
 
16
+ # This code used to ensure we can import from the project root in stage
17
+ # scripts
18
+ PROJECT_ROOT_PATH_PY_CODE = """
19
+ import os
20
+ import sys
21
+
22
+ sys.path.insert(
23
+ 0,
24
+ os.path.abspath(
25
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../")
26
+ ),
27
+ )
28
+ """
29
+
16
30
 
17
31
  def _parse_string_arg(val: str):
18
32
  try:
@@ -191,6 +205,7 @@ class Calkit(Magics):
191
205
  # First, let's write this cell out to a script, ensuring that we
192
206
  # load the important state at the top
193
207
  script_txt = "# This script was automatically generated by Calkit\n\n"
208
+ script_txt += PROJECT_ROOT_PATH_PY_CODE + "\n\n"
194
209
  script_txt += "import calkit\n\n"
195
210
  if args.dep:
196
211
  for d in args.dep:
@@ -65,6 +65,9 @@ def to_dvc(
65
65
  outs = []
66
66
  if env_fpath is not None:
67
67
  deps.append(env_fpath)
68
+ # Docker envs sometimes have deps, so add those too
69
+ if env.get("deps", []):
70
+ deps += env["deps"]
68
71
  outs.append({lock_fpath: dict(cache=False, persist=True)})
69
72
  stage = dict(cmd=cmd, deps=deps, outs=outs, always_changed=True)
70
73
  stage["desc"] = (
@@ -28,6 +28,7 @@ def test_run_in_env(tmp_dir):
28
28
  # First create a new Docker environment for this bare project
29
29
  subprocess.check_call(
30
30
  "calkit new docker-env "
31
+ "--image my-image "
31
32
  "--name my-image "
32
33
  "--from ubuntu "
33
34
  "--add-layer uv "
@@ -80,8 +80,10 @@ of environments a project can use, and they can be of any type.
80
80
 
81
81
  ## Adding a notebook to the pipeline
82
82
 
83
- A notebook can be added to the pipeline by editing the project's `calkit.yaml`
84
- file directly, using a `jupyter-notebook` stage.
83
+ A notebook can be added to the pipeline either with
84
+ `calkit new jupyter-notebook-stage` or
85
+ by editing the project's `calkit.yaml`
86
+ file directly.
85
87
  For example:
86
88
 
87
89
  ```yaml
File without changes
File without changes
File without changes
File without changes