calkit-python 0.11.0__tar.gz → 0.11.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {calkit_python-0.11.0 → calkit_python-0.11.2}/PKG-INFO +1 -1
  2. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/__init__.py +1 -1
  3. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/main.py +7 -4
  4. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/new.py +1 -1
  5. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cloud.py +5 -2
  6. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/conda.py +37 -30
  7. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/config.py +1 -0
  8. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/core.py +7 -2
  9. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/models.py +32 -0
  10. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_conda.py +38 -1
  11. {calkit_python-0.11.0 → calkit_python-0.11.2}/.github/FUNDING.yml +0 -0
  12. {calkit_python-0.11.0 → calkit_python-0.11.2}/.github/workflows/publish-test.yml +0 -0
  13. {calkit_python-0.11.0 → calkit_python-0.11.2}/.github/workflows/publish.yml +0 -0
  14. {calkit_python-0.11.0 → calkit_python-0.11.2}/.gitignore +0 -0
  15. {calkit_python-0.11.0 → calkit_python-0.11.2}/LICENSE +0 -0
  16. {calkit_python-0.11.0 → calkit_python-0.11.2}/README.md +0 -0
  17. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/calc.py +0 -0
  18. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/check.py +0 -0
  19. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/__init__.py +0 -0
  20. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/check.py +0 -0
  21. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/config.py +0 -0
  22. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/core.py +0 -0
  23. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/import_.py +0 -0
  24. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/list.py +0 -0
  25. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/notebooks.py +0 -0
  26. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/office.py +0 -0
  27. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/cli/update.py +0 -0
  28. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/data.py +0 -0
  29. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/docker.py +0 -0
  30. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/dvc.py +0 -0
  31. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/git.py +0 -0
  32. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/gui.py +0 -0
  33. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/jupyter.py +0 -0
  34. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/magics.py +0 -0
  35. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/office.py +0 -0
  36. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/server.py +0 -0
  37. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/__init__.py +0 -0
  38. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/core.py +0 -0
  39. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/__init__.py +0 -0
  40. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/article/paper.tex +0 -0
  41. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/core.py +0 -0
  42. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/jfm/jfm.bst +0 -0
  43. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/jfm/jfm.cls +0 -0
  44. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  45. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/jfm/paper.tex +0 -0
  46. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/templates/latex/jfm/upmath.sty +0 -0
  47. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/__init__.py +0 -0
  48. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/cli/__init__.py +0 -0
  49. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/cli/test_list.py +0 -0
  50. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/cli/test_main.py +0 -0
  51. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/cli/test_new.py +0 -0
  52. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_calc.py +0 -0
  53. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_check.py +0 -0
  54. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_core.py +0 -0
  55. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_dvc.py +0 -0
  56. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_jupyter.py +0 -0
  57. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_magics.py +0 -0
  58. {calkit_python-0.11.0 → calkit_python-0.11.2}/calkit/tests/test_templates.py +0 -0
  59. {calkit_python-0.11.0 → calkit_python-0.11.2}/docs/img/calkit-no-bg.png +0 -0
  60. {calkit_python-0.11.0 → calkit_python-0.11.2}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  61. {calkit_python-0.11.0 → calkit_python-0.11.2}/docs/tutorials/conda-envs.md +0 -0
  62. {calkit_python-0.11.0 → calkit_python-0.11.2}/docs/tutorials/img/run-proc.png +0 -0
  63. {calkit_python-0.11.0 → calkit_python-0.11.2}/docs/tutorials/notebook-pipeline.md +0 -0
  64. {calkit_python-0.11.0 → calkit_python-0.11.2}/docs/tutorials/procedures.md +0 -0
  65. {calkit_python-0.11.0 → calkit_python-0.11.2}/pyproject.toml +0 -0
  66. {calkit_python-0.11.0 → calkit_python-0.11.2}/test/pipeline.ipynb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.11.0
3
+ Version: 0.11.2
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.0"
1
+ __version__ = "0.11.2"
2
2
 
3
3
  from .core import *
4
4
  from . import git
