calkit-python 0.1.1__tar.gz → 0.2.1__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.1.1 → calkit_python-0.2.1}/PKG-INFO +1 -1
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/__init__.py +1 -1
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/core.py +5 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/main.py +87 -35
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/new.py +14 -26
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/docker.py +5 -3
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/dvc.py +15 -3
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/models.py +17 -0
- calkit_python-0.2.1/calkit/tests/test_dvc.py +15 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/.github/FUNDING.yml +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/.github/workflows/publish-test.yml +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/.github/workflows/publish.yml +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/.gitignore +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/LICENSE +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/README.md +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/config.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/import_.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/list.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cli/notebooks.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/cloud.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/config.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/core.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/data.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/git.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/gui.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/jupyter.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/server.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/cli/test_main.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/cli/test_new.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/test_core.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/cfd-study/README.md +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/cfd-study/calkit.yaml +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/cfd-study/config/simulations/runs.csv +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/cfd-study/notebook.ipynb +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/ms-office/.gitignore +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/ms-office/README.md +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/examples/ms-office/calkit.yaml +0 -0
- {calkit_python-0.1.1 → calkit_python-0.2.1}/pyproject.toml +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
7
8
|
import subprocess
|
|
@@ -12,7 +13,7 @@ import typer
|
|
|
12
13
|
from typing_extensions import Annotated, Optional
|
|
13
14
|
|
|
14
15
|
import calkit
|
|
15
|
-
from calkit.cli import print_sep, run_cmd
|
|
16
|
+
from calkit.cli import print_sep, raise_error, run_cmd
|
|
16
17
|
from calkit.cli.config import config_app
|
|
17
18
|
from calkit.cli.import_ import import_app
|
|
18
19
|
from calkit.cli.list import list_app
|
|
@@ -26,8 +27,11 @@ app = typer.Typer(
|
|
|
26
27
|
pretty_exceptions_show_locals=False,
|
|
27
28
|
)
|
|
28
29
|
app.add_typer(config_app, name="config", help="Configure Calkit.")
|
|
30
|
+
app.add_typer(new_app, name="new", help="Create a new Calkit object.")
|
|
29
31
|
app.add_typer(
|
|
30
|
-
new_app,
|
|
32
|
+
new_app,
|
|
33
|
+
name="create",
|
|
34
|
+
help="Create a new Calkit object (alias for 'new').",
|
|
31
35
|
)
|
|
32
36
|
app.add_typer(notebooks_app, name="nb", help="Work with Jupyter notebooks.")
|
|
33
37
|
app.add_typer(list_app, name="list", help="List Calkit objects.")
|
|
@@ -46,6 +50,53 @@ def main(
|
|
|
46
50
|
raise typer.Exit()
|
|
47
51
|
|
|
48
52
|
|
|
53
|
+
@app.command(name="clone")
|
|
54
|
+
def clone(
|
|
55
|
+
url: Annotated[str, typer.Argument(help="Repo URL.")],
|
|
56
|
+
location: Annotated[
|
|
57
|
+
str,
|
|
58
|
+
typer.Argument(
|
|
59
|
+
help="Location to clone to (default will be ./{repo_name})"
|
|
60
|
+
),
|
|
61
|
+
] = None,
|
|
62
|
+
no_config_remote: Annotated[
|
|
63
|
+
bool,
|
|
64
|
+
typer.Option(
|
|
65
|
+
"--no-config-remote",
|
|
66
|
+
help="Do not automatically configure Calkit DVC remote.",
|
|
67
|
+
),
|
|
68
|
+
] = False,
|
|
69
|
+
no_dvc_pull: Annotated[
|
|
70
|
+
bool, typer.Option("--no-dvc-pull", help="Do not pull DVC objects.")
|
|
71
|
+
] = False,
|
|
72
|
+
):
|
|
73
|
+
"""Clone a Git repo and by default configure and pull from the DVC
|
|
74
|
+
remote.
|
|
75
|
+
"""
|
|
76
|
+
# Git clone
|
|
77
|
+
cmd = ["git", "clone", url]
|
|
78
|
+
if location is not None:
|
|
79
|
+
cmd.append(location)
|
|
80
|
+
try:
|
|
81
|
+
subprocess.call(cmd)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise_error(str(e))
|
|
84
|
+
if location is None:
|
|
85
|
+
location = url.split("/")[-1].removesuffix(".git")
|
|
86
|
+
typer.echo(f"Moving into repo dir: {location}")
|
|
87
|
+
os.chdir(location)
|
|
88
|
+
# Setup auth for any Calkit remotes
|
|
89
|
+
if not no_config_remote:
|
|
90
|
+
remotes = calkit.dvc.get_remotes()
|
|
91
|
+
for name, url in remotes.items():
|
|
92
|
+
if name == "calkit" or name.startswith("calkit:"):
|
|
93
|
+
typer.echo(f"Setting up authentication for DVC remote: {name}")
|
|
94
|
+
calkit.dvc.set_remote_auth(remote_name=name)
|
|
95
|
+
# DVC pull
|
|
96
|
+
if not no_dvc_pull:
|
|
97
|
+
subprocess.call(["dvc", "pull"])
|
|
98
|
+
|
|
99
|
+
|
|
49
100
|
@app.command(name="status")
|
|
50
101
|
def get_status():
|
|
51
102
|
"""Get a unified Git and DVC status."""
|
|
@@ -97,8 +148,7 @@ def add(
|
|
|
97
148
|
adding any .dvc files to Git when adding to DVC.
|
|
98
149
|
"""
|
|
99
150
|
if to is not None and to not in ["git", "dvc"]:
|
|
100
|
-
|
|
101
|
-
raise typer.Exit(1)
|
|
151
|
+
raise_error(f"Invalid option for 'to': {to}")
|
|
102
152
|
# Ensure autostage is enabled for DVC
|
|
103
153
|
subprocess.call(["dvc", "config", "core.autostage", "true"])
|
|
104
154
|
subprocess.call(["git", "add", ".dvc/config"])
|
|
@@ -117,13 +167,11 @@ def add(
|
|
|
117
167
|
]
|
|
118
168
|
dvc_size_thresh_bytes = 1_000_000
|
|
119
169
|
if "." in paths and to is None:
|
|
120
|
-
|
|
121
|
-
raise typer.Exit(1)
|
|
170
|
+
raise_error("Cannot add '.' with calkit; use git or dvc")
|
|
122
171
|
if to is None:
|
|
123
172
|
for path in paths:
|
|
124
173
|
if os.path.isdir(path):
|
|
125
|
-
|
|
126
|
-
raise typer.Exit(1)
|
|
174
|
+
raise_error("Cannot auto-add directories; use git or dvc")
|
|
127
175
|
repo = git.Repo()
|
|
128
176
|
for path in paths:
|
|
129
177
|
# Detect if this file should be tracked with Git or DVC
|
|
@@ -323,8 +371,7 @@ def run_dvc_repro(
|
|
|
323
371
|
subprocess.call(["dvc", "repro"] + args)
|
|
324
372
|
# Now parse stage metadata for calkit objects
|
|
325
373
|
if not os.path.isfile("dvc.yaml"):
|
|
326
|
-
|
|
327
|
-
raise typer.Exit(1)
|
|
374
|
+
raise_error("No dvc.yaml file found")
|
|
328
375
|
objects = []
|
|
329
376
|
with open("dvc.yaml") as f:
|
|
330
377
|
pipeline = calkit.ryaml.load(f)
|
|
@@ -332,21 +379,18 @@ def run_dvc_repro(
|
|
|
332
379
|
ckmeta = stage_info.get("meta", {}).get("calkit")
|
|
333
380
|
if ckmeta is not None:
|
|
334
381
|
if not isinstance(ckmeta, dict):
|
|
335
|
-
|
|
382
|
+
raise_error(
|
|
336
383
|
f"Calkit metadata for {stage_name} is not a dictionary"
|
|
337
384
|
)
|
|
338
|
-
typer.Exit(1)
|
|
339
385
|
# Stage must have a single output
|
|
340
386
|
outs = stage_info.get("outs", [])
|
|
341
387
|
if len(outs) != 1:
|
|
342
|
-
|
|
388
|
+
raise_error(
|
|
343
389
|
f"Stage {stage_name} does not have exactly one output"
|
|
344
390
|
)
|
|
345
|
-
raise typer.Exit(1)
|
|
346
391
|
cktype = ckmeta.get("type")
|
|
347
392
|
if cktype not in ["figure", "dataset", "publication"]:
|
|
348
|
-
|
|
349
|
-
raise typer.Exit(1)
|
|
393
|
+
raise_error(f"Invalid Calkit output type '{cktype}'")
|
|
350
394
|
objects.append(
|
|
351
395
|
dict(path=outs[0]) | ckmeta | dict(stage=stage_name)
|
|
352
396
|
)
|
|
@@ -433,21 +477,24 @@ def run_in_env(
|
|
|
433
477
|
ck_info = calkit.load_calkit_info()
|
|
434
478
|
envs = ck_info.get("environments", {})
|
|
435
479
|
if not envs:
|
|
436
|
-
|
|
437
|
-
raise typer.Exit(1)
|
|
480
|
+
raise_error("No environments defined in calkit.yaml")
|
|
438
481
|
if isinstance(envs, list):
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
482
|
+
raise_error("Error: Environments should be a dict, not a list")
|
|
483
|
+
if env_name is None:
|
|
484
|
+
# See if there's a default env, or only one env defined
|
|
485
|
+
default_env_name = None
|
|
486
|
+
for n, e in envs.items():
|
|
487
|
+
if e.get("default"):
|
|
488
|
+
if default_env_name is not None:
|
|
489
|
+
raise_error(
|
|
490
|
+
"Only one default environment can be specified"
|
|
491
|
+
)
|
|
492
|
+
default_env_name = n
|
|
493
|
+
if default_env_name is None and len(envs) == 1:
|
|
494
|
+
default_env_name = list(envs.keys())[0]
|
|
495
|
+
env_name = default_env_name
|
|
449
496
|
if env_name is None:
|
|
450
|
-
|
|
497
|
+
raise_error("Environment must be specified if there are multiple")
|
|
451
498
|
env = envs[env_name]
|
|
452
499
|
cwd = os.getcwd()
|
|
453
500
|
image_name = env.get("image", env_name)
|
|
@@ -477,8 +524,7 @@ def run_in_env(
|
|
|
477
524
|
typer.echo(f"Running command: {cmd}")
|
|
478
525
|
subprocess.call(cmd)
|
|
479
526
|
else:
|
|
480
|
-
|
|
481
|
-
raise typer.Exit(1)
|
|
527
|
+
raise_error("Environment kind not supported")
|
|
482
528
|
|
|
483
529
|
|
|
484
530
|
@app.command(
|
|
@@ -507,8 +553,7 @@ def check_call(
|
|
|
507
553
|
subprocess.check_call(if_error, shell=True)
|
|
508
554
|
typer.echo("Fallback call succeeded")
|
|
509
555
|
except subprocess.CalledProcessError:
|
|
510
|
-
|
|
511
|
-
raise typer.Exit(1)
|
|
556
|
+
raise_error("Fallback call failed")
|
|
512
557
|
|
|
513
558
|
|
|
514
559
|
@app.command(
|
|
@@ -539,6 +584,10 @@ def build_docker(
|
|
|
539
584
|
except subprocess.CalledProcessError:
|
|
540
585
|
typer.echo(f"No image with tag {tag} found locally")
|
|
541
586
|
inspect = []
|
|
587
|
+
typer.echo(f"Reading Dockerfile from {fpath}")
|
|
588
|
+
with open(fpath) as f:
|
|
589
|
+
dockerfile = f.read()
|
|
590
|
+
dockerfile_md5 = hashlib.md5(dockerfile.encode()).hexdigest()
|
|
542
591
|
lock_fpath = fpath + "-lock.json"
|
|
543
592
|
rebuild = True
|
|
544
593
|
if os.path.isfile(lock_fpath):
|
|
@@ -549,11 +598,14 @@ def build_docker(
|
|
|
549
598
|
typer.echo(f"Lock file ({lock_fpath}) does not exist")
|
|
550
599
|
lock = None
|
|
551
600
|
if inspect and lock:
|
|
552
|
-
typer.echo("Checking image against lock file")
|
|
553
|
-
rebuild = inspect[0]["RootFS"]["Layers"] != lock[0]["RootFS"][
|
|
601
|
+
typer.echo("Checking image and Dockerfile against lock file")
|
|
602
|
+
rebuild = inspect[0]["RootFS"]["Layers"] != lock[0]["RootFS"][
|
|
603
|
+
"Layers"
|
|
604
|
+
] or dockerfile_md5 != lock[0].get("DockerfileMD5")
|
|
554
605
|
if rebuild:
|
|
555
606
|
subprocess.check_call(["docker", "build", "-t", tag, "-f", fpath, "."])
|
|
556
607
|
# Write the lock file
|
|
557
608
|
inspect = get_docker_inspect()
|
|
609
|
+
inspect[0]["DockerfileMD5"] = dockerfile_md5
|
|
558
610
|
with open(lock_fpath, "w") as f:
|
|
559
611
|
json.dump(inspect, f, indent=4)
|
|
@@ -10,17 +10,13 @@ import typer
|
|
|
10
10
|
from typing_extensions import Annotated
|
|
11
11
|
|
|
12
12
|
import calkit
|
|
13
|
+
from calkit.cli import raise_error
|
|
13
14
|
from calkit.core import ryaml
|
|
14
15
|
from calkit.docker import LAYERS
|
|
15
16
|
|
|
16
17
|
new_app = typer.Typer(no_args_is_help=True)
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def _error(txt):
|
|
20
|
-
typer.echo(txt, err=txt)
|
|
21
|
-
raise typer.Exit(1)
|
|
22
|
-
|
|
23
|
-
|
|
24
20
|
@new_app.command(name="figure")
|
|
25
21
|
def new_figure(
|
|
26
22
|
path: str,
|
|
@@ -69,18 +65,18 @@ def new_figure(
|
|
|
69
65
|
),
|
|
70
66
|
] = False,
|
|
71
67
|
):
|
|
72
|
-
"""
|
|
68
|
+
"""Create a new figure."""
|
|
73
69
|
ck_info = calkit.load_calkit_info()
|
|
74
70
|
figures = ck_info.get("figures", [])
|
|
75
71
|
paths = [f.get("path") for f in figures]
|
|
76
72
|
if not overwrite and path in paths:
|
|
77
|
-
|
|
73
|
+
raise_error(f"Figure at path {path} already exists")
|
|
78
74
|
if cmd is not None and stage_name is None:
|
|
79
|
-
|
|
75
|
+
raise_error("Stage name must be provided if command is specified")
|
|
80
76
|
if (deps or outs or outs_from_stage) and not cmd:
|
|
81
|
-
|
|
77
|
+
raise_error("Command must be provided")
|
|
82
78
|
if (deps or outs or outs_from_stage) and not stage_name:
|
|
83
|
-
|
|
79
|
+
raise_error("Stage name must be provided")
|
|
84
80
|
obj = dict(path=path, title=title)
|
|
85
81
|
if description is not None:
|
|
86
82
|
obj["description"] = description
|
|
@@ -91,7 +87,7 @@ def new_figure(
|
|
|
91
87
|
pipeline = calkit.dvc.read_pipeline()
|
|
92
88
|
stages = pipeline.get("stages", {})
|
|
93
89
|
if outs_from_stage not in stages:
|
|
94
|
-
|
|
90
|
+
raise_error(f"Stage {outs_from_stage} does not exist")
|
|
95
91
|
deps += stages[outs_from_stage].get("outs", [])
|
|
96
92
|
if path not in outs:
|
|
97
93
|
outs.append(path)
|
|
@@ -232,15 +228,9 @@ def new_docker_env(
|
|
|
232
228
|
):
|
|
233
229
|
"""Create a new Docker environment."""
|
|
234
230
|
if base and os.path.isfile(path) and not overwrite:
|
|
235
|
-
|
|
236
|
-
"Output path already exists (use -f to overwrite)", err=True
|
|
237
|
-
)
|
|
238
|
-
raise typer.Exit(1)
|
|
231
|
+
raise_error("Output path already exists (use -f to overwrite)")
|
|
239
232
|
if stage and not base:
|
|
240
|
-
|
|
241
|
-
"--from must be specified when creating a build stage", err=True
|
|
242
|
-
)
|
|
243
|
-
raise typer.Exit(1)
|
|
233
|
+
raise_error("--from must be specified when creating a build stage")
|
|
244
234
|
if image_name is None:
|
|
245
235
|
typer.echo("No image name specified; using environment name")
|
|
246
236
|
image_name = name
|
|
@@ -249,8 +239,7 @@ def new_docker_env(
|
|
|
249
239
|
txt = "FROM " + base + "\n\n"
|
|
250
240
|
for layer in layers:
|
|
251
241
|
if layer not in LAYERS:
|
|
252
|
-
|
|
253
|
-
raise typer.Exit(1)
|
|
242
|
+
raise_error(f"Unknown layer type '{layer}'")
|
|
254
243
|
txt += LAYERS[layer] + "\n\n"
|
|
255
244
|
txt += f"RUN mkdir {wdir}\n"
|
|
256
245
|
txt += f"WORKDIR {wdir}\n"
|
|
@@ -264,11 +253,10 @@ def new_docker_env(
|
|
|
264
253
|
typer.echo("Converting environments from list to dict")
|
|
265
254
|
envs = {env.pop("name"): env for env in envs}
|
|
266
255
|
if name in envs and not overwrite:
|
|
267
|
-
|
|
256
|
+
raise_error(
|
|
268
257
|
f"Environment with name {name} already exists "
|
|
269
258
|
"(use -f to overwrite)"
|
|
270
259
|
)
|
|
271
|
-
raise typer.Exit(1)
|
|
272
260
|
if base:
|
|
273
261
|
repo.git.add(path)
|
|
274
262
|
typer.echo("Adding environment to calkit.yaml")
|
|
@@ -309,7 +297,8 @@ def new_docker_env(
|
|
|
309
297
|
]
|
|
310
298
|
)
|
|
311
299
|
repo.git.add("calkit.yaml")
|
|
312
|
-
|
|
300
|
+
if stage:
|
|
301
|
+
repo.git.add("dvc.yaml")
|
|
313
302
|
if not no_commit and repo.git.diff("--staged"):
|
|
314
303
|
repo.git.commit(["-m", f"Add Docker environment {name}"])
|
|
315
304
|
|
|
@@ -348,8 +337,7 @@ def new_foreach_stage(
|
|
|
348
337
|
"""
|
|
349
338
|
pipeline = calkit.dvc.read_pipeline()
|
|
350
339
|
if name in pipeline and not overwrite:
|
|
351
|
-
|
|
352
|
-
raise typer.Exit(1)
|
|
340
|
+
raise_error("Stage already exists; use -f to overwrite")
|
|
353
341
|
if "stages" not in pipeline:
|
|
354
342
|
pipeline["stages"] = {}
|
|
355
343
|
pipeline["stages"][name] = dict(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Functionality for working with Docker."""
|
|
2
2
|
|
|
3
|
-
MAMBAFORGE_LAYER_TXT = """
|
|
3
|
+
MAMBAFORGE_LAYER_TXT = r"""
|
|
4
4
|
# Install Miniforge
|
|
5
5
|
ARG MINIFORGE_NAME=Mambaforge
|
|
6
6
|
ARG MINIFORGE_VERSION=24.3.0-0
|
|
@@ -40,8 +40,10 @@ RUN apt-get update > /dev/null && \
|
|
|
40
40
|
echo ". ${CONDA_DIR}/etc/profile.d/conda.sh && conda activate base" >> ~/.bashrc
|
|
41
41
|
""".strip()
|
|
42
42
|
|
|
43
|
-
FOAMPY_LAYER_TEXT = """
|
|
44
|
-
RUN pip install numpy pandas matplotlib
|
|
43
|
+
FOAMPY_LAYER_TEXT = r"""
|
|
44
|
+
RUN pip install --no-cache-dir numpy pandas matplotlib h5py \
|
|
45
|
+
&& pip install --no-cache-dir scipy \
|
|
46
|
+
&& pip install --no-cache-dir foampy
|
|
45
47
|
""".strip()
|
|
46
48
|
|
|
47
49
|
LAYERS = {
|
|
@@ -67,9 +67,7 @@ def add_external_remote(owner_name: str, project_name: str):
|
|
|
67
67
|
base_url = calkit.cloud.get_base_url()
|
|
68
68
|
remote_url = f"{base_url}/projects/{owner_name}/{project_name}/dvc"
|
|
69
69
|
remote_name = f"{get_app_name()}:{owner_name}/{project_name}"
|
|
70
|
-
subprocess.call(
|
|
71
|
-
["dvc", "remote", "add", "-f", remote_name, remote_url]
|
|
72
|
-
)
|
|
70
|
+
subprocess.call(["dvc", "remote", "add", "-f", remote_name, remote_url])
|
|
73
71
|
subprocess.call(["dvc", "remote", "modify", remote_name, "auth", "custom"])
|
|
74
72
|
set_remote_auth(remote_name)
|
|
75
73
|
|
|
@@ -79,3 +77,17 @@ def read_pipeline() -> dict:
|
|
|
79
77
|
return {}
|
|
80
78
|
with open("dvc.yaml") as f:
|
|
81
79
|
return calkit.ryaml.load(f)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_remotes() -> dict[str, str]:
|
|
83
|
+
"""Get a dictionary of DVC remotes, keyed by name, with URL as the
|
|
84
|
+
value.
|
|
85
|
+
"""
|
|
86
|
+
out = subprocess.check_output(["dvc", "remote", "list"]).decode().strip()
|
|
87
|
+
if not out:
|
|
88
|
+
return {}
|
|
89
|
+
resp = {}
|
|
90
|
+
for line in out.split("\n"):
|
|
91
|
+
name, url = line.split("\t")
|
|
92
|
+
resp[name] = url
|
|
93
|
+
return resp
|
|
@@ -66,6 +66,7 @@ class Environment(BaseModel):
|
|
|
66
66
|
path: str | None = None
|
|
67
67
|
description: str | None = None
|
|
68
68
|
stage: str | None = None
|
|
69
|
+
default: bool | None = None
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
class DockerEnvironment(Environment):
|
|
@@ -89,8 +90,24 @@ class Notebook(_CalkitObject):
|
|
|
89
90
|
class ProjectInfo(BaseModel):
|
|
90
91
|
"""All of the project's information or metadata, written to the
|
|
91
92
|
``calkit.yaml`` file.
|
|
93
|
+
|
|
94
|
+
Attributes
|
|
95
|
+
----------
|
|
96
|
+
parent : str
|
|
97
|
+
The project's parent project, if applicable. This should be set if
|
|
98
|
+
the project was created as a copy of another. This is similar to the
|
|
99
|
+
concept of forking, but unlike a fork, a child project's changes
|
|
100
|
+
are not meant to be merged back into the parent.
|
|
101
|
+
The format of this field should be something like
|
|
102
|
+
{owner_name}/{project_name}, e.g., 'someuser/some-project-name'.
|
|
103
|
+
Note that individual objects can be imported from other projects, but
|
|
104
|
+
that doesn't necessarily make them parent projects.
|
|
105
|
+
This is probably not that important of a distinction.
|
|
106
|
+
The real use case is being able to trace where things came from and
|
|
107
|
+
distinguish what has been newly created here.
|
|
92
108
|
"""
|
|
93
109
|
|
|
110
|
+
parent: str | None = None
|
|
94
111
|
questions: list[str] = []
|
|
95
112
|
datasets: list[Dataset] = []
|
|
96
113
|
figures: list[Figure] = []
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Tests for the ``dvc`` module."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import calkit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_get_remotes(tmp_dir):
|
|
9
|
+
subprocess.call(["git", "init"])
|
|
10
|
+
assert not calkit.dvc.get_remotes()
|
|
11
|
+
subprocess.call(["dvc", "init"])
|
|
12
|
+
assert not calkit.dvc.get_remotes()
|
|
13
|
+
subprocess.call(["dvc", "remote", "add", "something", "https://sup.com"])
|
|
14
|
+
resp = calkit.dvc.get_remotes()
|
|
15
|
+
assert resp == {"something": "https://sup.com"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|