calkit-python 0.16.2__tar.gz → 0.16.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. {calkit_python-0.16.2 → calkit_python-0.16.3}/.gitignore +1 -0
  2. {calkit_python-0.16.2 → calkit_python-0.16.3}/PKG-INFO +2 -1
  3. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/__init__.py +1 -1
  4. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/check.py +30 -4
  5. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/main.py +8 -6
  6. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/conda.py +12 -3
  7. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/cli/test_main.py +27 -0
  8. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/environments.md +15 -0
  9. {calkit_python-0.16.2 → calkit_python-0.16.3}/pyproject.toml +1 -0
  10. calkit_python-0.16.3/uv.lock +3735 -0
  11. {calkit_python-0.16.2 → calkit_python-0.16.3}/.github/FUNDING.yml +0 -0
  12. {calkit_python-0.16.2 → calkit_python-0.16.3}/.github/workflows/docs.yml +0 -0
  13. {calkit_python-0.16.2 → calkit_python-0.16.3}/.github/workflows/publish-test.yml +0 -0
  14. {calkit_python-0.16.2 → calkit_python-0.16.3}/.github/workflows/publish.yml +0 -0
  15. {calkit_python-0.16.2 → calkit_python-0.16.3}/LICENSE +0 -0
  16. {calkit_python-0.16.2 → calkit_python-0.16.3}/README.md +0 -0
  17. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/calc.py +0 -0
  18. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/check.py +0 -0
  19. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/__init__.py +0 -0
  20. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/config.py +0 -0
  21. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/core.py +0 -0
  22. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/import_.py +0 -0
  23. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/list.py +0 -0
  24. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/new.py +0 -0
  25. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/notebooks.py +0 -0
  26. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/office.py +0 -0
  27. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cli/update.py +0 -0
  28. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/cloud.py +0 -0
  29. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/config.py +0 -0
  30. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/core.py +0 -0
  31. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/datasets.py +0 -0
  32. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/docker.py +0 -0
  33. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/dvc.py +0 -0
  34. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/git.py +0 -0
  35. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/gui.py +0 -0
  36. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/jupyter.py +0 -0
  37. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/magics.py +0 -0
  38. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/models.py +0 -0
  39. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/office.py +0 -0
  40. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/ops.py +0 -0
  41. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/server.py +0 -0
  42. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/__init__.py +0 -0
  43. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/core.py +0 -0
  44. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/__init__.py +0 -0
  45. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/article/paper.tex +0 -0
  46. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/core.py +0 -0
  47. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/jfm/jfm.bst +0 -0
  48. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/jfm/jfm.cls +0 -0
  49. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  50. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/jfm/paper.tex +0 -0
  51. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/templates/latex/jfm/upmath.sty +0 -0
  52. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/__init__.py +0 -0
  53. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/cli/__init__.py +0 -0
  54. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/cli/test_list.py +0 -0
  55. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/cli/test_new.py +0 -0
  56. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_calc.py +0 -0
  57. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_check.py +0 -0
  58. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_conda.py +0 -0
  59. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_core.py +0 -0
  60. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_dvc.py +0 -0
  61. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_jupyter.py +0 -0
  62. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_magics.py +0 -0
  63. {calkit_python-0.16.2 → calkit_python-0.16.3}/calkit/tests/test_templates.py +0 -0
  64. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/CNAME +0 -0
  65. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/apps.md +0 -0
  66. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/calculations.md +0 -0
  67. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/calkit-yaml.md +0 -0
  68. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/cli-reference.md +0 -0
  69. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/cloud-integration.md +0 -0
  70. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/examples.md +0 -0
  71. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/help.md +0 -0
  72. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/img/c-to-the-k-white.svg +0 -0
  73. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/img/calkit-no-bg.png +0 -0
  74. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/index.md +0 -0
  75. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/installation.md +0 -0
  76. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/pipeline/index.md +0 -0
  77. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/pipeline/manual-steps.md +0 -0
  78. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/references.md +0 -0
  79. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  80. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/conda-envs.md +0 -0
  81. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/first-project.md +0 -0
  82. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  83. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  84. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  85. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  86. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  87. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  88. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  89. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  90. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  91. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  92. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  93. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  94. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  95. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  96. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/create-project.png +0 -0
  97. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  98. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/excel-chart.png +0 -0
  99. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/excel-data.png +0 -0
  100. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  101. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/needs-clone.png +0 -0
  102. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/new-stage.png +0 -0
  103. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  104. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  105. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/status-more-rows.png +0 -0
  106. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  107. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/untracked-data.png +0 -0
  108. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/updated-publication.png +0 -0
  109. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  110. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/office/workflow-page.png +0 -0
  111. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/clone.png +0 -0
  112. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/create-project.png +0 -0
  113. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  114. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  115. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  116. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/new-token.png +0 -0
  117. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/reclone.png +0 -0
  118. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  119. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/img/run-proc.png +0 -0
  120. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/latex-codespaces.md +0 -0
  121. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/matlab.md +0 -0
  122. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/notebook-pipeline.md +0 -0
  123. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/office.md +0 -0
  124. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/openfoam.md +0 -0
  125. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/tutorials/procedures.md +0 -0
  126. {calkit_python-0.16.2 → calkit_python-0.16.3}/docs/version-control.md +0 -0
  127. {calkit_python-0.16.2 → calkit_python-0.16.3}/mkdocs.yml +0 -0
  128. {calkit_python-0.16.2 → calkit_python-0.16.3}/test/pipeline.ipynb +0 -0
