calkit-python 0.11.1__tar.gz → 0.11.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 (66) hide show
  1. {calkit_python-0.11.1 → calkit_python-0.11.3}/PKG-INFO +1 -1
  2. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/__init__.py +1 -1
  3. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/main.py +14 -7
  4. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/new.py +65 -1
  5. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cloud.py +5 -2
  6. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/config.py +1 -0
  7. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/core.py +8 -3
  8. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/models.py +50 -3
  9. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/cli/test_main.py +17 -0
  10. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/cli/test_new.py +43 -0
  11. {calkit_python-0.11.1 → calkit_python-0.11.3}/.github/FUNDING.yml +0 -0
  12. {calkit_python-0.11.1 → calkit_python-0.11.3}/.github/workflows/publish-test.yml +0 -0
  13. {calkit_python-0.11.1 → calkit_python-0.11.3}/.github/workflows/publish.yml +0 -0
  14. {calkit_python-0.11.1 → calkit_python-0.11.3}/.gitignore +0 -0
  15. {calkit_python-0.11.1 → calkit_python-0.11.3}/LICENSE +0 -0
  16. {calkit_python-0.11.1 → calkit_python-0.11.3}/README.md +0 -0
  17. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/calc.py +0 -0
  18. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/check.py +0 -0
  19. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/__init__.py +0 -0
  20. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/check.py +0 -0
  21. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/config.py +0 -0
  22. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/core.py +0 -0
  23. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/import_.py +0 -0
  24. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/list.py +0 -0
  25. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/notebooks.py +0 -0
  26. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/office.py +0 -0
  27. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/cli/update.py +0 -0
  28. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/conda.py +0 -0
  29. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/data.py +0 -0
  30. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/docker.py +0 -0
  31. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/dvc.py +0 -0
  32. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/git.py +0 -0
  33. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/gui.py +0 -0
  34. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/jupyter.py +0 -0
  35. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/magics.py +0 -0
  36. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/office.py +0 -0
  37. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/server.py +0 -0
  38. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/__init__.py +0 -0
  39. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/core.py +0 -0
  40. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/__init__.py +0 -0
  41. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/article/paper.tex +0 -0
  42. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/core.py +0 -0
  43. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/jfm/jfm.bst +0 -0
  44. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/jfm/jfm.cls +0 -0
  45. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  46. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/jfm/paper.tex +0 -0
  47. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/templates/latex/jfm/upmath.sty +0 -0
  48. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/__init__.py +0 -0
  49. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/cli/__init__.py +0 -0
  50. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/cli/test_list.py +0 -0
  51. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_calc.py +0 -0
  52. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_check.py +0 -0
  53. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_conda.py +0 -0
  54. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_core.py +0 -0
  55. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_dvc.py +0 -0
  56. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_jupyter.py +0 -0
  57. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_magics.py +0 -0
  58. {calkit_python-0.11.1 → calkit_python-0.11.3}/calkit/tests/test_templates.py +0 -0
  59. {calkit_python-0.11.1 → calkit_python-0.11.3}/docs/img/calkit-no-bg.png +0 -0
  60. {calkit_python-0.11.1 → calkit_python-0.11.3}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  61. {calkit_python-0.11.1 → calkit_python-0.11.3}/docs/tutorials/conda-envs.md +0 -0
  62. {calkit_python-0.11.1 → calkit_python-0.11.3}/docs/tutorials/img/run-proc.png +0 -0
  63. {calkit_python-0.11.1 → calkit_python-0.11.3}/docs/tutorials/notebook-pipeline.md +0 -0
  64. {calkit_python-0.11.1 → calkit_python-0.11.3}/docs/tutorials/procedures.md +0 -0
  65. {calkit_python-0.11.1 → calkit_python-0.11.3}/pyproject.toml +0 -0
  66. {calkit_python-0.11.1 → calkit_python-0.11.3}/test/pipeline.ipynb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.11.1
