calkit-python 0.23.0__tar.gz → 0.24.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.
- {calkit_python-0.23.0 → calkit_python-0.24.0}/PKG-INFO +1 -1
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/__init__.py +1 -1
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/check.py +136 -21
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/main.py +1 -0
- calkit_python-0.24.0/calkit/cli/notebooks.py +116 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/overleaf.py +1 -1
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/dvc.py +10 -7
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/magics.py +40 -11
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/models/pipeline.py +115 -6
- calkit_python-0.24.0/calkit/notebooks.py +31 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/pipeline.py +25 -32
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/models/test_pipeline.py +11 -1
- calkit_python-0.24.0/calkit/tests/test_dvc.py +37 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_magics.py +38 -1
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_pipeline.py +46 -1
- calkit_python-0.24.0/docs/dependencies.md +51 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/environments.md +10 -3
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/pipeline/index.md +2 -2
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/notebook-pipeline.md +31 -8
- {calkit_python-0.23.0 → calkit_python-0.24.0}/mkdocs.yml +3 -2
- {calkit_python-0.23.0 → calkit_python-0.24.0}/test/pipeline.ipynb +4 -3
- calkit_python-0.23.0/calkit/cli/notebooks.py +0 -76
- calkit_python-0.23.0/calkit/tests/test_dvc.py +0 -23
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.github/FUNDING.yml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.github/workflows/docs.yml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.github/workflows/format.yml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.github/workflows/publish-test.yml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.github/workflows/publish.yml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.github/workflows/test.yml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.gitignore +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.pre-commit-config.yaml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/.python-version +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/CONTRIBUTING.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/LICENSE +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/Makefile +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/README.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/__main__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/calc.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/check.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/cloud.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/config.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/core.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/import_.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/list.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/new.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/office.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cli/update.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/cloud.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/conda.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/config.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/core.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/datasets.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/docker.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/environments.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/git.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/github.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/gui.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/jupyter.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/matlab.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/models/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/models/core.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/models/iteration.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/office.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/ops.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/releases.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/server.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/core.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/article/paper.tex +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/core.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/jfm/jfm.bst +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/jfm/jfm.cls +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/jfm/paper.tex +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/templates/latex/jfm/upmath.sty +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/cli/test_config.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/cli/test_main.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/cli/test_new.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/models/__init__.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_calc.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_check.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_conda.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_core.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/tests/test_templates.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/calkit/zenodo.py +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/CNAME +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/apps.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/calculations.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/calkit-yaml.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/cli-reference.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/cloud-integration.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/datasets.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/examples.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/help.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/img/c-to-the-k-white.svg +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/img/calkit-no-bg.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/img/connect-zenodo.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/index.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/installation.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/local-server.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/overleaf.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/pipeline/manual-steps.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/references.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/releases.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/adding-latex-pub-docker.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/conda-envs.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/existing-project.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/first-project.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/push.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/chart-more-rows.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/create-project.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/excel-chart.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/excel-data.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/needs-clone.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/new-stage.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/status-more-rows.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/untracked-data.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/updated-publication.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/office/workflow-page.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/clone.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/create-project.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/new-token.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/reclone.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/img/run-proc.png +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/index.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/latex-codespaces.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/matlab.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/office.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/openfoam.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/tutorials/procedures.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/docs/version-control.md +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/pyproject.toml +0 -0
- {calkit_python-0.23.0 → calkit_python-0.24.0}/uv.lock +0 -0
|
@@ -11,6 +11,8 @@ import subprocess
|
|
|
11
11
|
import warnings
|
|
12
12
|
from typing import Annotated
|
|
13
13
|
|
|
14
|
+
from calkit.environments import get_env_lock_fpath
|
|
15
|
+
|
|
14
16
|
# See https://github.com/calkit/calkit/issues/346
|
|
15
17
|
with warnings.catch_warnings():
|
|
16
18
|
warnings.filterwarnings("ignore", category=UserWarning)
|
|
@@ -40,28 +42,101 @@ def check_repro(
|
|
|
40
42
|
typer.echo(res.to_pretty().encode("utf-8", errors="replace"))
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
@check_app.command(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
@check_app.command(
|
|
46
|
+
name="env",
|
|
47
|
+
help="Check that an environment is up-to-date (alias for 'environment').",
|
|
48
|
+
)
|
|
49
|
+
@check_app.command(name="environment")
|
|
50
|
+
def check_environment(
|
|
51
|
+
env_name: Annotated[
|
|
47
52
|
str,
|
|
48
|
-
typer.Option(
|
|
49
|
-
"--if-error", help="Command to run if there is an error."
|
|
50
|
-
),
|
|
53
|
+
typer.Option("--name", "-n", help="Name of the environment to check."),
|
|
51
54
|
],
|
|
55
|
+
verbose: Annotated[
|
|
56
|
+
bool, typer.Option("--verbose", help="Print verbose output.")
|
|
57
|
+
] = False,
|
|
52
58
|
):
|
|
53
|
-
"""Check that
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
"""Check that an environment is up-to-date."""
|
|
60
|
+
dotenv.load_dotenv(dotenv_path=".env", verbose=verbose)
|
|
61
|
+
ck_info = calkit.load_calkit_info(process_includes="environments")
|
|
62
|
+
envs = ck_info.get("environments", {})
|
|
63
|
+
if not envs:
|
|
64
|
+
raise_error("No environments defined in calkit.yaml")
|
|
65
|
+
if isinstance(envs, list):
|
|
66
|
+
raise_error("Error: Environments should be a dict, not a list")
|
|
67
|
+
assert isinstance(envs, dict)
|
|
68
|
+
if env_name not in envs:
|
|
69
|
+
raise_error(f"Environment '{env_name}' does not exist")
|
|
70
|
+
env = envs[env_name]
|
|
71
|
+
if env["kind"] == "docker":
|
|
72
|
+
if "image" not in env:
|
|
73
|
+
raise_error("Image must be defined for Docker environments")
|
|
74
|
+
if "path" in env:
|
|
75
|
+
check_docker_env(
|
|
76
|
+
tag=env["image"],
|
|
77
|
+
fpath=env["path"],
|
|
78
|
+
lock_fpath=get_env_lock_fpath(
|
|
79
|
+
env=env, env_name=env_name, as_posix=False
|
|
80
|
+
),
|
|
81
|
+
platform=env.get("platform"),
|
|
82
|
+
deps=env.get("deps", []),
|
|
83
|
+
quiet=not verbose,
|
|
84
|
+
)
|
|
85
|
+
elif env["kind"] == "conda":
|
|
86
|
+
check_conda_env(
|
|
87
|
+
env_fpath=env["path"],
|
|
88
|
+
output_fpath=get_env_lock_fpath(
|
|
89
|
+
env=env, env_name=env_name, as_posix=False
|
|
90
|
+
),
|
|
91
|
+
relaxed=True, # TODO: Add option?
|
|
92
|
+
quiet=not verbose,
|
|
93
|
+
)
|
|
94
|
+
elif env["kind"] in ["pixi", "uv"]:
|
|
95
|
+
cmd = [env["kind"], "lock"]
|
|
96
|
+
if verbose:
|
|
97
|
+
typer.echo(f"Running command: {cmd}")
|
|
59
98
|
try:
|
|
60
|
-
|
|
61
|
-
subprocess.check_call(if_error, shell=True)
|
|
62
|
-
typer.echo("Fallback call succeeded")
|
|
99
|
+
subprocess.check_call(cmd)
|
|
63
100
|
except subprocess.CalledProcessError:
|
|
64
|
-
raise_error("
|
|
101
|
+
raise_error(f"Failed to check {env['kind']} environment")
|
|
102
|
+
elif (kind := env["kind"]) in ["uv-venv", "venv"]:
|
|
103
|
+
if "prefix" not in env:
|
|
104
|
+
raise_error("venv environments require a prefix")
|
|
105
|
+
if "path" not in env:
|
|
106
|
+
raise_error("venv environments require a path")
|
|
107
|
+
prefix = env["prefix"]
|
|
108
|
+
path = env["path"]
|
|
109
|
+
# Check environment
|
|
110
|
+
check_venv(
|
|
111
|
+
path=path,
|
|
112
|
+
prefix=prefix,
|
|
113
|
+
use_uv=kind == "uv-venv",
|
|
114
|
+
python=env.get("python"),
|
|
115
|
+
lock_fpath=get_env_lock_fpath(
|
|
116
|
+
env=env, env_name=env_name, as_posix=False
|
|
117
|
+
),
|
|
118
|
+
verbose=verbose,
|
|
119
|
+
)
|
|
120
|
+
elif env["kind"] == "ssh":
|
|
121
|
+
# TODO: How to check SSH environments?
|
|
122
|
+
# Maybe just check that we can connect
|
|
123
|
+
raise_error(
|
|
124
|
+
"Environment checking not implemented for SSH environments"
|
|
125
|
+
)
|
|
126
|
+
elif env["kind"] == "renv":
|
|
127
|
+
try:
|
|
128
|
+
subprocess.check_call(["Rscript", "-e", "'renv::restore()'"])
|
|
129
|
+
except subprocess.CalledProcessError:
|
|
130
|
+
raise_error("Failed to check renv")
|
|
131
|
+
elif env["kind"] == "matlab":
|
|
132
|
+
check_matlab_env(
|
|
133
|
+
env_name=env_name,
|
|
134
|
+
output_fpath=get_env_lock_fpath(
|
|
135
|
+
env=env, env_name=env_name, as_posix=False
|
|
136
|
+
), # type: ignore
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
raise_error(f"Environment kind '{env['kind']}' not supported")
|
|
65
140
|
|
|
66
141
|
|
|
67
142
|
@check_app.command(name="docker-env")
|
|
@@ -273,6 +348,9 @@ def check_venv(
|
|
|
273
348
|
"--python", help="Python version to specify if using uv."
|
|
274
349
|
),
|
|
275
350
|
] = None,
|
|
351
|
+
quiet: Annotated[
|
|
352
|
+
bool, typer.Option("--quiet", help="Do not print any output")
|
|
353
|
+
] = False,
|
|
276
354
|
verbose: Annotated[
|
|
277
355
|
bool, typer.Option("--verbose", help="Print verbose output.")
|
|
278
356
|
] = False,
|
|
@@ -283,7 +361,7 @@ def check_venv(
|
|
|
283
361
|
["uv", "venv"] if kind == "uv-venv" else ["python", "-m", "venv"]
|
|
284
362
|
)
|
|
285
363
|
pip_cmd = "pip" if kind == "venv" else "uv pip"
|
|
286
|
-
pip_install_args = "-q"
|
|
364
|
+
pip_install_args = "-q" if quiet else ""
|
|
287
365
|
if python is not None and not use_uv:
|
|
288
366
|
raise_error("Python version cannot be specified if not using uv")
|
|
289
367
|
if python is not None and use_uv:
|
|
@@ -319,7 +397,7 @@ def check_venv(
|
|
|
319
397
|
try:
|
|
320
398
|
if verbose:
|
|
321
399
|
typer.echo(f"Running command: {check_cmd}")
|
|
322
|
-
subprocess.
|
|
400
|
+
subprocess.check_call(
|
|
323
401
|
check_cmd,
|
|
324
402
|
shell=True,
|
|
325
403
|
cwd=wdir,
|
|
@@ -374,7 +452,11 @@ def check_matlab_env(
|
|
|
374
452
|
|
|
375
453
|
|
|
376
454
|
@check_app.command(name="env-vars")
|
|
377
|
-
def check_env_vars(
|
|
455
|
+
def check_env_vars(
|
|
456
|
+
verbose: Annotated[
|
|
457
|
+
bool, typer.Option("--verbose", "-v", help="Print verbose output")
|
|
458
|
+
] = False,
|
|
459
|
+
):
|
|
378
460
|
"""Check that the project's required environmental variables exist."""
|
|
379
461
|
typer.echo("Checking project environmental variables")
|
|
380
462
|
dotenv.load_dotenv(dotenv_path=".env")
|
|
@@ -383,11 +465,20 @@ def check_env_vars():
|
|
|
383
465
|
env_var_deps = {}
|
|
384
466
|
for d in deps:
|
|
385
467
|
if isinstance(d, dict):
|
|
386
|
-
|
|
468
|
+
keys = list(d.keys())
|
|
469
|
+
if len(keys) > 1:
|
|
470
|
+
raise_error(
|
|
471
|
+
f"Malformed dependency: {d}\n"
|
|
472
|
+
"Dependencies with attributes should have a single key "
|
|
473
|
+
"(their name)"
|
|
474
|
+
)
|
|
475
|
+
name = keys[0]
|
|
387
476
|
attrs = list(d.values())[0]
|
|
388
477
|
if attrs.get("kind") == "env-var":
|
|
389
478
|
env_var_deps[name] = attrs
|
|
390
479
|
for name, attrs in env_var_deps.items():
|
|
480
|
+
if verbose:
|
|
481
|
+
typer.echo(f"Checking for environmental variable '{name}'")
|
|
391
482
|
if name not in os.environ:
|
|
392
483
|
typer.echo(f"Missing env var '{name}'")
|
|
393
484
|
if "default" in attrs:
|
|
@@ -446,3 +537,27 @@ def check_pipeline(
|
|
|
446
537
|
raise_error(
|
|
447
538
|
f"Failed to compile pipeline: {e.__class__.__name__}: {e}"
|
|
448
539
|
)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@check_app.command(name="call")
|
|
543
|
+
def check_call(
|
|
544
|
+
cmd: Annotated[str, typer.Argument(help="Command to check.")],
|
|
545
|
+
if_error: Annotated[
|
|
546
|
+
str,
|
|
547
|
+
typer.Option(
|
|
548
|
+
"--if-error", help="Command to run if there is an error."
|
|
549
|
+
),
|
|
550
|
+
],
|
|
551
|
+
):
|
|
552
|
+
"""Check that a command succeeds and run an alternate if not."""
|
|
553
|
+
try:
|
|
554
|
+
subprocess.check_call(cmd, shell=True)
|
|
555
|
+
typer.echo("Command succeeded")
|
|
556
|
+
except subprocess.CalledProcessError:
|
|
557
|
+
typer.echo("Command failed")
|
|
558
|
+
try:
|
|
559
|
+
typer.echo("Attempting fallback call")
|
|
560
|
+
subprocess.check_call(if_error, shell=True)
|
|
561
|
+
typer.echo("Fallback call succeeded")
|
|
562
|
+
except subprocess.CalledProcessError:
|
|
563
|
+
raise_error("Fallback call failed")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Notebooks CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
|
|
11
|
+
import calkit.notebooks
|
|
12
|
+
from calkit.cli.core import raise_error
|
|
13
|
+
|
|
14
|
+
notebooks_app = typer.Typer(no_args_is_help=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@notebooks_app.command("clean")
|
|
18
|
+
def clean_notebook_outputs(path: str):
|
|
19
|
+
"""Clean notebook and place a copy in the cleaned notebooks directory.
|
|
20
|
+
|
|
21
|
+
This can be useful to use as a preprocessing DVC stage to use a clean
|
|
22
|
+
notebook as a dependency for a stage that caches and executed notebook.
|
|
23
|
+
"""
|
|
24
|
+
if os.path.isabs(path):
|
|
25
|
+
raise ValueError("Path must be relative")
|
|
26
|
+
fpath_out = calkit.notebooks.get_cleaned_notebook_path(path)
|
|
27
|
+
folder = os.path.dirname(fpath_out)
|
|
28
|
+
os.makedirs(folder, exist_ok=True)
|
|
29
|
+
fpath_out = os.path.abspath(fpath_out)
|
|
30
|
+
subprocess.call(
|
|
31
|
+
[
|
|
32
|
+
"jupyter",
|
|
33
|
+
"nbconvert",
|
|
34
|
+
path,
|
|
35
|
+
"--clear-output",
|
|
36
|
+
"--to",
|
|
37
|
+
"notebook",
|
|
38
|
+
"--output",
|
|
39
|
+
fpath_out,
|
|
40
|
+
]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@notebooks_app.command("execute")
|
|
45
|
+
def execute_notebook(
|
|
46
|
+
path: str,
|
|
47
|
+
env_name: Annotated[
|
|
48
|
+
str,
|
|
49
|
+
typer.Option(
|
|
50
|
+
"--environment",
|
|
51
|
+
"-e",
|
|
52
|
+
help="Environment name in which to run the notebook.",
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
to: Annotated[
|
|
56
|
+
list[str],
|
|
57
|
+
typer.Option("--to", help="Output format ('html' or 'notebook')."),
|
|
58
|
+
] = ["notebook"],
|
|
59
|
+
no_check: Annotated[
|
|
60
|
+
bool,
|
|
61
|
+
typer.Option(
|
|
62
|
+
"--no-check", help="Do not check environment before executing."
|
|
63
|
+
),
|
|
64
|
+
] = False,
|
|
65
|
+
):
|
|
66
|
+
"""Execute notebook and place a copy in the relevant directory.
|
|
67
|
+
|
|
68
|
+
This can be useful to use as a preprocessing DVC stage to use a clean
|
|
69
|
+
notebook as a dependency for a stage that caches and executed notebook.
|
|
70
|
+
"""
|
|
71
|
+
from calkit.cli.main import run_in_env
|
|
72
|
+
|
|
73
|
+
if os.path.isabs(path):
|
|
74
|
+
raise ValueError("Path must be relative")
|
|
75
|
+
# First, always execute the notebook and save as ipynb
|
|
76
|
+
fpath_out_exec = calkit.notebooks.get_executed_notebook_path(
|
|
77
|
+
notebook_path=path, to="notebook", as_posix=True
|
|
78
|
+
)
|
|
79
|
+
folder = os.path.dirname(fpath_out_exec)
|
|
80
|
+
os.makedirs(folder, exist_ok=True)
|
|
81
|
+
fpath_out_exec = os.path.abspath(fpath_out_exec)
|
|
82
|
+
cmd = [
|
|
83
|
+
"jupyter",
|
|
84
|
+
"nbconvert",
|
|
85
|
+
path,
|
|
86
|
+
"--execute",
|
|
87
|
+
"--to",
|
|
88
|
+
"notebook",
|
|
89
|
+
"--output",
|
|
90
|
+
fpath_out_exec,
|
|
91
|
+
]
|
|
92
|
+
run_in_env(cmd=cmd, env_name=env_name, no_check=no_check)
|
|
93
|
+
for to_fmt in to:
|
|
94
|
+
if to_fmt != "notebook":
|
|
95
|
+
try:
|
|
96
|
+
fpath_out = calkit.notebooks.get_executed_notebook_path(
|
|
97
|
+
notebook_path=path,
|
|
98
|
+
to=to_fmt, # type: ignore
|
|
99
|
+
)
|
|
100
|
+
except ValueError:
|
|
101
|
+
raise_error(f"Invalid output format: '{to}'")
|
|
102
|
+
folder = os.path.dirname(fpath_out)
|
|
103
|
+
os.makedirs(folder, exist_ok=True)
|
|
104
|
+
fpath_out = os.path.abspath(fpath_out)
|
|
105
|
+
# Now convert without executing or checking the environment
|
|
106
|
+
cmd = [
|
|
107
|
+
"jupyter",
|
|
108
|
+
"nbconvert",
|
|
109
|
+
fpath_out_exec,
|
|
110
|
+
"--to",
|
|
111
|
+
to_fmt,
|
|
112
|
+
"--output",
|
|
113
|
+
fpath_out,
|
|
114
|
+
]
|
|
115
|
+
typer.echo(f"Exporting {to_fmt}")
|
|
116
|
+
run_in_env(cmd=cmd, env_name=env_name, no_check=True)
|
|
@@ -141,7 +141,7 @@ def import_publication(
|
|
|
141
141
|
pdf_path = sync_paths[0].removesuffix(".tex") + ".pdf"
|
|
142
142
|
typer.echo(f"Using PDF path: {pdf_path}")
|
|
143
143
|
tex_path = pdf_path.removesuffix(".pdf") + ".tex"
|
|
144
|
-
pub_path =
|
|
144
|
+
pub_path = PurePosixPath(dest_dir, pdf_path).as_posix()
|
|
145
145
|
pub_paths = [pub.get("path") for pub in pubs]
|
|
146
146
|
if not overwrite and pub_path in pub_paths:
|
|
147
147
|
raise_error(
|
|
@@ -155,7 +155,7 @@ def read_pipeline(wdir: str = ".") -> dict:
|
|
|
155
155
|
return calkit.ryaml.load(f)
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def get_remotes(wdir: str = None) -> dict[str, str]:
|
|
158
|
+
def get_remotes(wdir: str | None = None) -> dict[str, str]:
|
|
159
159
|
"""Get a dictionary of DVC remotes, keyed by name, with URL as the
|
|
160
160
|
value.
|
|
161
161
|
"""
|
|
@@ -169,13 +169,16 @@ def get_remotes(wdir: str = None) -> dict[str, str]:
|
|
|
169
169
|
if not out:
|
|
170
170
|
return {}
|
|
171
171
|
resp = {}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
out = out.replace("(default)", "").strip()
|
|
173
|
+
is_name = True
|
|
174
|
+
for token in out.split():
|
|
175
|
+
token = token.strip()
|
|
176
|
+
if is_name:
|
|
177
|
+
name = token
|
|
178
|
+
else:
|
|
179
|
+
url = token
|
|
178
180
|
resp[name] = url
|
|
181
|
+
is_name = not is_name
|
|
179
182
|
return resp
|
|
180
183
|
|
|
181
184
|
|
|
@@ -38,12 +38,11 @@ class Calkit(Magics):
|
|
|
38
38
|
)
|
|
39
39
|
@magic_arguments.argument(
|
|
40
40
|
"--env",
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
),
|
|
41
|
+
"--environment",
|
|
42
|
+
"-e",
|
|
43
|
+
required=True,
|
|
44
|
+
help=("Environment with which to run this cell."),
|
|
45
|
+
type=_parse_string_arg,
|
|
47
46
|
)
|
|
48
47
|
@magic_arguments.argument(
|
|
49
48
|
"--dep",
|
|
@@ -112,6 +111,19 @@ class Calkit(Magics):
|
|
|
112
111
|
help="Description for Calkit output object.",
|
|
113
112
|
type=_parse_string_arg,
|
|
114
113
|
)
|
|
114
|
+
@magic_arguments.argument(
|
|
115
|
+
"--out-storage",
|
|
116
|
+
choices=["dvc", "git", "none"],
|
|
117
|
+
help="Which version control system to use to store the output.",
|
|
118
|
+
default="dvc",
|
|
119
|
+
type=_parse_string_arg,
|
|
120
|
+
)
|
|
121
|
+
@magic_arguments.argument(
|
|
122
|
+
"--verbose",
|
|
123
|
+
help="Print verbose output.",
|
|
124
|
+
default=False,
|
|
125
|
+
action="store_true",
|
|
126
|
+
)
|
|
115
127
|
@cell_magic
|
|
116
128
|
def stage(self, line, cell):
|
|
117
129
|
"""Turn a notebook cell into a DVC pipeline stage.
|
|
@@ -222,21 +234,22 @@ class Calkit(Magics):
|
|
|
222
234
|
with open(script_fpath, "w") as f:
|
|
223
235
|
f.write(script_txt)
|
|
224
236
|
# Create a DVC stage that runs the script, defining the appropriate
|
|
225
|
-
# dependencies and outputs
|
|
237
|
+
# dependencies and outputs
|
|
238
|
+
# TODO: Insert this into dvc.yaml directly, since DVC reformats
|
|
226
239
|
cmd = [
|
|
227
240
|
sys.executable,
|
|
228
241
|
"-m",
|
|
229
242
|
"dvc",
|
|
230
243
|
"stage",
|
|
231
244
|
"add",
|
|
232
|
-
"-q",
|
|
233
245
|
"-n",
|
|
234
246
|
args.name,
|
|
235
|
-
"--run",
|
|
236
247
|
"--force",
|
|
237
248
|
"-d",
|
|
238
249
|
_posix_path(script_fpath),
|
|
239
250
|
]
|
|
251
|
+
if not args.verbose:
|
|
252
|
+
cmd.append("-q")
|
|
240
253
|
if args.dep:
|
|
241
254
|
for dep in args.dep:
|
|
242
255
|
dep_split = dep.split(":")
|
|
@@ -259,8 +272,13 @@ class Calkit(Magics):
|
|
|
259
272
|
kws = dict(stage_name=args.name, out_name=out_name)
|
|
260
273
|
if len(out_split) > 1:
|
|
261
274
|
kws["fmt"] = out_split[1]
|
|
275
|
+
# Figure out DVC caching
|
|
276
|
+
if args.out_storage == "dvc":
|
|
277
|
+
out_flag = "-o"
|
|
278
|
+
else:
|
|
279
|
+
out_flag = "--outs-no-cache"
|
|
262
280
|
cmd += [
|
|
263
|
-
|
|
281
|
+
out_flag,
|
|
264
282
|
_posix_path(calkit.get_notebook_stage_out_path(**kws)),
|
|
265
283
|
]
|
|
266
284
|
if args.out_path:
|
|
@@ -274,7 +292,18 @@ class Calkit(Magics):
|
|
|
274
292
|
stage_cmd = xenv + " -- " + stage_cmd
|
|
275
293
|
cmd.append(stage_cmd)
|
|
276
294
|
try:
|
|
277
|
-
subprocess.run(
|
|
295
|
+
subprocess.run(
|
|
296
|
+
cmd, check=True, capture_output=not args.verbose, text=True
|
|
297
|
+
)
|
|
298
|
+
except subprocess.CalledProcessError as e:
|
|
299
|
+
print(f"Error: {e.stderr}")
|
|
300
|
+
raise e
|
|
301
|
+
# Now run the stage
|
|
302
|
+
run_cmd = [sys.executable, "-m", "dvc", "repro", args.name]
|
|
303
|
+
try:
|
|
304
|
+
subprocess.run(
|
|
305
|
+
run_cmd, check=True, capture_output=False, text=True
|
|
306
|
+
)
|
|
278
307
|
except subprocess.CalledProcessError as e:
|
|
279
308
|
print(f"Error: {e.stderr}")
|
|
280
309
|
raise e
|
|
@@ -13,6 +13,10 @@ from calkit.models.iteration import (
|
|
|
13
13
|
ParametersType,
|
|
14
14
|
RangeIteration,
|
|
15
15
|
)
|
|
16
|
+
from calkit.notebooks import (
|
|
17
|
+
get_cleaned_notebook_path,
|
|
18
|
+
get_executed_notebook_path,
|
|
19
|
+
)
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class Input(BaseModel):
|
|
@@ -156,7 +160,7 @@ class Stage(BaseModel):
|
|
|
156
160
|
|
|
157
161
|
|
|
158
162
|
class PythonScriptStage(Stage):
|
|
159
|
-
kind: Literal["python-script"]
|
|
163
|
+
kind: Literal["python-script"] = "python-script"
|
|
160
164
|
script_path: str
|
|
161
165
|
args: list[str] = []
|
|
162
166
|
|
|
@@ -292,6 +296,9 @@ class JupyterNotebookStage(Stage):
|
|
|
292
296
|
2. Notebook running, depending on the cleaned notebook, and optionally
|
|
293
297
|
producing HTML output.
|
|
294
298
|
|
|
299
|
+
Alternatively, we could force the use of ``nbstripout`` so the cleaned
|
|
300
|
+
notebook is saved at the notebook path.
|
|
301
|
+
|
|
295
302
|
TODO: Can/should we do something like Papermill and let users modify
|
|
296
303
|
parameters in the notebook?
|
|
297
304
|
|
|
@@ -299,15 +306,115 @@ class JupyterNotebookStage(Stage):
|
|
|
299
306
|
needing to be run from top to bottom every time they change.
|
|
300
307
|
"""
|
|
301
308
|
|
|
302
|
-
kind: Literal["jupyter-notebook"]
|
|
309
|
+
kind: Literal["jupyter-notebook"] = "jupyter-notebook"
|
|
303
310
|
notebook_path: str
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
311
|
+
cleaned_ipynb_storage: Literal["git", "dvc"] | None = "git"
|
|
312
|
+
executed_ipynb_storage: Literal["git", "dvc"] | None = "dvc"
|
|
313
|
+
html_storage: Literal["git", "dvc"] | None = "dvc"
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def cleaned_notebook_path(self) -> str:
|
|
317
|
+
return get_cleaned_notebook_path(self.notebook_path, as_posix=True)
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def executed_notebook_path(self) -> str:
|
|
321
|
+
return get_executed_notebook_path(
|
|
322
|
+
self.notebook_path, to="notebook", as_posix=True
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def html_path(self) -> str:
|
|
327
|
+
return get_executed_notebook_path(
|
|
328
|
+
self.notebook_path, to="html", as_posix=True
|
|
329
|
+
)
|
|
307
330
|
|
|
308
331
|
@property
|
|
309
332
|
def dvc_deps(self) -> list[str]:
|
|
310
|
-
return [self.
|
|
333
|
+
return [self.cleaned_notebook_path] + super().dvc_deps
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def dvc_cmd(self) -> str:
|
|
337
|
+
cmd = f"calkit nb execute --environment {self.environment} --no-check"
|
|
338
|
+
if self.html_storage:
|
|
339
|
+
cmd += " --to html"
|
|
340
|
+
cmd += f' "{self.notebook_path}"'
|
|
341
|
+
return cmd
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def dvc_outs(self) -> list[str | dict]:
|
|
345
|
+
outs = super().dvc_outs
|
|
346
|
+
# TODO: This should also export HTML?
|
|
347
|
+
exec_nb_path = self.executed_notebook_path
|
|
348
|
+
exec_nb_out = {
|
|
349
|
+
exec_nb_path: {"cache": self.executed_ipynb_storage == "dvc"}
|
|
350
|
+
}
|
|
351
|
+
outs = outs + [exec_nb_out]
|
|
352
|
+
return outs
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def dvc_clean_stage(self) -> dict:
|
|
356
|
+
"""Create a DVC stage for notebook cleaning so the cleaned notebook
|
|
357
|
+
can be used as a DVC dependency.
|
|
358
|
+
|
|
359
|
+
TODO: Should we use Jupytext for this so diffs are nice?
|
|
360
|
+
"""
|
|
361
|
+
clean_nb_path = self.cleaned_notebook_path
|
|
362
|
+
stage = {
|
|
363
|
+
"cmd": f'calkit nb clean "{self.notebook_path}"',
|
|
364
|
+
"deps": [self.notebook_path],
|
|
365
|
+
"outs": [
|
|
366
|
+
{clean_nb_path: {"cache": self.cleaned_ipynb_storage == "dvc"}}
|
|
367
|
+
],
|
|
368
|
+
}
|
|
369
|
+
return stage
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def notebook_outputs(self) -> list[PathOutput]:
|
|
373
|
+
"""Return a list of special notebook outputs so their storage can be
|
|
374
|
+
respected.
|
|
375
|
+
"""
|
|
376
|
+
return [
|
|
377
|
+
PathOutput(
|
|
378
|
+
path=self.cleaned_notebook_path,
|
|
379
|
+
storage=self.cleaned_ipynb_storage,
|
|
380
|
+
),
|
|
381
|
+
PathOutput(
|
|
382
|
+
path=self.executed_notebook_path,
|
|
383
|
+
storage=self.executed_ipynb_storage,
|
|
384
|
+
),
|
|
385
|
+
PathOutput(path=self.html_path, storage=self.html_storage),
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class WordToPdfStage(Stage):
|
|
390
|
+
kind: Literal["word-to-pdf"] = "word-to-pdf"
|
|
391
|
+
word_doc_path: str
|
|
392
|
+
environment: str = "_system"
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def dvc_deps(self) -> list[str]:
|
|
396
|
+
return [self.word_doc_path] + super().dvc_deps
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def out_path(self) -> str:
|
|
400
|
+
return PurePosixPath(
|
|
401
|
+
self.word_doc_path.removesuffix(".docx") + ".pdf"
|
|
402
|
+
).as_posix()
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def dvc_outs(self) -> list[str | dict]:
|
|
406
|
+
outs = super().dvc_outs
|
|
407
|
+
out_path = self.out_path
|
|
408
|
+
if out_path not in outs:
|
|
409
|
+
outs.append(out_path)
|
|
410
|
+
return outs
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def dvc_cmd(self) -> str:
|
|
414
|
+
return (
|
|
415
|
+
f'calkit office word-to-pdf "{self.word_doc_path}" '
|
|
416
|
+
f'-o "{self.out_path}"'
|
|
417
|
+
)
|
|
311
418
|
|
|
312
419
|
|
|
313
420
|
class Pipeline(BaseModel):
|
|
@@ -322,6 +429,8 @@ class Pipeline(BaseModel):
|
|
|
322
429
|
| ShellScriptStage
|
|
323
430
|
| DockerCommandStage
|
|
324
431
|
| RScriptStage
|
|
432
|
+
| WordToPdfStage
|
|
433
|
+
| JupyterNotebookStage
|
|
325
434
|
),
|
|
326
435
|
Discriminator("kind"),
|
|
327
436
|
],
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Functionality for working with notebooks."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_executed_notebook_path(
|
|
9
|
+
notebook_path: str, to: Literal["html", "notebook"], as_posix: bool = True
|
|
10
|
+
) -> str:
|
|
11
|
+
"""Return the path of an executed notebook."""
|
|
12
|
+
nb_dir = os.path.dirname(notebook_path)
|
|
13
|
+
nb_fname = os.path.basename(notebook_path)
|
|
14
|
+
if to == "html":
|
|
15
|
+
fname_out = nb_fname.removesuffix(".ipynb") + ".html"
|
|
16
|
+
else:
|
|
17
|
+
fname_out = nb_fname
|
|
18
|
+
# Different output types go to different subdirectories
|
|
19
|
+
subdirs = {"html": "html", "notebook": "executed"}
|
|
20
|
+
p = os.path.join(".calkit", "notebooks", subdirs[to], nb_dir, fname_out)
|
|
21
|
+
if as_posix:
|
|
22
|
+
p = PurePosixPath(p).as_posix()
|
|
23
|
+
return p
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_cleaned_notebook_path(path: str, as_posix: bool = True) -> str:
|
|
27
|
+
"""Return the path of a cleaned notebook."""
|
|
28
|
+
p = os.path.join(".calkit", "notebooks", "cleaned", path)
|
|
29
|
+
if as_posix:
|
|
30
|
+
p = PurePosixPath(p).as_posix()
|
|
31
|
+
return p
|