@@ -4,3 +4,4 @@ dev.ipynb
4
4
  .vscode
5
5
  site
6
6
  .env
7
+ .venv
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.16.2
3
+ Version: 0.16.3
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -12,6 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Requires-Python: >=3.8
14
14
  Requires-Dist: arithmeval
15
+ Requires-Dist: checksumdir
15
16
  Requires-Dist: docx2pdf
16
17
  Requires-Dist: dvc
17
18
  Requires-Dist: eval-type-backport; python_version < '3.10'
@@ -1,4 +1,4 @@
1
- __version__ = "0.16.2"
1
+ __version__ = "0.16.3"
2
2
 
3
3
  from .core import *
4
4
  from . import git
@@ -9,6 +9,7 @@ import os
9
9
  import subprocess
10
10
  from typing import Annotated
11
11
 
12
+ import checksumdir
12
13
  import typer
13
14
 
14
15
  import calkit
@@ -65,6 +66,14 @@ def check_docker_env(
65
66
  platform: Annotated[
66
67
  str, typer.Option("--platform", help="Which platform(s) to build for.")
67
68
  ] = None,
69
+ deps: Annotated[
70
+ list[str],
71
+ typer.Option(
72
+ "--dep",
73
+ "-d",
74
+ help="Declare an explicit dependency for this Docker image.",
75
+ ),
76
+ ] = [],
68
77
  quiet: Annotated[
69
78
  bool, typer.Option("--quiet", "-q", help="Be quiet.")
70
79
  ] = False,
@@ -81,6 +90,14 @@ def check_docker_env(
81
90
  _ = out[0].pop("DockerVersion")
82
91
  return out
83
92
 
93
+ def get_md5(path: str, exclude_files: list[str] | None = None) -> str:
94
+ if os.path.isdir(path):
95
+ return checksumdir.dirhash(dep, excluded_files=exclude_files)
96
+ else:
97
+ with open(path) as f:
98
+ content = f.read()
99
+ return hashlib.md5(content.encode()).hexdigest()
100
+
84
101
  outfile = open(os.devnull, "w") if quiet else None
85
102
  typer.echo(f"Checking for existing image with tag {tag}", file=outfile)
86
103
  # First call Docker inspect
@@ -90,10 +107,12 @@ def check_docker_env(
90
107
  typer.echo(f"No image with tag {tag} found locally", file=outfile)
91
108
  inspect = []
92
109
  typer.echo(f"Reading Dockerfile from {fpath}", file=outfile)
93
- with open(fpath) as f:
94
- dockerfile = f.read()
95
- dockerfile_md5 = hashlib.md5(dockerfile.encode()).hexdigest()
110
+ dockerfile_md5 = get_md5(fpath)
96
111
  lock_fpath = fpath + "-lock.json"
112
+ # Compute MD5s of any dependencies
113
+ deps_md5s = {}
114
+ for dep in deps:
115
+ deps_md5s[dep] = get_md5(dep, exclude_files=lock_fpath)
97
116
  rebuild = True
98
117
  if os.path.isfile(lock_fpath):
99
118
  typer.echo(f"Reading lock file: {lock_fpath}", file=outfile)
@@ -109,6 +128,12 @@ def check_docker_env(
109
128
  rebuild = inspect[0]["RootFS"]["Layers"] != lock[0]["RootFS"][
110
129
  "Layers"
111
130
  ] or dockerfile_md5 != lock[0].get("DockerfileMD5")
131
+ if not rebuild:
132
+ for dep, md5 in deps_md5s.items():
133
+ if md5 != lock[0].get("DepsMD5s", {}).get(dep):
134
+ typer.echo(f"Found modified dependency: {dep}")
135
+ rebuild = True
136
+ break
112
137
  if rebuild:
113
138
  wdir, fname = os.path.split(fpath)
114
139
  if not wdir:
@@ -117,10 +142,11 @@ def check_docker_env(
117
142
  if platform is not None:
118
143
  cmd += ["--platform", platform]
119
144
  cmd.append(".")
120
- subprocess.check_call(cmd, cwd=wdir)
145
+ subprocess.check_output(cmd, cwd=wdir)
121
146
  # Write the lock file
122
147
  inspect = get_docker_inspect()
123
148
  inspect[0]["DockerfileMD5"] = dockerfile_md5
149
+ inspect[0]["DepsMD5s"] = deps_md5s
124
150
  with open(lock_fpath, "w") as f:
125
151
  json.dump(inspect, f, indent=4)
126
152
 
@@ -57,7 +57,8 @@ def _to_shell_cmd(cmd: list[str]) -> str:
57
57
  """
58
58
  quoted_cmd = []
59
59
  for part in cmd:
60
- if " " in part or '"' in part or "'" in part:
60
+ # Find quotes within quotes and escape them
61
+ if " " in part or '"' in part[1:-1] or "'" in part[1:-1]:
61
62
  part = part.replace('"', r"\"")
62
63
  quoted_cmd.append(f'"{part}"')
63
64
  else:
@@ -708,12 +709,11 @@ def run_in_env(
708
709
  if env_name not in envs:
709
710
  raise_error(f"Environment '{env_name}' does not exist")
710
711
  env = envs[env_name]
711
- if wdir is not None:
712
- cwd = os.path.abspath(wdir)
713
- else:
714
- cwd = os.getcwd()
715
712
  image_name = env.get("image", env_name)
716
713
  docker_wdir = env.get("wdir", "/work")
714
+ docker_wdir_mount = docker_wdir
715
+ if wdir is not None:
716
+ docker_wdir = os.path.join(docker_wdir, wdir)
717
717
  shell = env.get("shell", "sh")
718
718
  platform = env.get("platform")
719
719
  if env["kind"] == "docker":
@@ -724,6 +724,7 @@ def run_in_env(
724
724
  tag=env["image"],
725
725
  fpath=env["path"],
726
726
  platform=env.get("platform"),
727
+ deps=env.get("deps", []),
727
728
  quiet=True,
728
729
  )
729
730
  shell_cmd = _to_shell_cmd(cmd)
@@ -733,13 +734,14 @@ def run_in_env(
733
734
  ]
734
735
  if platform:
735
736
  docker_cmd += ["--platform", platform]
737
+ docker_cmd += env.get("args", [])
736
738
  docker_cmd += [
737
739
  "-it" if sys.stdin.isatty() else "-i",
738
740
  "--rm",
739
741
  "-w",
740
742
  docker_wdir,
741
743
  "-v",
742
- f"{cwd}:{docker_wdir}",
744
+ f"{os.getcwd()}:{docker_wdir_mount}",
743
745
  image_name,
744
746
  shell,
745
747
  "-c",
@@ -4,9 +4,10 @@ import json
4
4
  import os
5
5
  import re
6
6
  import subprocess
7
+ import warnings
7
8
 
8
9
  from packaging.specifiers import SpecifierSet
9
- from packaging.version import Version
10
+ from packaging.version import InvalidVersion, Version
10
11
  from pydantic import BaseModel
11
12
 
12
13
  import calkit
@@ -20,14 +21,22 @@ def _check_single(req: str, actual: str, conda: bool = False) -> bool:
20
21
  if conda and req_spec.startswith("="):
21
22
  req_spec = "=" + req_spec
22
23
  if not req_spec.endswith(".*"):
23
- req_spec += ".*"
24
+ if len(req_spec.split(".")) < 3:
25
+ req_spec += ".*"
24
26
  actual_name, actual_vers = re.split("[=<>]+", actual, maxsplit=1)
25
27
  if actual_name != req_name:
26
28
  return False
27
29
  actual_spec = actual.removeprefix(actual_name)
28
30
  if conda and actual_spec.startswith("="):
29
31
  actual_spec = "=" + actual_spec
30
- version = Version(actual_vers)
32
+ try:
33
+ version = Version(actual_vers)
34
+ except InvalidVersion:
35
+ warnings.warn(
36
+ f"Cannot properly check {actual_name} version {actual_vers}"
37
+ )
38
+ # TODO: Check exact version only
39
+ return True
31
40
  spec = SpecifierSet(req_spec)
32
41
  return spec.contains(version)
33
42
 
@@ -85,6 +85,26 @@ def test_run_in_env(tmp_dir):
85
85
  ck_info = calkit.load_calkit_info()
86
86
  env = ck_info["environments"]["py3.10"]
87
87
  assert env.get("path") is None
88
+ # Test that we can run a command that changes directory first
89
+ os.makedirs("my-new-dir/another", exist_ok=True)
90
+ out = (
91
+ subprocess.check_output(
92
+ "calkit xenv -n py3.10 --wdir my-new-dir -- ls",
93
+ shell=True,
94
+ )
95
+ .decode()
96
+ .strip()
97
+ )
98
+ assert out == "another"
99
+ out = (
100
+ subprocess.check_output(
101
+ "calkit xenv -n py3.10 --wdir my-new-dir -- ls ..",
102
+ shell=True,
103
+ )
104
+ .decode()
105
+ .strip()
106
+ )
107
+ assert "my-new-dir" in out.split("\n")
88
108
 
89
109
 
90
110
  def test_run_in_venv(tmp_dir):
@@ -206,6 +226,13 @@ def test_to_shell_cmd():
206
226
  shell_cmd = _to_shell_cmd(cmd)
207
227
  assert shell_cmd == 'python -c "print(\\"hello world\\")"'
208
228
  subprocess.check_call(shell_cmd, shell=True)
229
+ cmd = [
230
+ "sh",
231
+ "-c",
232
+ "cd dir1 && ls",
233
+ ]
234
+ good_shell_cmd = 'sh -c "cd dir1 && ls"'
235
+ assert _to_shell_cmd(cmd) == good_shell_cmd
209
236
 
210
237
 
211
238
  def test_add(tmp_dir):
@@ -154,6 +154,21 @@ and another call to `calkit xenv -n foam2` will kick off a rebuild
154
154
  automatically,
155
155
  since the lock file will no longer match the Dockerfile.
156
156
 
157
+ If you're copying local files into the Docker image,
158
+ you can declare these
159
+ dependencies in the environment definition so the content of those will be
160
+ tracked as well:
161
+
162
+ ```yaml
163
+ # In calkit.yaml
164
+ environments:
165
+ foam2:
166
+ kind: docker
167
+ image: foam2
168
+ deps:
169
+ - src/mySolver.C
170
+ ```
171
+
157
172
  This highlights Calkit's declarative design philosophy.
158
173
  Simply declare the environment and use it in a pipeline stage
159
174
  and Calkit will ensure it is built and up to date.
@@ -13,6 +13,7 @@ classifiers = [
13
13
  ]
14
14
  dependencies = [
15
15
  "arithmeval",
16
+ "checksumdir",
16
17
  "docx2pdf",
17
18
  "dvc",
18
19
  "eval-type-backport; python_version < '3.10'",