3
+ Version: 0.11.3
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://github.com/calkit/calkit
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -1,4 +1,4 @@
1
- __version__ = "0.11.1"
1
+ __version__ = "0.11.3"
2
2
 
3
3
  from .core import *
4
4
  from . import git
@@ -7,6 +7,7 @@ import functools
7
7
  import hashlib
8
8
  import json
9
9
  import os
10
+ import platform as _platform
10
11
  import subprocess
11
12
  import sys
12
13
  import time
@@ -93,9 +94,9 @@ def clone(
93
94
  if location is not None:
94
95
  cmd.append(location)
95
96
  try:
96
- subprocess.call(cmd)
97
- except Exception as e:
98
- raise_error(str(e))
97
+ subprocess.check_call(cmd)
98
+ except subprocess.CalledProcessError:
99
+ raise_error("Failed to clone with Git")
99
100
  if location is None:
100
101
  location = url.split("/")[-1].removesuffix(".git")
101
102
  typer.echo(f"Moving into repo dir: {location}")
@@ -109,7 +110,10 @@ def clone(
109
110
  calkit.dvc.set_remote_auth(remote_name=name)
110
111
  # DVC pull
111
112
  if not no_dvc_pull:
112
- subprocess.call(["dvc", "pull"])
113
+ try:
114
+ subprocess.check_call(["dvc", "pull"])
115
+ except subprocess.CalledProcessError:
116
+ raise_error("Failed to pull from DVC remote(s)")
113
117
 
114
118
 
115
119
  @app.command(name="status")
@@ -654,7 +658,6 @@ def run_in_env(
654
658
  except subprocess.CalledProcessError:
655
659
  raise_error(f"Failed to run in {env['kind']} environment")
656
660
  elif env["kind"] == "uv-venv":
657
- # TODO: This doesn't work on Windows
658
661
  if "prefix" not in env:
659
662
  raise_error("uv-venv environments require a prefix")
660
663
  if "path" not in env:
@@ -673,8 +676,12 @@ def run_in_env(
673
676
  raise_error(f"Failed to create uv-venv at {prefix}")
674
677
  fname, ext = os.path.splitext(path)
675
678
  lock_fpath = fname + "-lock" + ext
679
+ if _platform.system() == "Windows":
680
+ activate_cmd = f"{prefix}\\Scripts\\activate"
681
+ else:
682
+ activate_cmd = f". {prefix}/bin/activate"
676
683
  check_cmd = (
677
- f". {prefix}/bin/activate "
684
+ f"{activate_cmd} "
678
685
  f"&& uv pip install -q -r {path} "
679
686
  f"&& uv pip freeze > {lock_fpath} "
680
687
  "&& deactivate"
@@ -686,7 +693,7 @@ def run_in_env(
686
693
  except subprocess.CalledProcessError:
687
694
  raise_error("Failed to check uv-venv")
688
695
  # Now run the command
689
- cmd = f". {prefix}/bin/activate && {shell_cmd} && deactivate"
696
+ cmd = f"{activate_cmd} && {shell_cmd} && deactivate"
690
697
  if verbose:
691
698
  typer.echo(f"Running command: {cmd}")
692
699
  try:
@@ -827,7 +827,7 @@ def new_publication(
827
827
  else:
828
828
  template_type = None
829
829
  # Check all of our inputs
830
- if template_type not in ["latex"]:
830
+ if template_type is not None and template_type not in ["latex"]:
831
831
  raise_error(f"Unknown template type '{template_type}'")
832
832
  if env_name is not None and template_type != "latex":
833
833
  raise_error("Environments can only be created for latex templates")
@@ -1035,3 +1035,67 @@ def new_conda_env(
1035
1035
  repo.git.add("dvc.yaml")
1036
1036
  if not no_commit and repo.git.diff("--staged"):
1037
1037
  repo.git.commit(["-m", f"Add Conda environment {name}"])
1038
+
1039
+
1040
+ @new_app.command("uv-venv")
1041
+ def new_uv_venv(
1042
+ packages: Annotated[
1043
+ list[str],
1044
+ typer.Argument(help="Packages to include in the environment."),
1045
+ ],
1046
+ name: Annotated[
1047
+ str, typer.Option("--name", "-n", help="Environment name.")
1048
+ ],
1049
+ path: Annotated[
1050
+ str, typer.Option("--path", help="Path for requirements file.")
1051
+ ] = "requirements.txt",
1052
+ prefix: Annotated[
1053
+ str, typer.Option("--prefix", help="Prefix for environment location.")
1054
+ ] = ".venv",
1055
+ description: Annotated[
1056
+ str, typer.Option("--description", help="Description.")
1057
+ ] = None,
1058
+ overwrite: Annotated[
1059
+ bool,
1060
+ typer.Option(
1061
+ "--overwrite",
1062
+ "-f",
1063
+ help="Overwrite any existing environment with this name.",
1064
+ ),
1065
+ ] = False,
1066
+ no_commit: Annotated[
1067
+ bool, typer.Option("--no-commit", help="Do not commit changes.")
1068
+ ] = False,
1069
+ ):
1070
+ """Create a new uv virtual environment."""
1071
+ if os.path.isfile(path) and not overwrite:
1072
+ raise_error("Output path already exists (use -f to overwrite)")
1073
+ repo = git.Repo()
1074
+ # Add environment to Calkit info
1075
+ ck_info = calkit.load_calkit_info()
1076
+ # If environments is a list instead of a dict, reformulate it
1077
+ envs = ck_info.get("environments", {})
1078
+ if isinstance(envs, list):
1079
+ typer.echo("Converting environments from list to dict")
1080
+ envs = {env.pop("name"): env for env in envs}
1081
+ if name in envs and not overwrite:
1082
+ raise_error(
1083
+ f"Environment with name {name} already exists "
1084
+ "(use -f to overwrite)"
1085
+ )
1086
+ packages_txt = "\n".join(packages)
1087
+ # Write environment to path
1088
+ with open(path, "w") as f:
1089
+ f.write(packages_txt)
1090
+ repo.git.add(path)
1091
+ typer.echo("Adding environment to calkit.yaml")
1092
+ env = dict(path=path, kind="uv-venv", prefix=prefix)
1093
+ if description is not None:
1094
+ env["description"] = description
1095
+ envs[name] = env
1096
+ ck_info["environments"] = envs
1097
+ with open("calkit.yaml", "w") as f:
1098
+ ryaml.dump(ck_info, f)
1099
+ repo.git.add("calkit.yaml")
1100
+ if not no_commit and repo.git.diff("--staged"):
1101
+ repo.git.commit(["-m", f"Add uv venv {name}"])
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
5
+ import warnings
6
6
  from functools import partial
7
7
  from typing import Literal
8
8
 
@@ -35,6 +35,7 @@ def get_token() -> str:
35
35
  _tokens[get_base_url()] = token
36
36
  # TODO: Check for expiration
37
37
  if token is None:
38
+ warnings.warn("No token found; attempting email+password auth")
38
39
  return auth()
39
40
  return token
40
41
 
@@ -50,10 +51,12 @@ def get_headers(headers: dict | None = None) -> dict:
50
51
  def auth() -> str:
51
52
  """Authenticate with the server and save a token."""
52
53
  cfg = config.read()
54
+ if cfg.email is None or cfg.password is None:
55
+ raise ValueError("Config is missing email or password")
53
56
  base_url = get_base_url()
54
57
  resp = requests.post(
55
58
  base_url + "/login/access-token",
56
- data=dict(username=cfg.username, password=cfg.password),
59
+ data=dict(username=cfg.email, password=cfg.password),
57
60
  )
58
61
  token = resp.json()["access_token"]
59
62
  _tokens[base_url] = token
@@ -48,6 +48,7 @@ class Settings(BaseSettings):
48
48
  env_prefix="CALKIT" + get_env_suffix(sep="_") + "_",
49
49
  )
50
50
  username: str | None = None
51
+ email: str | None = None
51
52
  token: str | None = None
52
53
  dvc_token: str | None = None
53
54
  dataframe_engine: Literal["pandas", "polars"] = "pandas"
@@ -16,13 +16,14 @@ except ImportError:
16
16
  UTC = _timezone.utc
17
17
 
18
18
  from datetime import datetime
19
-
20
19
  from typing import Literal
21
20
 
22
21
  import ruamel.yaml
23
22
  from git import Repo
24
23
  from git.exc import InvalidGitRepositoryError
25
24
 
25
+ from calkit.models import ProjectInfo
26
+
26
27
  logging.basicConfig(level=logging.INFO)
27
28
  logger = logging.getLogger(__package__)
28
29
 
@@ -60,8 +61,10 @@ def find_project_dirs(relative=False, max_depth=3) -> list[str]:
60
61
 
61
62
 
62
63
  def load_calkit_info(
63
- wdir=None, process_includes: bool | str | list[str] = False
64
- ) -> dict:
64
+ wdir=None,
65
+ process_includes: bool | str | list[str] = False,
66
+ as_pydantic: bool = False,
67
+ ) -> dict | ProjectInfo:
65
68
  """Load Calkit project information.
66
69
 
67
70
  Parameters
@@ -99,6 +102,8 @@ def load_calkit_info(
99
102
  with open(include_fpath) as f:
100
103
  include_data = ryaml.load(f)
101
104
  info[kind][obj_name] |= include_data
105
+ if as_pydantic:
106
+ return ProjectInfo.model_validate(info)
102
107
  return info
103
108
 
104
109
 
@@ -42,6 +42,7 @@ class Publication(_CalkitObject):
42
42
  "journal-article",
43
43
  "conference-paper",
44
44
  "presentation",
45
+ "proposal",
45
46
  "poster",
46
47
  "report",
47
48
  "blog",
@@ -62,7 +63,15 @@ class ReferenceCollection(BaseModel):
62
63
 
63
64
  class Environment(BaseModel):
64
65
  kind: Literal[
65
- "conda", "docker", "pip", "poetry", "npm", "yarn", "remote-ssh"
66
+ "conda",
67
+ "docker",
68
+ "poetry",
69
+ "npm",
70
+ "yarn",
71
+ "remote-ssh",
72
+ "uv",
73
+ "pixi",
74
+ "uv-venv",
66
75
  ]
67
76
  path: str | None = None
68
77
  description: str | None = None
@@ -70,8 +79,13 @@ class Environment(BaseModel):
70
79
  default: bool | None = None
71
80
 
72
81
 
82
+ class UvVenvEnvironment(Environment):
83
+ kind: Literal["uv-venv"]
84
+ prefix: str
85
+
86
+
73
87
  class DockerEnvironment(Environment):
74
- kind: str = "docker"
88
+ kind: Literal["docker"]
75
89
  image: str
76
90
  layers: list[str] | None = None
77
91
  shell: Literal["bash", "sh"] = "sh"
@@ -106,6 +120,7 @@ class ProcedureInput(BaseModel):
106
120
  description : str
107
121
  Optional longer description of the input.
108
122
  """
123
+
109
124
  name: str | None = None
110
125
  dtype: Literal["int", "bool", "str", "float"] = None
111
126
  units: str | None = None
@@ -143,6 +158,32 @@ class Procedure(BaseModel):
143
158
  imported_from: str | None = None
144
159
 
145
160
 
161
+ class Release(BaseModel):
162
+ kind: Literal["project", "publication", "dataset", "model", "figure"]
163
+ url: str | None = None
164
+ doi: str | None = None
165
+ has_suffix: bool = False
166
+
167
+
168
+ class ProjectRelease(Release):
169
+ kind: Literal["project"]
170
+
171
+
172
+ class PublicationRelease(Release):
173
+ kind: Literal["publication"]
174
+ path: str
175
+
176
+
177
+ class DatasetRelease(Release):
178
+ kind: Literal["dataset"]
179
+ path: str
180
+
181
+
182
+ class ModelRelease(Release):
183
+ kind: Literal["model"]
184
+ path: str
185
+
186
+
146
187
  class ProjectInfo(BaseModel):
147
188
  """All of the project's information or metadata, written to the
148
189
  ``calkit.yaml`` file.
@@ -174,7 +215,13 @@ class ProjectInfo(BaseModel):
174
215
  figures: list[Figure] = []
175
216
  publications: list[Publication] = []
176
217
  references: list[ReferenceCollection] = []
177
- environments: dict[str, Environment | DockerEnvironment] = {}
218
+ environments: dict[
219
+ str, Environment | DockerEnvironment | UvVenvEnvironment
220
+ ] = {}
178
221
  software: list[Software] = []
179
222
  notebooks: list[Notebook] = []
180
223
  procedures: dict[str, Procedure] = {}
224
+ releases: dict[
225
+ str,
226
+ ProjectRelease | PublicationRelease | DatasetRelease | ModelRelease,
227
+ ] = {}
@@ -89,6 +89,23 @@ def test_run_in_env(tmp_dir):
89
89
  ck_info = calkit.load_calkit_info()
90
90
  env = ck_info["environments"]["py3.10"]
91
91
  assert env.get("path") is None
92
+ # Test uv venv
93
+ subprocess.check_call(
94
+ ["calkit", "new", "uv-venv", "-n", "uv1", "polars==1.18.0"]
95
+ )
96
+ out = subprocess.check_output(
97
+ [
98
+ "calkit",
99
+ "runenv",
100
+ "-n",
101
+ "uv1",
102
+ "--",
103
+ "python",
104
+ "-c",
105
+ "'import polars; print(polars.__version__)'",
106
+ ]
107
+ ).decode().strip()
108
+ assert out == "1.18.0"
92
109
 
93
110
 
94
111
  def test_check_call():
@@ -169,3 +169,46 @@ def test_new_publication(tmp_dir):
169
169
  "calkit runenv -n my-latex-env "
170
170
  '"cd my-paper && latexmk -interaction=nonstopmode -pdf paper.tex"'
171
171
  )
172
+
173
+
174
+ def test_new_uv_venv(tmp_dir):
175
+ subprocess.check_call(["git", "init"])
176
+ subprocess.check_call(["dvc", "init"])
177
+ subprocess.check_call(
178
+ [
179
+ "calkit",
180
+ "new",
181
+ "uv-venv",
182
+ "-n",
183
+ "my-uv-venv",
184
+ "pandas>=2.0",
185
+ "matplotlib",
186
+ ]
187
+ )
188
+ ck_info = calkit.load_calkit_info(as_pydantic=True)
189
+ envs = ck_info.environments
190
+ env = envs["my-uv-venv"]
191
+ assert env.path == "requirements.txt"
192
+ assert env.prefix == ".venv"
193
+ assert env.kind == "uv-venv"
194
+ subprocess.check_call(
195
+ [
196
+ "calkit",
197
+ "new",
198
+ "uv-venv",
199
+ "-n",
200
+ "my-uv-venv2",
201
+ "--path",
202
+ "requirements-2.txt",
203
+ "--prefix",
204
+ ".venv2",
205
+ "pandas>=2.0",
206
+ "matplotlib",
207
+ ]
208
+ )
209
+ ck_info = calkit.load_calkit_info(as_pydantic=True)
210
+ envs = ck_info.environments
211
+ env = envs["my-uv-venv2"]
212
+ assert env.path == "requirements-2.txt"
213
+ assert env.prefix == ".venv2"
214
+ assert env.kind == "uv-venv"
File without changes
File without changes