lamin_cli 1.11.0__py2.py3-none-any.whl → 1.12.1__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 +27 -27
- lamin_cli/__main__.py +826 -625
- lamin_cli/_annotate.py +47 -47
- lamin_cli/_cache.py +49 -41
- lamin_cli/_context.py +76 -76
- lamin_cli/_delete.py +85 -44
- lamin_cli/_io.py +147 -144
- lamin_cli/_load.py +203 -203
- lamin_cli/_migration.py +50 -50
- lamin_cli/_save.py +350 -325
- lamin_cli/_settings.py +154 -96
- lamin_cli/clone/_clone_verification.py +56 -56
- lamin_cli/clone/create_sqlite_clone_and_import_db.py +53 -51
- lamin_cli/compute/modal.py +174 -175
- lamin_cli/urls.py +10 -8
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info}/METADATA +3 -2
- lamin_cli-1.12.1.dist-info/RECORD +22 -0
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info}/WHEEL +1 -1
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info/licenses}/LICENSE +201 -201
- lamin_cli-1.11.0.dist-info/RECORD +0 -22
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info}/entry_points.txt +0 -0
lamin_cli/_io.py
CHANGED
|
@@ -1,144 +1,147 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import subprocess
|
|
6
|
-
import sys
|
|
7
|
-
import tempfile
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
import lamindb_setup as ln_setup
|
|
11
|
-
|
|
12
|
-
from lamin_cli.clone._clone_verification import (
|
|
13
|
-
_count_instance_records,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
if os.environ.get("NO_RICH"):
|
|
17
|
-
import click as click
|
|
18
|
-
else:
|
|
19
|
-
import rich_click as click
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@click.group()
|
|
23
|
-
def io():
|
|
24
|
-
"""Import and export instances."""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# fmt: off
|
|
28
|
-
@io.command("snapshot")
|
|
29
|
-
@click.option("--upload/--no-upload", is_flag=True, help="Whether to upload the snapshot.", default=True)
|
|
30
|
-
@click.option("--track/--no-track", is_flag=True, help="Whether to track snapshot generation.", default=True)
|
|
31
|
-
# fmt: on
|
|
32
|
-
def snapshot(upload: bool, track: bool) -> None:
|
|
33
|
-
"""Create a SQLite snapshot of the connected instance."""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
modules_complete.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@
|
|
106
|
-
@click.option("--
|
|
107
|
-
@click.option("--
|
|
108
|
-
@click.option("--
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@
|
|
130
|
-
@click.option("--
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import lamindb_setup as ln_setup
|
|
11
|
+
|
|
12
|
+
from lamin_cli.clone._clone_verification import (
|
|
13
|
+
_count_instance_records,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if os.environ.get("NO_RICH"):
|
|
17
|
+
import click as click
|
|
18
|
+
else:
|
|
19
|
+
import rich_click as click
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group()
|
|
23
|
+
def io():
|
|
24
|
+
"""Import and export instances."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# fmt: off
|
|
28
|
+
@io.command("snapshot")
|
|
29
|
+
@click.option("--upload/--no-upload", is_flag=True, help="Whether to upload the snapshot.", default=True)
|
|
30
|
+
@click.option("--track/--no-track", is_flag=True, help="Whether to track snapshot generation.", default=True)
|
|
31
|
+
# fmt: on
|
|
32
|
+
def snapshot(upload: bool, track: bool) -> None:
|
|
33
|
+
"""Create a SQLite snapshot of the connected instance."""
|
|
34
|
+
from lamindb_setup.io import export_db
|
|
35
|
+
if not ln_setup.settings._instance_exists:
|
|
36
|
+
raise click.ClickException(
|
|
37
|
+
"Not connected to an instance. Please run: lamin connect account/name"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
instance_owner = ln_setup.settings.instance.owner
|
|
41
|
+
instance_name = ln_setup.settings.instance.name
|
|
42
|
+
|
|
43
|
+
ln_setup.connect(f"{instance_owner}/{instance_name}", use_root_db_user=True)
|
|
44
|
+
|
|
45
|
+
import lamindb as ln
|
|
46
|
+
|
|
47
|
+
original_counts = _count_instance_records()
|
|
48
|
+
|
|
49
|
+
modules_without_lamindb = ln_setup.settings.instance.modules
|
|
50
|
+
modules_complete = modules_without_lamindb.copy()
|
|
51
|
+
modules_complete.add("lamindb")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
with tempfile.TemporaryDirectory() as export_dir:
|
|
55
|
+
if track:
|
|
56
|
+
ln.track("o39ljgTzvFew", key="__lamin_io_snapshot__.py")
|
|
57
|
+
export_db(module_names=modules_complete, output_dir=export_dir)
|
|
58
|
+
if track:
|
|
59
|
+
ln.finish()
|
|
60
|
+
|
|
61
|
+
script_path = (
|
|
62
|
+
Path(__file__).parent / "clone" / "create_sqlite_clone_and_import_db.py"
|
|
63
|
+
)
|
|
64
|
+
result = subprocess.run(
|
|
65
|
+
[
|
|
66
|
+
sys.executable,
|
|
67
|
+
str(script_path),
|
|
68
|
+
"--instance-name",
|
|
69
|
+
instance_name,
|
|
70
|
+
"--export-dir",
|
|
71
|
+
export_dir,
|
|
72
|
+
"--modules",
|
|
73
|
+
",".join(modules_without_lamindb),
|
|
74
|
+
"--original-counts",
|
|
75
|
+
json.dumps(original_counts),
|
|
76
|
+
],
|
|
77
|
+
check=False,
|
|
78
|
+
stderr=subprocess.PIPE,
|
|
79
|
+
text=True,
|
|
80
|
+
cwd=Path.cwd(),
|
|
81
|
+
)
|
|
82
|
+
if result.returncode != 0:
|
|
83
|
+
try:
|
|
84
|
+
mismatches = json.loads(result.stderr.strip())
|
|
85
|
+
error_msg = "Record count mismatch detected:\n" + "\n".join(
|
|
86
|
+
[f" {table}: original={orig}, clone={clone}"
|
|
87
|
+
for table, (orig, clone) in mismatches.items()]
|
|
88
|
+
)
|
|
89
|
+
raise click.ClickException(error_msg)
|
|
90
|
+
except (json.JSONDecodeError, AttributeError, ValueError, TypeError):
|
|
91
|
+
raise click.ClickException(f"Clone verification failed:\n{result.stderr}") from None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
ln_setup.connect(f"{instance_owner}/{instance_name}", use_root_db_user=True)
|
|
95
|
+
if upload:
|
|
96
|
+
ln_setup.core._clone.upload_sqlite_clone(
|
|
97
|
+
local_sqlite_path=f"{instance_name}-clone/.lamindb/lamin.db",
|
|
98
|
+
compress=True,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
ln_setup.disconnect()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# fmt: off
|
|
105
|
+
@io.command("exportdb")
|
|
106
|
+
@click.option("--modules", type=str, default=None, help="Comma-separated list of modules to export (e.g., 'lamindb,bionty').",)
|
|
107
|
+
@click.option("--output-dir", type=str, help="Output directory for exported parquet files.")
|
|
108
|
+
@click.option("--max-workers", type=int, default=8, help="Number of parallel workers.")
|
|
109
|
+
@click.option("--chunk-size", type=int, default=500_000, help="Number of rows per chunk for large tables.")
|
|
110
|
+
# fmt: on
|
|
111
|
+
def exportdb(modules: str | None, output_dir: str, max_workers: int, chunk_size: int):
|
|
112
|
+
"""Export registry tables to parquet files."""
|
|
113
|
+
from lamindb_setup.io import export_db
|
|
114
|
+
if not ln_setup.settings._instance_exists:
|
|
115
|
+
raise click.ClickException(
|
|
116
|
+
"Not connected to an instance. Please run: lamin connect account/name"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
module_list = modules.split(",") if modules else None
|
|
120
|
+
export_db(
|
|
121
|
+
module_names=module_list,
|
|
122
|
+
output_dir=output_dir,
|
|
123
|
+
max_workers=max_workers,
|
|
124
|
+
chunk_size=chunk_size,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# fmt: off
|
|
129
|
+
@io.command("importdb")
|
|
130
|
+
@click.option("--modules", type=str, default=None, help="Comma-separated list of modules to import (e.g., 'lamindb,bionty').")
|
|
131
|
+
@click.option("--input-dir", type=str, help="Input directory containing exported parquet files.")
|
|
132
|
+
@click.option("--if-exists", type=click.Choice(["fail", "replace", "append"]), default="replace", help="How to handle existing data.")
|
|
133
|
+
# fmt: on
|
|
134
|
+
def importdb(modules: str | None, input_dir: str, if_exists: str):
|
|
135
|
+
"""Import registry tables from parquet files."""
|
|
136
|
+
from lamindb_setup.io import import_db
|
|
137
|
+
if not ln_setup.settings._instance_exists:
|
|
138
|
+
raise click.ClickException(
|
|
139
|
+
"Not connected to an instance. Please run: lamin connect account/name"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
module_list = modules.split(",") if modules else None
|
|
143
|
+
import_db(
|
|
144
|
+
module_names=module_list,
|
|
145
|
+
input_dir=input_dir,
|
|
146
|
+
if_exists=if_exists,
|
|
147
|
+
)
|