calkit-python 0.21.3__tar.gz → 0.21.4__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 (144) hide show
  1. {calkit_python-0.21.3 → calkit_python-0.21.4}/PKG-INFO +1 -1
  2. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/__init__.py +1 -1
  3. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/check.py +1 -1
  4. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/main.py +12 -3
  5. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/new.py +11 -0
  6. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/conda.py +58 -46
  7. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_conda.py +81 -1
  8. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/environments.md +7 -0
  9. {calkit_python-0.21.3 → calkit_python-0.21.4}/.github/FUNDING.yml +0 -0
  10. {calkit_python-0.21.3 → calkit_python-0.21.4}/.github/workflows/docs.yml +0 -0
  11. {calkit_python-0.21.3 → calkit_python-0.21.4}/.github/workflows/format.yml +0 -0
  12. {calkit_python-0.21.3 → calkit_python-0.21.4}/.github/workflows/publish-test.yml +0 -0
  13. {calkit_python-0.21.3 → calkit_python-0.21.4}/.github/workflows/publish.yml +0 -0
  14. {calkit_python-0.21.3 → calkit_python-0.21.4}/.github/workflows/test.yml +0 -0
  15. {calkit_python-0.21.3 → calkit_python-0.21.4}/.gitignore +0 -0
  16. {calkit_python-0.21.3 → calkit_python-0.21.4}/.pre-commit-config.yaml +0 -0
  17. {calkit_python-0.21.3 → calkit_python-0.21.4}/CONTRIBUTING.md +0 -0
  18. {calkit_python-0.21.3 → calkit_python-0.21.4}/LICENSE +0 -0
  19. {calkit_python-0.21.3 → calkit_python-0.21.4}/Makefile +0 -0
  20. {calkit_python-0.21.3 → calkit_python-0.21.4}/README.md +0 -0
  21. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/__main__.py +0 -0
  22. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/calc.py +0 -0
  23. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/__init__.py +0 -0
  24. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/check.py +0 -0
  25. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/cloud.py +0 -0
  26. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/config.py +0 -0
  27. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/core.py +0 -0
  28. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/import_.py +0 -0
  29. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/list.py +0 -0
  30. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/notebooks.py +0 -0
  31. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/office.py +0 -0
  32. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cli/update.py +0 -0
  33. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/cloud.py +0 -0
  34. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/config.py +0 -0
  35. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/core.py +0 -0
  36. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/datasets.py +0 -0
  37. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/docker.py +0 -0
  38. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/dvc.py +0 -0
  39. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/git.py +0 -0
  40. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/github.py +0 -0
  41. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/gui.py +0 -0
  42. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/jupyter.py +0 -0
  43. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/magics.py +0 -0
  44. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/models.py +0 -0
  45. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/office.py +0 -0
  46. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/ops.py +0 -0
  47. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/releases.py +0 -0
  48. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/server.py +0 -0
  49. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/__init__.py +0 -0
  50. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/core.py +0 -0
  51. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/__init__.py +0 -0
  52. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/article/paper.tex +0 -0
  53. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/core.py +0 -0
  54. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/jfm/jfm.bst +0 -0
  55. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/jfm/jfm.cls +0 -0
  56. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  57. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/jfm/paper.tex +0 -0
  58. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/templates/latex/jfm/upmath.sty +0 -0
  59. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/__init__.py +0 -0
  60. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/cli/__init__.py +0 -0
  61. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/cli/test_list.py +0 -0
  62. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/cli/test_main.py +0 -0
  63. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/cli/test_new.py +0 -0
  64. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_calc.py +0 -0
  65. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_check.py +0 -0
  66. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_core.py +0 -0
  67. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_dvc.py +0 -0
  68. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_jupyter.py +0 -0
  69. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_magics.py +0 -0
  70. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/tests/test_templates.py +0 -0
  71. {calkit_python-0.21.3 → calkit_python-0.21.4}/calkit/zenodo.py +0 -0
  72. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/CNAME +0 -0
  73. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/apps.md +0 -0
  74. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/calculations.md +0 -0
  75. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/calkit-yaml.md +0 -0
  76. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/cli-reference.md +0 -0
  77. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/cloud-integration.md +0 -0
  78. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/datasets.md +0 -0
  79. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/examples.md +0 -0
  80. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/help.md +0 -0
  81. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/img/c-to-the-k-white.svg +0 -0
  82. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/img/calkit-no-bg.png +0 -0
  83. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/img/connect-zenodo.png +0 -0
  84. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/index.md +0 -0
  85. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/installation.md +0 -0
  86. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/local-server.md +0 -0
  87. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/pipeline/index.md +0 -0
  88. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/pipeline/manual-steps.md +0 -0
  89. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/references.md +0 -0
  90. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/releases.md +0 -0
  91. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  92. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/conda-envs.md +0 -0
  93. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/existing-project.md +0 -0
  94. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/first-project.md +0 -0
  95. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  96. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  97. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  98. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  99. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  100. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  101. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  102. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  103. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  104. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  105. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  106. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  107. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  108. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  109. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/create-project.png +0 -0
  110. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  111. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/excel-chart.png +0 -0
  112. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/excel-data.png +0 -0
  113. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  114. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/needs-clone.png +0 -0
  115. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/new-stage.png +0 -0
  116. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  117. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  118. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/status-more-rows.png +0 -0
  119. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  120. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/untracked-data.png +0 -0
  121. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/updated-publication.png +0 -0
  122. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  123. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/office/workflow-page.png +0 -0
  124. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/clone.png +0 -0
  125. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/create-project.png +0 -0
  126. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  127. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  128. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  129. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/new-token.png +0 -0
  130. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/reclone.png +0 -0
  131. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  132. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/img/run-proc.png +0 -0
  133. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/index.md +0 -0
  134. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/latex-codespaces.md +0 -0
  135. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/matlab.md +0 -0
  136. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/notebook-pipeline.md +0 -0
  137. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/office.md +0 -0
  138. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/openfoam.md +0 -0
  139. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/tutorials/procedures.md +0 -0
  140. {calkit_python-0.21.3 → calkit_python-0.21.4}/docs/version-control.md +0 -0
  141. {calkit_python-0.21.3 → calkit_python-0.21.4}/mkdocs.yml +0 -0
  142. {calkit_python-0.21.3 → calkit_python-0.21.4}/pyproject.toml +0 -0
  143. {calkit_python-0.21.3 → calkit_python-0.21.4}/test/pipeline.ipynb +0 -0
  144. {calkit_python-0.21.3 → calkit_python-0.21.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.21.3
3
+ Version: 0.21.4
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.21.3"
1
+ __version__ = "0.21.4"
2
2
 
3
3
  from .core import *
4
4
  from . import git
@@ -180,7 +180,7 @@ def check_reproducibility(
180
180
  if log_func is None:
181
181
  log_func = print
182
182
  try:
183
- repo = git.Repo(wdir)
183
+ git.Repo(wdir)
184
184
  res["is_git_repo"] = True
185
185
  except InvalidGitRepositoryError:
186
186
  res["is_git_repo"] = False
@@ -905,7 +905,7 @@ def run_in_env(
905
905
  fpath=env["path"],
906
906
  platform=env.get("platform"),
907
907
  deps=env.get("deps", []),
908
- quiet=True,
908
+ quiet=not verbose,
909
909
  )
910
910
  shell_cmd = _to_shell_cmd(cmd)
911
911
  docker_cmd = [
@@ -949,9 +949,18 @@ def run_in_env(
949
949
  conda_env = calkit.ryaml.load(f)
950
950
  if not no_check:
951
951
  check_conda_env(
952
- env_fpath=env["path"], relaxed=relaxed_check, quiet=True
952
+ env_fpath=env["path"],
953
+ relaxed=relaxed_check,
954
+ quiet=not verbose,
953
955
  )
954
- cmd = ["conda", "run", "-n", conda_env["name"]] + cmd
956
+ # TODO: Prefix should only be in the env file or calkit.yaml, not both?
957
+ prefix = env.get("prefix")
958
+ conda_cmd = ["conda", "run"]
959
+ if prefix is not None:
960
+ conda_cmd += ["--prefix", os.path.abspath(prefix)]
961
+ else:
962
+ conda_cmd += ["-n", conda_env["name"]]
963
+ cmd = conda_cmd + cmd
955
964
  if verbose:
956
965
  typer.echo(f"Running command: {cmd}")
957
966
  try:
@@ -963,6 +963,9 @@ def new_conda_env(
963
963
  pip_packages: Annotated[
964
964
  list[str], typer.Option("--pip", help="Packages to install with pip.")
965
965
  ] = [],
966
+ prefix: Annotated[
967
+ str, typer.Option("--prefix", help="Prefix for environment location.")
968
+ ] = None,
966
969
  description: Annotated[
967
970
  str, typer.Option("--description", help="Description.")
968
971
  ] = None,
@@ -1003,6 +1006,12 @@ def new_conda_env(
1003
1006
  conda_env = dict(
1004
1007
  name=conda_name, channels=["conda-forge"], dependencies=packages
1005
1008
  )
1009
+ if prefix is not None:
1010
+ from calkit.cli.main import ignore
1011
+
1012
+ conda_env["prefix"] = prefix
1013
+ ignore(prefix, no_commit=True)
1014
+ repo.git.add(".gitignore")
1006
1015
  if pip_packages:
1007
1016
  conda_env["dependencies"].append(dict(pip=pip_packages))
1008
1017
  with open(path, "w") as f:
@@ -1010,6 +1019,8 @@ def new_conda_env(
1010
1019
  repo.git.add(path)
1011
1020
  typer.echo("Adding environment to calkit.yaml")
1012
1021
  env = dict(path=path, kind="conda")
1022
+ if prefix is not None:
1023
+ env["prefix"] = prefix
1013
1024
  if description is not None:
1014
1025
  env["description"] = description
1015
1026
  envs[name] = env
@@ -75,35 +75,58 @@ def check_env(
75
75
  log_func(f"Checking conda env defined in {env_fpath}")
76
76
  res = EnvCheckResult()
77
77
  # Use mamba here because it's faster and produces less output
78
+ info = json.loads(subprocess.check_output(["conda", "info", "--json"]))
79
+ root_prefix = info["root_prefix"]
80
+ envs_dir = os.path.join(root_prefix, "envs")
78
81
  envs = json.loads(
79
82
  subprocess.check_output(["mamba", "env", "list", "--json"]).decode()
80
83
  )["envs"]
81
- # Get existing env names, but skip the base environment
82
- # Note that this could fail for environments with non-default prefixes
84
+ # Get existing env names for those in the envs directory
83
85
  existing_env_names = [
84
- os.path.basename(env) for env in envs if "envs" in env
86
+ os.path.basename(env) for env in envs if env.startswith(envs_dir)
85
87
  ]
88
+ # Get a list of environments defined by prefix instead of name
89
+ env_prefixes = [e for e in envs if not e.startswith(root_prefix)]
86
90
  with open(env_fpath) as f:
87
91
  env_spec = ryaml.load(f)
88
92
  env_name = env_spec["name"]
89
- env_check_fpath = os.path.join(
90
- os.path.expanduser("~"),
91
- ".calkit",
92
- "conda-env-checks",
93
- env_name + ".yml",
94
- )
93
+ prefix = env_spec.get("prefix")
94
+ prefix_orig = prefix
95
+ if prefix is not None:
96
+ prefix = os.path.abspath(prefix)
97
+ env_check_fpath = os.path.join(prefix, "env-export.yml")
98
+ else:
99
+ env_check_fpath = os.path.join(
100
+ os.path.expanduser("~"),
101
+ ".calkit",
102
+ "conda-env-checks",
103
+ env_name + ".yml",
104
+ )
95
105
  env_check_dir = os.path.dirname(env_check_fpath)
96
106
  os.makedirs(env_check_dir, exist_ok=True)
107
+ # Create env export command, which will be used later
108
+ export_cmd = [
109
+ "conda", # Mamba output is slightly different
110
+ "env",
111
+ "export",
112
+ "--no-builds",
113
+ "--json",
114
+ ]
115
+ # Create with conda since newer mamba versions create a strange
116
+ # "Library" subdirectory, at least on Windows
117
+ create_cmd = ["conda", "env", "create", "-y", "-f", env_fpath]
118
+ if prefix is not None:
119
+ export_cmd += ["--prefix", prefix]
120
+ create_cmd += ["--prefix", prefix]
121
+ else:
122
+ export_cmd += ["-n", env_name]
97
123
  # Check if env even exists
98
- if env_name not in existing_env_names:
124
+ # If env has a prefix defined, it will be identified by that
125
+ if env_name not in existing_env_names and prefix not in env_prefixes:
99
126
  log_func(f"Environment {env_name} doesn't exist; creating")
100
127
  res.env_exists = False
101
128
  # Environment doesn't exist, so create it
102
- # Create with conda since newer mamba versions create a strange
103
- # "Library" subdirectory, at least on Windows
104
- subprocess.check_call(
105
- ["conda", "env", "create", "-y", "-f", env_fpath]
106
- )
129
+ subprocess.check_call(create_cmd)
107
130
  env_needs_rebuild = False
108
131
  env_needs_export = True
109
132
  else:
@@ -111,6 +134,7 @@ def check_env(
111
134
  env_needs_export = False
112
135
  # Environment does exist, so check it
113
136
  if os.path.isfile(env_check_fpath):
137
+ log_func(f"Found env check file at {env_check_fpath}")
114
138
  # Open up the env check result file
115
139
  with open(env_check_fpath) as f:
116
140
  env_check = ryaml.load(f)
@@ -122,24 +146,16 @@ def check_env(
122
146
  current_mtime = os.path.getmtime(
123
147
  os.path.normpath(env_check["prefix"])
124
148
  )
149
+ log_func(f"Env check mtime: {existing_mtime}")
150
+ log_func(f"Env dir mtime: {current_mtime}")
125
151
  env_needs_export = existing_mtime != current_mtime
126
152
  else:
153
+ log_func(f"Env check file at {env_check_fpath} does not exist")
127
154
  env_needs_export = True
128
155
  if env_needs_export:
129
- res.env_needs_export = True
130
156
  log_func(f"Exporting existing env to {env_check_fpath}")
131
157
  env_check = json.loads(
132
- subprocess.check_output(
133
- [
134
- "conda", # Mamba output is slightly different
135
- "env",
136
- "export",
137
- "-n",
138
- env_name,
139
- "--no-builds",
140
- "--json",
141
- ]
142
- ).decode()
158
+ subprocess.check_output(export_cmd).decode()
143
159
  )
144
160
  env_check["mtime"] = os.path.getmtime(
145
161
  os.path.normpath(env_check["prefix"])
@@ -188,29 +204,16 @@ def check_env(
188
204
  if env_needs_rebuild:
189
205
  res.env_needs_rebuild = True
190
206
  log_func(f"Rebuilding {env_name} since it does not match spec")
191
- subprocess.check_call(
192
- ["conda", "env", "create", "-y", "-f", env_fpath]
193
- )
207
+ subprocess.check_call(create_cmd)
194
208
  env_needs_export = True
195
209
  else:
196
210
  log_func(f"Environment {env_name} matches spec")
197
211
  res.env_needs_rebuild = False
198
212
  # If the env was rebuilt, export the env check
213
+ res.env_needs_export = env_needs_export
199
214
  if env_needs_export:
200
215
  log_func(f"Exporting existing env to {env_check_fpath}")
201
- env_check = json.loads(
202
- subprocess.check_output(
203
- [
204
- "conda", # Mamba output is slightly different
205
- "env",
206
- "export",
207
- "-n",
208
- env_name,
209
- "--no-builds",
210
- "--json",
211
- ]
212
- ).decode()
213
- )
216
+ env_check = json.loads(subprocess.check_output(export_cmd).decode())
214
217
  env_check["mtime"] = os.path.getmtime(
215
218
  os.path.normpath(env_check["prefix"])
216
219
  )
@@ -225,8 +228,17 @@ def check_env(
225
228
  or not os.path.isfile(output_fpath)
226
229
  ):
227
230
  log_func(f"Exporting lock file to {output_fpath}")
231
+ env_export = json.loads(
232
+ subprocess.check_output(
233
+ [a for a in export_cmd if a != "--no-builds"]
234
+ ).decode()
235
+ )
236
+ # Remove prefix from env export since it will be an absolute path
237
+ _ = env_export.pop("prefix")
238
+ # Remove name if prefix is set, since that will be the prefix
239
+ if prefix is not None:
240
+ _ = env_export.pop("name")
241
+ env_export["prefix"] = prefix_orig
228
242
  with open(output_fpath, "w") as f:
229
- _ = env_check.pop("mtime")
230
- _ = env_check.pop("prefix")
231
- ryaml.dump(env_check, f)
243
+ ryaml.dump(env_export, f)
232
244
  return res
@@ -80,10 +80,11 @@ def test_check_env(tmp_dir, conda_env_name):
80
80
  )
81
81
  res = check_env()
82
82
  assert res.env_exists
83
- assert not res.env_needs_export
83
+ assert res.env_needs_export
84
84
  assert res.env_needs_rebuild
85
85
  res = check_env()
86
86
  assert not res.env_needs_rebuild
87
+ assert not res.env_needs_export
87
88
  # Check relaxed mode, where we allow dependencies to be in either the pip
88
89
  # or conda section
89
90
  subprocess.check_call(
@@ -163,3 +164,82 @@ def test_check_env(tmp_dir, conda_env_name):
163
164
  res = check_env()
164
165
  assert not res.env_needs_export
165
166
  assert not res.env_needs_rebuild
167
+
168
+
169
+ @pytest.fixture
170
+ def conda_env_prefix():
171
+ prefix = ".conda-envs/my-conda-env"
172
+ yield prefix
173
+ subprocess.check_call(["conda", "env", "remove", "-y", "--prefix", prefix])
174
+
175
+
176
+ def test_check_prefix_env(tmp_dir, conda_env_prefix):
177
+ subprocess.check_call(["calkit", "init"])
178
+ # Test we can use a local prefix
179
+ subprocess.check_call(
180
+ [
181
+ "calkit",
182
+ "new",
183
+ "conda-env",
184
+ "-n",
185
+ "my-conda-env",
186
+ "python=3.12",
187
+ "--prefix",
188
+ conda_env_prefix,
189
+ ]
190
+ )
191
+ res = check_env()
192
+ assert not res.env_exists
193
+ assert res.env_needs_export
194
+ assert os.path.isfile(os.path.join(conda_env_prefix, "env-export.yml"))
195
+ res = check_env()
196
+ assert res.env_exists
197
+ # Env will need to be exported a second time since we save it inside the
198
+ # prefix folder, so the mtime will be slightly after creation of the
199
+ # initial environment
200
+ assert res.env_needs_export
201
+ assert not res.env_needs_rebuild
202
+ # Now the env should be exported okay
203
+ res = check_env()
204
+ assert res.env_exists
205
+ assert not res.env_needs_export
206
+ assert not res.env_needs_rebuild
207
+ subprocess.check_call(
208
+ ["calkit", "xenv", "-n", "my-conda-env", "python", "--version"]
209
+ )
210
+ # Test that we can add a new dependency
211
+ with open("environment.yml") as f:
212
+ env = calkit.ryaml.load(f)
213
+ env["dependencies"].append("requests")
214
+ with open("environment.yml", "w") as f:
215
+ calkit.ryaml.dump(env, f)
216
+ res = check_env()
217
+ assert res.env_exists
218
+ assert res.env_needs_export
219
+ assert res.env_needs_rebuild
220
+ subprocess.check_call(
221
+ [
222
+ "calkit",
223
+ "xenv",
224
+ "-n",
225
+ "my-conda-env",
226
+ "python",
227
+ "-c",
228
+ "import requests",
229
+ ]
230
+ )
231
+ # Test that we can specify --wdir
232
+ os.makedirs("subdir")
233
+ subprocess.check_call(
234
+ [
235
+ "calkit",
236
+ "xenv",
237
+ "--wdir",
238
+ "subdir",
239
+ "-n",
240
+ "my-conda-env",
241
+ "python",
242
+ "-c",
243
+ "import requests",
244
+ ]
245
+ )
@@ -236,6 +236,13 @@ The new Conda environment spec will be written to `environment.yml`
236
236
  by default,
237
237
  which can be controlled with the `--path` option.
238
238
 
239
+ A prefix for the environment can be specified to keep all packages under the
240
+ project directory, e.g., by adding `--prefix .conda-envs/my-conda-env`.
241
+ If this option is omitted, the environment will become part of Conda's
242
+ system-wide collection of environments with a name like
243
+ `{project_name}-{env_name}`,
244
+ where the project name is added to avoid conflicts.
245
+
239
246
  Similar to other environment types,
240
247
  any time a command is executed with `calkit xenv`,
241
248
  this environment will be checked and created or updated as necessary.
File without changes
File without changes
File without changes
File without changes