lamin_cli 1.10.0__py2.py3-none-any.whl → 1.12.0__py2.py3-none-any.whl
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.
- lamin_cli/__init__.py +1 -1
- lamin_cli/__main__.py +322 -82
- lamin_cli/_cache.py +1 -1
- lamin_cli/_context.py +76 -0
- lamin_cli/_delete.py +52 -11
- lamin_cli/_io.py +10 -7
- lamin_cli/_load.py +6 -1
- lamin_cli/_save.py +50 -11
- lamin_cli/_settings.py +85 -27
- lamin_cli/clone/create_sqlite_clone_and_import_db.py +3 -1
- lamin_cli/compute/modal.py +3 -4
- lamin_cli/urls.py +4 -2
- {lamin_cli-1.10.0.dist-info → lamin_cli-1.12.0.dist-info}/METADATA +2 -3
- lamin_cli-1.12.0.dist-info/RECORD +22 -0
- {lamin_cli-1.10.0.dist-info → lamin_cli-1.12.0.dist-info}/WHEEL +1 -1
- lamin_cli-1.10.0.dist-info/RECORD +0 -21
- {lamin_cli-1.10.0.dist-info/licenses → lamin_cli-1.12.0.dist-info}/LICENSE +0 -0
- {lamin_cli-1.10.0.dist-info → lamin_cli-1.12.0.dist-info}/entry_points.txt +0 -0
lamin_cli/_cache.py
CHANGED
lamin_cli/_context.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from lamin_utils import logger
|
|
7
|
+
from lamindb_setup.core._settings_store import settings_dir
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_current_run_file() -> Path:
|
|
11
|
+
"""Get the path to the file storing the current run UID."""
|
|
12
|
+
return settings_dir / "current_shell_run.txt"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_interactive_shell() -> bool:
|
|
16
|
+
"""Check if running in an interactive terminal."""
|
|
17
|
+
return sys.stdin.isatty() and sys.stdout.isatty() and os.isatty(0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_script_filename() -> Path:
|
|
21
|
+
"""Try to get the filename of the calling shell script."""
|
|
22
|
+
import psutil
|
|
23
|
+
|
|
24
|
+
parent = psutil.Process(os.getppid())
|
|
25
|
+
cmdline = parent.cmdline()
|
|
26
|
+
|
|
27
|
+
# For shells like bash, sh, zsh
|
|
28
|
+
if parent.name() in ["bash", "sh", "zsh", "dash"]:
|
|
29
|
+
# cmdline is typically: ['/bin/bash', 'script.sh', ...]
|
|
30
|
+
if len(cmdline) > 1 and not cmdline[1].startswith("-"):
|
|
31
|
+
return Path(cmdline[1])
|
|
32
|
+
raise click.ClickException(
|
|
33
|
+
"Cannot determine script filename. Please run in an interactive shell."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def track():
|
|
38
|
+
import lamindb as ln
|
|
39
|
+
|
|
40
|
+
if not ln.setup.settings._instance_exists:
|
|
41
|
+
raise click.ClickException(
|
|
42
|
+
"Not connected to an instance. Please run: lamin connect account/name"
|
|
43
|
+
)
|
|
44
|
+
path = get_script_filename()
|
|
45
|
+
source_code = path.read_text()
|
|
46
|
+
transform = ln.Transform(
|
|
47
|
+
key=path.name, source_code=source_code, type="script"
|
|
48
|
+
).save()
|
|
49
|
+
run = ln.Run(transform=transform).save()
|
|
50
|
+
current_run_file = get_current_run_file()
|
|
51
|
+
current_run_file.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
current_run_file.write_text(run.uid)
|
|
53
|
+
logger.important(f"started tracking shell run: {run.uid}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def finish():
|
|
57
|
+
from datetime import datetime, timezone
|
|
58
|
+
|
|
59
|
+
import lamindb as ln
|
|
60
|
+
|
|
61
|
+
if not ln.setup.settings._instance_exists:
|
|
62
|
+
raise click.ClickException(
|
|
63
|
+
"Not connected to an instance. Please run: lamin connect account/name"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
current_run_file = get_current_run_file()
|
|
67
|
+
if not current_run_file.exists():
|
|
68
|
+
raise click.ClickException(
|
|
69
|
+
"No active run to finish. Please run `lamin track` first."
|
|
70
|
+
)
|
|
71
|
+
run = ln.Run.get(uid=current_run_file.read_text().strip())
|
|
72
|
+
run._status_code = 0
|
|
73
|
+
run.finished_at = datetime.now(timezone.utc)
|
|
74
|
+
run.save()
|
|
75
|
+
current_run_file.unlink()
|
|
76
|
+
logger.important(f"finished tracking shell run: {run.uid}")
|
lamin_cli/_delete.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import click
|
|
1
2
|
from lamindb_setup import connect
|
|
2
3
|
from lamindb_setup import delete as delete_instance
|
|
4
|
+
from lamindb_setup.errors import StorageNotEmpty
|
|
3
5
|
|
|
4
6
|
from .urls import decompose_url
|
|
5
7
|
|
|
@@ -8,7 +10,7 @@ def delete(
|
|
|
8
10
|
entity: str,
|
|
9
11
|
name: str | None = None,
|
|
10
12
|
uid: str | None = None,
|
|
11
|
-
|
|
13
|
+
key: str | None = None,
|
|
12
14
|
permanent: bool | None = None,
|
|
13
15
|
force: bool = False,
|
|
14
16
|
):
|
|
@@ -24,21 +26,60 @@ def delete(
|
|
|
24
26
|
|
|
25
27
|
Branch.get(name=name).delete(permanent=permanent)
|
|
26
28
|
elif entity == "artifact":
|
|
27
|
-
assert uid is not None
|
|
29
|
+
assert uid is not None or key is not None, (
|
|
30
|
+
"You have to pass a uid or key for deleting an artifact."
|
|
31
|
+
)
|
|
28
32
|
from lamindb import Artifact
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
if key is not None:
|
|
35
|
+
record = Artifact.objects.filter(key=key).order_by("-created_at").first()
|
|
36
|
+
if record is None:
|
|
37
|
+
raise SystemExit(f"Artifact with key={key} does not exist.")
|
|
38
|
+
else:
|
|
39
|
+
record = Artifact.get(uid)
|
|
40
|
+
record.delete(permanent=permanent)
|
|
31
41
|
elif entity == "transform":
|
|
32
|
-
assert uid is not None
|
|
42
|
+
assert uid is not None or key is not None, (
|
|
43
|
+
"You have to pass a uid or key for deleting a transform."
|
|
44
|
+
)
|
|
33
45
|
from lamindb import Transform
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
if key is not None:
|
|
48
|
+
record = Transform.objects.filter(key=key).order_by("-created_at").first()
|
|
49
|
+
if record is None:
|
|
50
|
+
raise SystemExit(f"Transform with key={key} does not exist.")
|
|
51
|
+
else:
|
|
52
|
+
record = Transform.get(uid)
|
|
53
|
+
record.delete(permanent=permanent)
|
|
36
54
|
elif entity == "collection":
|
|
37
|
-
assert uid is not None
|
|
55
|
+
assert uid is not None or key is not None, (
|
|
56
|
+
"You have to pass a uid or key for deleting a collection."
|
|
57
|
+
)
|
|
38
58
|
from lamindb import Collection
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
if key is not None:
|
|
61
|
+
record = Collection.objects.filter(key=key).order_by("-created_at").first()
|
|
62
|
+
if record is None:
|
|
63
|
+
raise SystemExit(f"Collection with key={key} does not exist.")
|
|
64
|
+
else:
|
|
65
|
+
record = Collection.get(uid)
|
|
66
|
+
record.delete(permanent=permanent)
|
|
67
|
+
elif entity == "record":
|
|
68
|
+
assert uid is not None or name is not None, (
|
|
69
|
+
"You have to pass a uid or name for deleting a record."
|
|
70
|
+
)
|
|
71
|
+
from lamindb import Record
|
|
72
|
+
|
|
73
|
+
if name is not None:
|
|
74
|
+
record = Record.objects.get(name=name)
|
|
75
|
+
if record is None:
|
|
76
|
+
raise SystemExit(f"Record with name={name} does not exist.")
|
|
77
|
+
else:
|
|
78
|
+
record = Record.get(uid)
|
|
79
|
+
record.delete(permanent=permanent)
|
|
80
|
+
else:
|
|
81
|
+
# could introduce "db" as an entity
|
|
82
|
+
try:
|
|
83
|
+
return delete_instance(entity, force=force)
|
|
84
|
+
except StorageNotEmpty as e:
|
|
85
|
+
raise click.ClickException(str(e)) from e
|
lamin_cli/_io.py
CHANGED
|
@@ -30,7 +30,8 @@ def io():
|
|
|
30
30
|
@click.option("--track/--no-track", is_flag=True, help="Whether to track snapshot generation.", default=True)
|
|
31
31
|
# fmt: on
|
|
32
32
|
def snapshot(upload: bool, track: bool) -> None:
|
|
33
|
-
"""Create
|
|
33
|
+
"""Create a SQLite snapshot of the connected instance."""
|
|
34
|
+
from lamindb_setup.io import export_db
|
|
34
35
|
if not ln_setup.settings._instance_exists:
|
|
35
36
|
raise click.ClickException(
|
|
36
37
|
"Not connected to an instance. Please run: lamin connect account/name"
|
|
@@ -52,8 +53,8 @@ def snapshot(upload: bool, track: bool) -> None:
|
|
|
52
53
|
|
|
53
54
|
with tempfile.TemporaryDirectory() as export_dir:
|
|
54
55
|
if track:
|
|
55
|
-
ln.track()
|
|
56
|
-
|
|
56
|
+
ln.track("o39ljgTzvFew", key="__lamin_io_snapshot__.py")
|
|
57
|
+
export_db(module_names=modules_complete, output_dir=export_dir)
|
|
57
58
|
if track:
|
|
58
59
|
ln.finish()
|
|
59
60
|
|
|
@@ -86,8 +87,8 @@ def snapshot(upload: bool, track: bool) -> None:
|
|
|
86
87
|
for table, (orig, clone) in mismatches.items()]
|
|
87
88
|
)
|
|
88
89
|
raise click.ClickException(error_msg)
|
|
89
|
-
except json.JSONDecodeError:
|
|
90
|
-
|
|
90
|
+
except (json.JSONDecodeError, AttributeError, ValueError, TypeError):
|
|
91
|
+
raise click.ClickException(f"Clone verification failed:\n{result.stderr}") from None
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
ln_setup.connect(f"{instance_owner}/{instance_name}", use_root_db_user=True)
|
|
@@ -109,13 +110,14 @@ def snapshot(upload: bool, track: bool) -> None:
|
|
|
109
110
|
# fmt: on
|
|
110
111
|
def exportdb(modules: str | None, output_dir: str, max_workers: int, chunk_size: int):
|
|
111
112
|
"""Export registry tables to parquet files."""
|
|
113
|
+
from lamindb_setup.io import export_db
|
|
112
114
|
if not ln_setup.settings._instance_exists:
|
|
113
115
|
raise click.ClickException(
|
|
114
116
|
"Not connected to an instance. Please run: lamin connect account/name"
|
|
115
117
|
)
|
|
116
118
|
|
|
117
119
|
module_list = modules.split(",") if modules else None
|
|
118
|
-
|
|
120
|
+
export_db(
|
|
119
121
|
module_names=module_list,
|
|
120
122
|
output_dir=output_dir,
|
|
121
123
|
max_workers=max_workers,
|
|
@@ -131,13 +133,14 @@ def exportdb(modules: str | None, output_dir: str, max_workers: int, chunk_size:
|
|
|
131
133
|
# fmt: on
|
|
132
134
|
def importdb(modules: str | None, input_dir: str, if_exists: str):
|
|
133
135
|
"""Import registry tables from parquet files."""
|
|
136
|
+
from lamindb_setup.io import import_db
|
|
134
137
|
if not ln_setup.settings._instance_exists:
|
|
135
138
|
raise click.ClickException(
|
|
136
139
|
"Not connected to an instance. Please run: lamin connect account/name"
|
|
137
140
|
)
|
|
138
141
|
|
|
139
142
|
module_list = modules.split(",") if modules else None
|
|
140
|
-
|
|
143
|
+
import_db(
|
|
141
144
|
module_names=module_list,
|
|
142
145
|
input_dir=input_dir,
|
|
143
146
|
if_exists=if_exists,
|
lamin_cli/_load.py
CHANGED
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
from lamin_utils import logger
|
|
8
8
|
|
|
9
|
+
from ._context import get_current_run_file
|
|
9
10
|
from ._save import infer_registry_from_path, parse_title_r_notebook
|
|
10
11
|
from .urls import decompose_url
|
|
11
12
|
|
|
@@ -48,6 +49,10 @@ def load(
|
|
|
48
49
|
ln_setup.connect(instance)
|
|
49
50
|
import lamindb as ln
|
|
50
51
|
|
|
52
|
+
current_run = None
|
|
53
|
+
if get_current_run_file().exists():
|
|
54
|
+
current_run = ln.Run.get(uid=get_current_run_file().read_text().strip())
|
|
55
|
+
|
|
51
56
|
def script_to_notebook(
|
|
52
57
|
transform: ln.Transform, notebook_path: Path, bump_revision: bool = False
|
|
53
58
|
) -> None:
|
|
@@ -182,7 +187,7 @@ def load(
|
|
|
182
187
|
entities = entities.order_by("-created_at")
|
|
183
188
|
|
|
184
189
|
entity_obj = entities.first()
|
|
185
|
-
cache_path = entity_obj.cache()
|
|
190
|
+
cache_path = entity_obj.cache(is_run_input=current_run)
|
|
186
191
|
|
|
187
192
|
# collection gives us a list of paths
|
|
188
193
|
if isinstance(cache_path, list):
|
lamin_cli/_save.py
CHANGED
|
@@ -9,17 +9,23 @@ import lamindb_setup as ln_setup
|
|
|
9
9
|
from lamin_utils import logger
|
|
10
10
|
from lamindb_setup.core.hashing import hash_file
|
|
11
11
|
|
|
12
|
+
from lamin_cli._context import get_current_run_file
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def infer_registry_from_path(path: Path | str) -> str:
|
|
14
16
|
suffixes_transform = {
|
|
15
17
|
"py": {".py", ".ipynb"},
|
|
16
18
|
"R": {".R", ".qmd", ".Rmd"},
|
|
19
|
+
"sh": {".sh"},
|
|
17
20
|
}
|
|
18
21
|
if isinstance(path, str):
|
|
19
22
|
path = Path(path)
|
|
20
23
|
registry = (
|
|
21
24
|
"transform"
|
|
22
|
-
if path.suffix
|
|
25
|
+
if path.suffix
|
|
26
|
+
in suffixes_transform["py"]
|
|
27
|
+
.union(suffixes_transform["R"])
|
|
28
|
+
.union(suffixes_transform["sh"])
|
|
23
29
|
else "artifact"
|
|
24
30
|
)
|
|
25
31
|
return registry
|
|
@@ -42,9 +48,11 @@ def parse_uid_from_code(content: str, suffix: str) -> str | None:
|
|
|
42
48
|
r'track\(\s*(?:transform\s*=\s*)?([\'"])([a-zA-Z0-9]{12,16})\1'
|
|
43
49
|
)
|
|
44
50
|
uid_pattern = None
|
|
51
|
+
elif suffix == ".sh":
|
|
52
|
+
return None
|
|
45
53
|
else:
|
|
46
|
-
raise
|
|
47
|
-
"Only .py, .ipynb, .R, .qmd, .Rmd files are supported for saving"
|
|
54
|
+
raise click.ClickException(
|
|
55
|
+
"Only .py, .ipynb, .R, .qmd, .Rmd, .sh files are supported for saving"
|
|
48
56
|
" transforms."
|
|
49
57
|
)
|
|
50
58
|
|
|
@@ -82,8 +90,13 @@ def save(
|
|
|
82
90
|
) -> str | None:
|
|
83
91
|
import lamindb as ln
|
|
84
92
|
from lamindb._finish import save_context_core
|
|
93
|
+
from lamindb_setup.core._settings_store import settings_dir
|
|
85
94
|
from lamindb_setup.core.upath import LocalPathClasses, UPath, create_path
|
|
86
95
|
|
|
96
|
+
current_run = None
|
|
97
|
+
if get_current_run_file().exists():
|
|
98
|
+
current_run = ln.Run.get(uid=get_current_run_file().read_text().strip())
|
|
99
|
+
|
|
87
100
|
# this allows to have the correct treatment of credentials in case of cloud paths
|
|
88
101
|
ppath = create_path(path)
|
|
89
102
|
# isinstance is needed to cast the type of path to UPath
|
|
@@ -100,14 +113,14 @@ def save(
|
|
|
100
113
|
ln.Q(name=project) | ln.Q(uid=project)
|
|
101
114
|
).one_or_none()
|
|
102
115
|
if project_record is None:
|
|
103
|
-
raise
|
|
116
|
+
raise click.ClickException(
|
|
104
117
|
f"Project '{project}' not found, either create it with `ln.Project(name='...').save()` or fix typos."
|
|
105
118
|
)
|
|
106
119
|
space_record = None
|
|
107
120
|
if space is not None:
|
|
108
121
|
space_record = ln.Space.filter(ln.Q(name=space) | ln.Q(uid=space)).one_or_none()
|
|
109
122
|
if space_record is None:
|
|
110
|
-
raise
|
|
123
|
+
raise click.ClickException(
|
|
111
124
|
f"Space '{space}' not found, either create it on LaminHub or fix typos."
|
|
112
125
|
)
|
|
113
126
|
branch_record = None
|
|
@@ -116,7 +129,7 @@ def save(
|
|
|
116
129
|
ln.Q(name=branch) | ln.Q(uid=branch)
|
|
117
130
|
).one_or_none()
|
|
118
131
|
if branch_record is None:
|
|
119
|
-
raise
|
|
132
|
+
raise click.ClickException(
|
|
120
133
|
f"Branch '{branch}' not found, either create it with `ln.Branch(name='...').save()` or fix typos."
|
|
121
134
|
)
|
|
122
135
|
|
|
@@ -132,7 +145,7 @@ def save(
|
|
|
132
145
|
.first()
|
|
133
146
|
)
|
|
134
147
|
if revises is None:
|
|
135
|
-
raise
|
|
148
|
+
raise click.ClickException("The stem uid is not found.")
|
|
136
149
|
|
|
137
150
|
if is_cloud_path:
|
|
138
151
|
if key is not None:
|
|
@@ -149,6 +162,7 @@ def save(
|
|
|
149
162
|
revises=revises,
|
|
150
163
|
branch=branch_record,
|
|
151
164
|
space=space_record,
|
|
165
|
+
run=current_run,
|
|
152
166
|
).save()
|
|
153
167
|
logger.important(f"saved: {artifact}")
|
|
154
168
|
logger.important(f"storage path: {artifact.path}")
|
|
@@ -219,7 +233,7 @@ def save(
|
|
|
219
233
|
if transform is None:
|
|
220
234
|
uid = f"{stem_uid}0000"
|
|
221
235
|
else:
|
|
222
|
-
transform_hash, _ = hash_file(ppath)
|
|
236
|
+
_, transform_hash, _ = hash_file(ppath)
|
|
223
237
|
transform = ln.Transform.filter(hash=transform_hash).first()
|
|
224
238
|
if transform is not None and transform.hash is not None:
|
|
225
239
|
if transform.hash == transform_hash:
|
|
@@ -252,7 +266,30 @@ def save(
|
|
|
252
266
|
.first()
|
|
253
267
|
)
|
|
254
268
|
if revises is None:
|
|
255
|
-
|
|
269
|
+
# check if the transform is on other branches
|
|
270
|
+
revises_other_branches = (
|
|
271
|
+
ln.Transform.filter(uid__startswith=stem_uid, branch=None)
|
|
272
|
+
.order_by("-created_at")
|
|
273
|
+
.first()
|
|
274
|
+
)
|
|
275
|
+
if revises_other_branches is not None:
|
|
276
|
+
if revises_other_branches.branch_id == -1:
|
|
277
|
+
raise click.ClickException(
|
|
278
|
+
"Transform is in the trash, please restore it before running `lamin save`!"
|
|
279
|
+
)
|
|
280
|
+
elif revises_other_branches.branch_id == 0:
|
|
281
|
+
raise click.ClickException(
|
|
282
|
+
"Transform is in the archive, please restore it before running `lamin save`!"
|
|
283
|
+
)
|
|
284
|
+
elif (
|
|
285
|
+
revises_other_branches.branch_id != ln_setup.settings.branch.id
|
|
286
|
+
):
|
|
287
|
+
raise click.ClickException(
|
|
288
|
+
"Transform is on a different branch"
|
|
289
|
+
f"({revises_other_branches.branch.name}), please switch to the correct branch"
|
|
290
|
+
" before running `lamin save`!"
|
|
291
|
+
)
|
|
292
|
+
raise click.ClickException("The stem uid is not found.")
|
|
256
293
|
if transform is None:
|
|
257
294
|
if ppath.suffix == ".ipynb":
|
|
258
295
|
from nbproject.dev import read_notebook
|
|
@@ -268,7 +305,7 @@ def save(
|
|
|
268
305
|
uid=uid,
|
|
269
306
|
description=description,
|
|
270
307
|
key=key,
|
|
271
|
-
type="script" if ppath.suffix in {".R", ".py"} else "notebook",
|
|
308
|
+
type="script" if ppath.suffix in {".R", ".py", ".sh"} else "notebook",
|
|
272
309
|
revises=revises,
|
|
273
310
|
)
|
|
274
311
|
if space is not None:
|
|
@@ -308,4 +345,6 @@ def save(
|
|
|
308
345
|
)
|
|
309
346
|
return return_code
|
|
310
347
|
else:
|
|
311
|
-
raise
|
|
348
|
+
raise click.ClickException(
|
|
349
|
+
"Allowed values for '--registry' are: 'artifact', 'transform'"
|
|
350
|
+
)
|
lamin_cli/_settings.py
CHANGED
|
@@ -12,35 +12,38 @@ else:
|
|
|
12
12
|
@click.group(invoke_without_command=True)
|
|
13
13
|
@click.pass_context
|
|
14
14
|
def settings(ctx):
|
|
15
|
-
"""Manage settings.
|
|
15
|
+
"""Manage development & cache directories, branch, and space settings.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Get or set a setting by name:
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
- `dev-dir` → development directory {attr}`~lamindb.setup.core.SetupSettings.dev_dir`
|
|
20
|
+
- `cache-dir` → cache directory {attr}`~lamindb.setup.core.SetupSettings.cache_dir`
|
|
21
|
+
- `branch` → branch {attr}`~lamindb.setup.core.SetupSettings.branch`
|
|
22
|
+
- `space` → space {attr}`~lamindb.setup.core.SetupSettings.space`
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Display via [lamin info](https://docs.lamin.ai/cli#info)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
- `private-django-api` → {attr}`~lamindb.setup.core.SetupSettings.private_django_api`
|
|
27
|
-
- `branch` → current branch (use `lamin switch --branch` to change)
|
|
28
|
-
- `space` → current space (use `lamin switch --space` to change)
|
|
29
|
-
|
|
30
|
-
Examples for getting a setting:
|
|
26
|
+
Examples:
|
|
31
27
|
|
|
32
28
|
```
|
|
33
|
-
|
|
34
|
-
lamin settings get
|
|
29
|
+
# dev-dir
|
|
30
|
+
lamin settings dev-dir get
|
|
31
|
+
lamin settings dev-dir set . # set to current directory
|
|
32
|
+
lamin settings dev-dir set ~/my-project
|
|
33
|
+
lamin settings dev-dir unset
|
|
34
|
+
# cache-dir
|
|
35
|
+
lamin settings cache-dir get
|
|
36
|
+
lamin settings cache-dir set /path/to/cache
|
|
37
|
+
lamin settings cache-dir clear
|
|
38
|
+
# branch
|
|
39
|
+
lamin settings branch get
|
|
40
|
+
lamin settings branch set main
|
|
41
|
+
# space
|
|
42
|
+
lamin settings space get
|
|
43
|
+
lamin settings space set all
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
lamin settings set dev-dir . # set dev-dir to current directory
|
|
41
|
-
lamin settings set dev-dir ~/my-project # set dev-dir to ~/my-project
|
|
42
|
-
lamin settings set dev-dir none # unset dev-dir
|
|
43
|
-
```
|
|
46
|
+
→ Python/R alternative: {attr}`~lamindb.setup.core.SetupSettings.dev_dir`, {attr}`~lamindb.setup.core.SetupSettings.cache_dir`, {attr}`~lamindb.setup.core.SetupSettings.branch`, and {attr}`~lamindb.setup.core.SetupSettings.space`
|
|
44
47
|
"""
|
|
45
48
|
if ctx.invoked_subcommand is None:
|
|
46
49
|
from lamindb_setup import settings as settings_
|
|
@@ -49,7 +52,53 @@ def settings(ctx):
|
|
|
49
52
|
click.echo(settings_)
|
|
50
53
|
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
# -----------------------------------------------------------------------------
|
|
56
|
+
# dev-dir group (pattern: lamin settings dev-dir get/set)
|
|
57
|
+
# -----------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@click.group("dev-dir")
|
|
61
|
+
def dev_dir_group():
|
|
62
|
+
"""Get or set the development directory."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dev_dir_group.command("get")
|
|
66
|
+
def dev_dir_get():
|
|
67
|
+
"""Show the current development directory."""
|
|
68
|
+
from lamindb_setup import settings as settings_
|
|
69
|
+
|
|
70
|
+
value = settings_.dev_dir
|
|
71
|
+
click.echo(value if value is not None else "None")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dev_dir_group.command("set")
|
|
75
|
+
@click.argument("value", type=str)
|
|
76
|
+
def dev_dir_set(value: str):
|
|
77
|
+
"""Set the development directory."""
|
|
78
|
+
from lamindb_setup import settings as settings_
|
|
79
|
+
|
|
80
|
+
if value.lower() == "none":
|
|
81
|
+
value = None # type: ignore[assignment]
|
|
82
|
+
settings_.dev_dir = value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dev_dir_group.command("unset")
|
|
86
|
+
def dev_dir_unset():
|
|
87
|
+
"""Unset the development directory."""
|
|
88
|
+
from lamindb_setup import settings as settings_
|
|
89
|
+
|
|
90
|
+
settings_.dev_dir = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
settings.add_command(dev_dir_group)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# -----------------------------------------------------------------------------
|
|
97
|
+
# Legacy get/set (hidden, backward compatibility)
|
|
98
|
+
# -----------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@settings.command("set", hidden=True)
|
|
53
102
|
@click.argument(
|
|
54
103
|
"setting",
|
|
55
104
|
type=click.Choice(
|
|
@@ -57,8 +106,8 @@ def settings(ctx):
|
|
|
57
106
|
),
|
|
58
107
|
)
|
|
59
108
|
@click.argument("value") # No explicit type - let Click handle it
|
|
60
|
-
def
|
|
61
|
-
"""Set a setting."""
|
|
109
|
+
def set_legacy(setting: str, value: str):
|
|
110
|
+
"""Set a setting (legacy). Use lamin settings <name> set <value> instead."""
|
|
62
111
|
from lamindb_setup import settings as settings_
|
|
63
112
|
|
|
64
113
|
if setting == "auto-connect":
|
|
@@ -71,7 +120,7 @@ def set(setting: str, value: str):
|
|
|
71
120
|
settings_.dev_dir = value
|
|
72
121
|
|
|
73
122
|
|
|
74
|
-
@settings.command("get")
|
|
123
|
+
@settings.command("get", hidden=True)
|
|
75
124
|
@click.argument(
|
|
76
125
|
"setting",
|
|
77
126
|
type=click.Choice(
|
|
@@ -79,8 +128,8 @@ def set(setting: str, value: str):
|
|
|
79
128
|
case_sensitive=False,
|
|
80
129
|
),
|
|
81
130
|
)
|
|
82
|
-
def
|
|
83
|
-
"""Get a setting."""
|
|
131
|
+
def get_legacy(setting: str):
|
|
132
|
+
"""Get a setting (legacy). Use lamin settings <name> get instead."""
|
|
84
133
|
from lamindb_setup import settings as settings_
|
|
85
134
|
|
|
86
135
|
if setting == "branch":
|
|
@@ -94,3 +143,12 @@ def get(setting: str):
|
|
|
94
143
|
else:
|
|
95
144
|
value = getattr(settings_, setting.replace("-", "_"))
|
|
96
145
|
click.echo(value)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# -----------------------------------------------------------------------------
|
|
149
|
+
# cache-dir (already uses lamin settings cache-dir get/set/clear)
|
|
150
|
+
# -----------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
from lamin_cli._cache import cache
|
|
153
|
+
|
|
154
|
+
settings.add_command(cache, "cache-dir")
|
|
@@ -6,6 +6,8 @@ from pathlib import Path
|
|
|
6
6
|
import lamindb_setup as ln_setup
|
|
7
7
|
|
|
8
8
|
if __name__ == "__main__":
|
|
9
|
+
from lamindb_setup.io import import_db
|
|
10
|
+
|
|
9
11
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
10
12
|
|
|
11
13
|
parser = argparse.ArgumentParser()
|
|
@@ -25,7 +27,7 @@ if __name__ == "__main__":
|
|
|
25
27
|
storage=f"{instance_name}-clone", modules=f"{','.join(modules_without_lamindb)}"
|
|
26
28
|
)
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
import_db(
|
|
29
31
|
module_names=list(modules_complete), input_dir=export_dir, if_exists="replace"
|
|
30
32
|
)
|
|
31
33
|
|
lamin_cli/compute/modal.py
CHANGED
|
@@ -157,11 +157,11 @@ class Runner:
|
|
|
157
157
|
) -> modal.Image:
|
|
158
158
|
if env_variables is None:
|
|
159
159
|
env_variables = {}
|
|
160
|
+
base_packages = ["lamindb", "httpx_retries"]
|
|
160
161
|
if packages is None:
|
|
161
|
-
packages =
|
|
162
|
+
packages = base_packages
|
|
162
163
|
else:
|
|
163
|
-
packages
|
|
164
|
-
|
|
164
|
+
packages += base_packages
|
|
165
165
|
if image_url is None:
|
|
166
166
|
image = modal.Image.debian_slim(python_version=python_version)
|
|
167
167
|
else:
|
|
@@ -170,6 +170,5 @@ class Runner:
|
|
|
170
170
|
image.pip_install(packages)
|
|
171
171
|
.env(env_variables)
|
|
172
172
|
.add_local_python_source("lamindb", "lamindb_setup", copy=True)
|
|
173
|
-
.run_commands("lamin settings set auto-connect true")
|
|
174
173
|
.add_local_dir(local_dir, remote_dir)
|
|
175
174
|
)
|
lamin_cli/urls.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
def decompose_url(url: str) -> tuple[str, str, str]:
|
|
2
|
-
assert any(
|
|
3
|
-
|
|
2
|
+
assert any(
|
|
3
|
+
keyword in url for keyword in ["transform", "artifact", "collection", "record"]
|
|
4
|
+
)
|
|
5
|
+
for entity in ["transform", "artifact", "collection", "record"]:
|
|
4
6
|
if entity in url:
|
|
5
7
|
break
|
|
6
8
|
uid = url.split(f"{entity}/")[1]
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: lamin_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
4
4
|
Summary: Lamin CLI.
|
|
5
5
|
Author-email: Lamin Labs <open-source@lamin.ai>
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
-
License-File: LICENSE
|
|
8
7
|
Requires-Dist: rich-click>=1.7
|
|
9
8
|
Project-URL: Home, https://github.com/laminlabs/lamin-cli
|
|
10
9
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
lamin_cli/__init__.py,sha256=IJ_B3QJrPMYc1SfgQQ1WgVnE2dEZOf8wSmQIGZhAauw,605
|
|
2
|
+
lamin_cli/__main__.py,sha256=HkZh79zC2mrdmAtpib1uE83T_E5pRUuYrYXqgfrmY8w,27931
|
|
3
|
+
lamin_cli/_annotate.py,sha256=ZD76__K-mQt7UpYqyM1I2lKCs-DraTmnkjsByIHmD-g,1839
|
|
4
|
+
lamin_cli/_cache.py,sha256=SvfgC0pIpsjIGUYm21oPUfE_RCOG2_b0cvDscLtzbYo,826
|
|
5
|
+
lamin_cli/_context.py,sha256=JLhv98isasX_M_o6tIDkeBWcNH8ryRrl3nk2rmwsuD4,2392
|
|
6
|
+
lamin_cli/_delete.py,sha256=TQV-CJOV3tYBGm6vCj0JKymptrXWhBvJ8CZPfDVyzAw,3061
|
|
7
|
+
lamin_cli/_io.py,sha256=HPFpASrTa3zLsIXmTpZ69l34t3ZoYIa_SNHOclJnd-o,5175
|
|
8
|
+
lamin_cli/_load.py,sha256=v7PCB7sYqGs6jF186aN191lTV2Mr5jdayHKSmdgsw_k,8445
|
|
9
|
+
lamin_cli/_migration.py,sha256=w-TLYC2q6sqcfNtIUewqRW_69_xy_71X3eSV7Ud58jk,1239
|
|
10
|
+
lamin_cli/_save.py,sha256=PiBFlTgPMPdFg4eMqRPYOiF4rl7R6LA3e3RTpZc_gaM,13806
|
|
11
|
+
lamin_cli/_settings.py,sha256=vuKZeEToEh0pb_R4ECzWpe6FeJgmIACLEupPYxQ60k4,4781
|
|
12
|
+
lamin_cli/urls.py,sha256=6yTBFByrzdWIntFjupC5Dt8hYkl4agR31D4g1zjzPK8,401
|
|
13
|
+
lamin_cli/clone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
lamin_cli/clone/_clone_verification.py,sha256=8hRFZt_1Z0i2wMqKBkrioq01RJEjwHy9cYmw6LV4PXI,1835
|
|
15
|
+
lamin_cli/clone/create_sqlite_clone_and_import_db.py,sha256=m4Lzm1UvqNjYR2WKE0u_g-RrKv77pdVwHyFOKdR0exQ,1576
|
|
16
|
+
lamin_cli/compute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
lamin_cli/compute/modal.py,sha256=-QBnqxjI8AJDjkF7-Q30o4m3EpSeJ2_n5wYxedaPiLA,5955
|
|
18
|
+
lamin_cli-1.12.0.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
19
|
+
lamin_cli-1.12.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
20
|
+
lamin_cli-1.12.0.dist-info/WHEEL,sha256=ssQ84EZ5gH1pCOujd3iW7HClo_O_aDaClUbX4B8bjKY,100
|
|
21
|
+
lamin_cli-1.12.0.dist-info/METADATA,sha256=JN15Pvzdjb2Eshdqrp5lh6NM-kkmccfy-STokqEFNGY,338
|
|
22
|
+
lamin_cli-1.12.0.dist-info/RECORD,,
|