@@ -93,9 +93,9 @@ def clone(
93
93
  if location is not None:
94
94
  cmd.append(location)
95
95
  try:
96
- subprocess.call(cmd)
97
- except Exception as e:
98
- raise_error(str(e))
96
+ subprocess.check_call(cmd)
97
+ except subprocess.CalledProcessError:
98
+ raise_error("Failed to clone with Git")
99
99
  if location is None:
100
100
  location = url.split("/")[-1].removesuffix(".git")
101
101
  typer.echo(f"Moving into repo dir: {location}")
@@ -109,7 +109,10 @@ def clone(
109
109
  calkit.dvc.set_remote_auth(remote_name=name)
110
110
  # DVC pull
111
111
  if not no_dvc_pull:
112
- subprocess.call(["dvc", "pull"])
112
+ try:
113
+ subprocess.check_call(["dvc", "pull"])
114
+ except subprocess.CalledProcessError:
115
+ raise_error("Failed to pull from DVC remote(s)")
113
116
 
114
117
 
115
118
  @app.command(name="status")
@@ -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")
@@ -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
@@ -2,14 +2,43 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import re
5
6
  import subprocess
6
7
 
8
+ from packaging.specifiers import SpecifierSet
9
+ from packaging.version import Version
7
10
  from pydantic import BaseModel
8
11
 
9
12
  import calkit
10
13
  from calkit import ryaml
11
14
 
12
15
 
16
+ def _check_single(req: str, actual: str, conda: bool = False) -> bool:
17
+ """Helper function for checking actual versions against requirements."""
18
+ req_name = re.split("[=<>]", req)[0]
19
+ req_spec = req.removeprefix(req_name)
20
+ if conda and req_spec.startswith("="):
21
+ req_spec = "=" + req_spec
22
+ if not req_spec.endswith(".*"):
23
+ req_spec += ".*"
24
+ actual_name, actual_vers = re.split("[=<>]+", actual, maxsplit=1)
25
+ if actual_name != req_name:
26
+ return False
27
+ actual_spec = actual.removeprefix(actual_name)
28
+ if conda and actual_spec.startswith("="):
29
+ actual_spec = "=" + actual_spec
30
+ version = Version(actual_vers)
31
+ spec = SpecifierSet(req_spec)
32
+ return spec.contains(version)
33
+
34
+
35
+ def _check_list(req: str, actual: list[str], conda: bool = False) -> bool:
36
+ for installed in actual:
37
+ if _check_single(req, installed, conda=conda):
38
+ return True
39
+ return False
40
+
41
+
13
42
  class EnvCheckResult(BaseModel):
14
43
  env_exists: bool | None = None
15
44
  env_needs_export: bool | None = None
@@ -127,45 +156,23 @@ def check_env(
127
156
  required_conda_deps.append(dep.replace("==", "="))
128
157
  log_func("Checking conda dependencies")
129
158
  for dep in required_conda_deps:
130
- dep_split = dep.split("=")
131
- package = dep_split[0]
132
- if len(dep_split) > 1:
133
- version = dep_split[1]
134
- else:
135
- version = None
136
- if version is not None and dep not in existing_conda_deps:
159
+ is_okay = _check_list(
160
+ req=dep, actual=existing_conda_deps, conda=True
161
+ )
162
+ if not is_okay:
137
163
  log_func(f"Found missing dependency: {dep}")
138
164
  env_needs_rebuild = True
139
165
  break
140
- elif version is None:
141
- # TODO: This does not handle specification of only major or
142
- # major+minor version
143
- if package not in [
144
- d.split("=")[0] for d in existing_conda_deps
145
- ]:
146
- log_func(f"Found missing dependency: {dep}")
147
- env_needs_rebuild = True
148
- break
149
166
  if not env_needs_rebuild and not relaxed:
150
167
  log_func("Checking pip dependencies")
151
168
  for dep in required_pip_deps:
152
- dep_split = dep.split("==")
153
- package = dep_split[0]
154
- if len(dep_split) > 1:
155
- version = dep_split[1]
156
- else:
157
- version = None
158
- if version is not None and dep not in existing_pip_deps:
169
+ is_okay = _check_list(
170
+ req=dep, actual=existing_pip_deps, conda=False
171
+ )
172
+ if not is_okay:
159
173
  env_needs_rebuild = True
160
174
  log_func(f"Found missing dependency: {dep}")
161
175
  break
162
- elif version is None:
163
- if package not in [
164
- d.split("==")[0] for d in existing_pip_deps
165
- ]:
166
- log_func(f"Found missing dependency: {dep}")
167
- env_needs_rebuild = True
168
- break
169
176
  if env_needs_rebuild:
170
177
  res.env_needs_rebuild = True
171
178
  log_func(f"Rebuilding {env_name} since it does not match spec")
@@ -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,7 +61,9 @@ 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
+ wdir=None,
65
+ process_includes: bool | str | list[str] = False,
66
+ as_pydantic: bool = False,
64
67
  ) -> dict:
65
68
  """Load Calkit project information.
66
69
 
@@ -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",
@@ -106,6 +107,7 @@ class ProcedureInput(BaseModel):
106
107
  description : str
107
108
  Optional longer description of the input.
108
109
  """
110
+
109
111
  name: str | None = None
110
112
  dtype: Literal["int", "bool", "str", "float"] = None
111
113
  units: str | None = None
@@ -143,6 +145,32 @@ class Procedure(BaseModel):
143
145
  imported_from: str | None = None
144
146
 
145
147
 
148
+ class Release(BaseModel):
149
+ kind: Literal["project", "publication", "dataset", "model", "figure"]
150
+ url: str | None = None
151
+ doi: str | None = None
152
+ has_suffix: bool = False
153
+
154
+
155
+ class ProjectRelease(Release):
156
+ kind: Literal["project"]
157
+
158
+
159
+ class PublicationRelease(Release):
160
+ kind: Literal["publication"]
161
+ path: str
162
+
163
+
164
+ class DatasetRelease(Release):
165
+ kind: Literal["dataset"]
166
+ path: str
167
+
168
+
169
+ class ModelRelease(Release):
170
+ kind: Literal["model"]
171
+ path: str
172
+
173
+
146
174
  class ProjectInfo(BaseModel):
147
175
  """All of the project's information or metadata, written to the
148
176
  ``calkit.yaml`` file.
@@ -178,3 +206,7 @@ class ProjectInfo(BaseModel):
178
206
  software: list[Software] = []
179
207
  notebooks: list[Notebook] = []
180
208
  procedures: dict[str, Procedure] = {}
209
+ releases: dict[
210
+ str,
211
+ ProjectRelease | PublicationRelease | DatasetRelease | ModelRelease,
212
+ ] = {}
@@ -5,7 +5,25 @@ import uuid
5
5
 
6
6
  import pytest
7
7
 
8
- from calkit.conda import check_env
8
+ from calkit.conda import _check_list, _check_single, check_env
9
+
10
+
11
+ def test_check_single():
12
+ assert _check_single("python=3.12", "python=3.12.18", conda=True)
13
+ assert _check_single("python=3", "python=3.12.18", conda=True)
14
+ assert _check_single("python=3.12.18", "python=3.12.18", conda=True)
15
+ assert _check_single("python>=3.12,<3.13", "python==3.12.18", conda=False)
16
+
17
+
18
+ def test_check_list():
19
+ installed = ["python=3.12.1", "numpy=1.0.11"]
20
+ assert _check_list("python=3", installed, conda=True)
21
+ assert _check_list("numpy", installed, conda=True)
22
+ assert not _check_list("pandas", installed, conda=True)
23
+ installed = ["python==3.12.1", "numpy==1.0.11"]
24
+ assert _check_list("python>=3", installed, conda=False)
25
+ assert _check_list("numpy", installed, conda=False)
26
+ assert not _check_list("pandas", installed, conda=False)
9
27
 
10
28
 
11
29
  def delete_env(name: str):
@@ -124,3 +142,22 @@ def test_check_env(tmp_dir, env_name):
124
142
  )
125
143
  res = check_env(relaxed=True)
126
144
  assert not res.env_needs_rebuild
145
+ # Make sure we can handle other ways of specifying versions
146
+ subprocess.check_call(
147
+ [
148
+ "calkit",
149
+ "new",
150
+ "conda-env",
151
+ "--overwrite",
152
+ "-n",
153
+ env_name,
154
+ "python=3.12",
155
+ "--pip",
156
+ "numpy>=1",
157
+ ]
158
+ )
159
+ res = check_env()
160
+ assert res.env_needs_rebuild
161
+ res = check_env()
162
+ assert not res.env_needs_export
163
+ assert not res.env_needs_rebuild
File without changes
File without changes