lamin_cli 1.12.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 -826
- lamin_cli/_annotate.py +47 -47
- lamin_cli/_cache.py +49 -41
- lamin_cli/_context.py +76 -76
- lamin_cli/_delete.py +85 -85
- lamin_cli/_io.py +147 -147
- lamin_cli/_load.py +203 -203
- lamin_cli/_migration.py +50 -50
- lamin_cli/_save.py +350 -350
- lamin_cli/_settings.py +154 -154
- lamin_cli/clone/_clone_verification.py +56 -56
- lamin_cli/clone/create_sqlite_clone_and_import_db.py +53 -53
- lamin_cli/compute/modal.py +174 -174
- lamin_cli/urls.py +10 -10
- {lamin_cli-1.12.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.12.0.dist-info → lamin_cli-1.12.1.dist-info}/WHEEL +1 -1
- {lamin_cli-1.12.0.dist-info → lamin_cli-1.12.1.dist-info/licenses}/LICENSE +201 -201
- lamin_cli-1.12.0.dist-info/RECORD +0 -22
- {lamin_cli-1.12.0.dist-info → lamin_cli-1.12.1.dist-info}/entry_points.txt +0 -0
lamin_cli/_load.py
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
import shutil
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from lamin_utils import logger
|
|
8
|
-
|
|
9
|
-
from ._context import get_current_run_file
|
|
10
|
-
from ._save import infer_registry_from_path, parse_title_r_notebook
|
|
11
|
-
from .urls import decompose_url
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def load(
|
|
15
|
-
entity: str | None = None,
|
|
16
|
-
uid: str | None = None,
|
|
17
|
-
key: str | None = None,
|
|
18
|
-
with_env: bool = False,
|
|
19
|
-
):
|
|
20
|
-
"""Load artifact, collection, or transform from LaminDB.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
entity: URL containing 'lamin', or 'artifact', 'collection', or 'transform'
|
|
24
|
-
uid: Unique identifier (prefix matching supported)
|
|
25
|
-
key: Key identifier
|
|
26
|
-
with_env: If True, also load environment requirements file for transforms
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
Path to loaded transform, or None for artifacts/collections
|
|
30
|
-
"""
|
|
31
|
-
import lamindb_setup as ln_setup
|
|
32
|
-
|
|
33
|
-
if entity is None:
|
|
34
|
-
if key is None:
|
|
35
|
-
raise SystemExit("Either entity or key has to be provided.")
|
|
36
|
-
else:
|
|
37
|
-
entity = infer_registry_from_path(key)
|
|
38
|
-
|
|
39
|
-
if entity.startswith("https://") and "lamin" in entity:
|
|
40
|
-
url = entity
|
|
41
|
-
instance, entity, uid = decompose_url(url)
|
|
42
|
-
elif entity not in {"artifact", "transform", "collection"}:
|
|
43
|
-
raise SystemExit(
|
|
44
|
-
"Entity has to be a laminhub URL or 'artifact', 'collection', or 'transform'"
|
|
45
|
-
)
|
|
46
|
-
else:
|
|
47
|
-
instance = ln_setup.settings.instance.slug
|
|
48
|
-
|
|
49
|
-
ln_setup.connect(instance)
|
|
50
|
-
import lamindb as ln
|
|
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
|
-
|
|
56
|
-
def script_to_notebook(
|
|
57
|
-
transform: ln.Transform, notebook_path: Path, bump_revision: bool = False
|
|
58
|
-
) -> None:
|
|
59
|
-
import jupytext
|
|
60
|
-
from lamin_utils._base62 import increment_base62
|
|
61
|
-
|
|
62
|
-
if notebook_path.suffix == ".ipynb":
|
|
63
|
-
# below is backward compat
|
|
64
|
-
if "# # transform.name" in transform.source_code:
|
|
65
|
-
new_content = transform.source_code.replace(
|
|
66
|
-
"# # transform.name", f"# # {transform.description}"
|
|
67
|
-
)
|
|
68
|
-
elif transform.source_code.startswith("# %% [markdown]"):
|
|
69
|
-
source_code_split = transform.source_code.split("\n")
|
|
70
|
-
if source_code_split[1] == "#":
|
|
71
|
-
source_code_split[1] = f"# # {transform.description}"
|
|
72
|
-
new_content = "\n".join(source_code_split)
|
|
73
|
-
else:
|
|
74
|
-
new_content = transform.source_code
|
|
75
|
-
else: # R notebook
|
|
76
|
-
new_content = transform.source_code
|
|
77
|
-
current_title = parse_title_r_notebook(new_content)
|
|
78
|
-
if current_title is not None and current_title != transform.description:
|
|
79
|
-
pattern = r'^(---\n.*?title:\s*)"([^"]*)"(.*?---)'
|
|
80
|
-
replacement = f'\\1"{transform.description}"\\3'
|
|
81
|
-
new_content = re.sub(
|
|
82
|
-
pattern,
|
|
83
|
-
replacement,
|
|
84
|
-
new_content,
|
|
85
|
-
flags=re.DOTALL | re.MULTILINE,
|
|
86
|
-
)
|
|
87
|
-
logger.important(
|
|
88
|
-
f"updated title to match description: {current_title} →"
|
|
89
|
-
f" {transform.description}"
|
|
90
|
-
)
|
|
91
|
-
if bump_revision:
|
|
92
|
-
uid = transform.uid
|
|
93
|
-
if (
|
|
94
|
-
uid in new_content
|
|
95
|
-
): # this only hits if it has the full uid, not for the stem uid
|
|
96
|
-
new_uid = f"{uid[:-4]}{increment_base62(uid[-4:])}"
|
|
97
|
-
new_content = new_content.replace(uid, new_uid)
|
|
98
|
-
logger.important(f"updated uid: {uid} → {new_uid}")
|
|
99
|
-
if notebook_path.suffix == ".ipynb":
|
|
100
|
-
notebook = jupytext.reads(new_content, fmt="py:percent")
|
|
101
|
-
jupytext.write(notebook, notebook_path)
|
|
102
|
-
else:
|
|
103
|
-
notebook_path.write_text(new_content)
|
|
104
|
-
|
|
105
|
-
query_by_uid = uid is not None
|
|
106
|
-
|
|
107
|
-
match entity:
|
|
108
|
-
case "transform":
|
|
109
|
-
if query_by_uid:
|
|
110
|
-
# we don't use .get here because DoesNotExist is hard to catch
|
|
111
|
-
# due to private django API
|
|
112
|
-
# here full uid is not expected anymore as before
|
|
113
|
-
# via ln.Transform.objects.get(uid=uid)
|
|
114
|
-
transforms = ln.Transform.objects.filter(uid__startswith=uid)
|
|
115
|
-
else:
|
|
116
|
-
# if below, we take is_latest=True as the criterion, we might get draft notebooks
|
|
117
|
-
# hence, we use source_code__isnull=False and order by created_at instead
|
|
118
|
-
transforms = ln.Transform.objects.filter(
|
|
119
|
-
key=key, source_code__isnull=False
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
if (n_transforms := len(transforms)) == 0:
|
|
123
|
-
err_msg = f"uid {uid}" if query_by_uid else f"key={key} and source_code"
|
|
124
|
-
raise SystemExit(f"Transform with {err_msg} does not exist.")
|
|
125
|
-
|
|
126
|
-
if n_transforms > 1:
|
|
127
|
-
transforms = transforms.order_by("-created_at")
|
|
128
|
-
transform = transforms.first()
|
|
129
|
-
|
|
130
|
-
target_path = Path(transform.key)
|
|
131
|
-
if ln_setup.settings.dev_dir is not None:
|
|
132
|
-
target_path = ln_setup.settings.dev_dir / target_path
|
|
133
|
-
if len(target_path.parents) > 1:
|
|
134
|
-
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
-
if target_path.exists():
|
|
136
|
-
response = input(f"! {target_path} exists: replace? (y/n)")
|
|
137
|
-
if response != "y":
|
|
138
|
-
raise SystemExit("Aborted.")
|
|
139
|
-
|
|
140
|
-
if transform.source_code is not None:
|
|
141
|
-
if target_path.suffix in (".ipynb", ".Rmd", ".qmd"):
|
|
142
|
-
script_to_notebook(transform, target_path, bump_revision=True)
|
|
143
|
-
else:
|
|
144
|
-
target_path.write_text(transform.source_code)
|
|
145
|
-
else:
|
|
146
|
-
raise SystemExit("No source code available for this transform.")
|
|
147
|
-
|
|
148
|
-
logger.important(f"{transform.type} is here: {target_path}")
|
|
149
|
-
|
|
150
|
-
if with_env:
|
|
151
|
-
ln.settings.track_run_inputs = False
|
|
152
|
-
if (
|
|
153
|
-
transform.latest_run is not None
|
|
154
|
-
and transform.latest_run.environment is not None
|
|
155
|
-
):
|
|
156
|
-
filepath_env_cache = transform.latest_run.environment.cache()
|
|
157
|
-
target_env_filename = (
|
|
158
|
-
target_path.parent / f"{target_path.stem}__requirements.txt"
|
|
159
|
-
)
|
|
160
|
-
shutil.move(filepath_env_cache, target_env_filename)
|
|
161
|
-
logger.important(f"environment is here: {target_env_filename}")
|
|
162
|
-
else:
|
|
163
|
-
logger.warning(
|
|
164
|
-
"latest transform run with environment doesn't exist"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
return target_path
|
|
168
|
-
case "artifact" | "collection":
|
|
169
|
-
ln.settings.track_run_inputs = False
|
|
170
|
-
|
|
171
|
-
EntityClass = ln.Artifact if entity == "artifact" else ln.Collection
|
|
172
|
-
|
|
173
|
-
# we don't use .get here because DoesNotExist is hard to catch due to private django API
|
|
174
|
-
# we use `.objects` here because we don't want to exclude kind = __lamindb_run__ artifacts
|
|
175
|
-
if query_by_uid:
|
|
176
|
-
entities = EntityClass.objects.filter(uid__startswith=uid)
|
|
177
|
-
else:
|
|
178
|
-
entities = EntityClass.objects.filter(key=key)
|
|
179
|
-
|
|
180
|
-
if (n_entities := len(entities)) == 0:
|
|
181
|
-
err_msg = f"uid={uid}" if query_by_uid else f"key={key}"
|
|
182
|
-
raise SystemExit(
|
|
183
|
-
f"{entity.capitalize()} with {err_msg} does not exist."
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
if n_entities > 1:
|
|
187
|
-
entities = entities.order_by("-created_at")
|
|
188
|
-
|
|
189
|
-
entity_obj = entities.first()
|
|
190
|
-
cache_path = entity_obj.cache(is_run_input=current_run)
|
|
191
|
-
|
|
192
|
-
# collection gives us a list of paths
|
|
193
|
-
if isinstance(cache_path, list):
|
|
194
|
-
logger.important(f"{entity} paths ({len(cache_path)} files):")
|
|
195
|
-
for i, path in enumerate(cache_path):
|
|
196
|
-
if i < 5 or i >= len(cache_path) - 5:
|
|
197
|
-
logger.important(f" [{i + 1}/{len(cache_path)}] {path}")
|
|
198
|
-
elif i == 5:
|
|
199
|
-
logger.important(f" ... {len(cache_path) - 10} more files ...")
|
|
200
|
-
else:
|
|
201
|
-
logger.important(f"{entity} is here: {cache_path}")
|
|
202
|
-
case _:
|
|
203
|
-
raise AssertionError(f"unknown entity {entity}")
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from lamin_utils import logger
|
|
8
|
+
|
|
9
|
+
from ._context import get_current_run_file
|
|
10
|
+
from ._save import infer_registry_from_path, parse_title_r_notebook
|
|
11
|
+
from .urls import decompose_url
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load(
|
|
15
|
+
entity: str | None = None,
|
|
16
|
+
uid: str | None = None,
|
|
17
|
+
key: str | None = None,
|
|
18
|
+
with_env: bool = False,
|
|
19
|
+
):
|
|
20
|
+
"""Load artifact, collection, or transform from LaminDB.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
entity: URL containing 'lamin', or 'artifact', 'collection', or 'transform'
|
|
24
|
+
uid: Unique identifier (prefix matching supported)
|
|
25
|
+
key: Key identifier
|
|
26
|
+
with_env: If True, also load environment requirements file for transforms
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Path to loaded transform, or None for artifacts/collections
|
|
30
|
+
"""
|
|
31
|
+
import lamindb_setup as ln_setup
|
|
32
|
+
|
|
33
|
+
if entity is None:
|
|
34
|
+
if key is None:
|
|
35
|
+
raise SystemExit("Either entity or key has to be provided.")
|
|
36
|
+
else:
|
|
37
|
+
entity = infer_registry_from_path(key)
|
|
38
|
+
|
|
39
|
+
if entity.startswith("https://") and "lamin" in entity:
|
|
40
|
+
url = entity
|
|
41
|
+
instance, entity, uid = decompose_url(url)
|
|
42
|
+
elif entity not in {"artifact", "transform", "collection"}:
|
|
43
|
+
raise SystemExit(
|
|
44
|
+
"Entity has to be a laminhub URL or 'artifact', 'collection', or 'transform'"
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
instance = ln_setup.settings.instance.slug
|
|
48
|
+
|
|
49
|
+
ln_setup.connect(instance)
|
|
50
|
+
import lamindb as ln
|
|
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
|
+
|
|
56
|
+
def script_to_notebook(
|
|
57
|
+
transform: ln.Transform, notebook_path: Path, bump_revision: bool = False
|
|
58
|
+
) -> None:
|
|
59
|
+
import jupytext
|
|
60
|
+
from lamin_utils._base62 import increment_base62
|
|
61
|
+
|
|
62
|
+
if notebook_path.suffix == ".ipynb":
|
|
63
|
+
# below is backward compat
|
|
64
|
+
if "# # transform.name" in transform.source_code:
|
|
65
|
+
new_content = transform.source_code.replace(
|
|
66
|
+
"# # transform.name", f"# # {transform.description}"
|
|
67
|
+
)
|
|
68
|
+
elif transform.source_code.startswith("# %% [markdown]"):
|
|
69
|
+
source_code_split = transform.source_code.split("\n")
|
|
70
|
+
if source_code_split[1] == "#":
|
|
71
|
+
source_code_split[1] = f"# # {transform.description}"
|
|
72
|
+
new_content = "\n".join(source_code_split)
|
|
73
|
+
else:
|
|
74
|
+
new_content = transform.source_code
|
|
75
|
+
else: # R notebook
|
|
76
|
+
new_content = transform.source_code
|
|
77
|
+
current_title = parse_title_r_notebook(new_content)
|
|
78
|
+
if current_title is not None and current_title != transform.description:
|
|
79
|
+
pattern = r'^(---\n.*?title:\s*)"([^"]*)"(.*?---)'
|
|
80
|
+
replacement = f'\\1"{transform.description}"\\3'
|
|
81
|
+
new_content = re.sub(
|
|
82
|
+
pattern,
|
|
83
|
+
replacement,
|
|
84
|
+
new_content,
|
|
85
|
+
flags=re.DOTALL | re.MULTILINE,
|
|
86
|
+
)
|
|
87
|
+
logger.important(
|
|
88
|
+
f"updated title to match description: {current_title} →"
|
|
89
|
+
f" {transform.description}"
|
|
90
|
+
)
|
|
91
|
+
if bump_revision:
|
|
92
|
+
uid = transform.uid
|
|
93
|
+
if (
|
|
94
|
+
uid in new_content
|
|
95
|
+
): # this only hits if it has the full uid, not for the stem uid
|
|
96
|
+
new_uid = f"{uid[:-4]}{increment_base62(uid[-4:])}"
|
|
97
|
+
new_content = new_content.replace(uid, new_uid)
|
|
98
|
+
logger.important(f"updated uid: {uid} → {new_uid}")
|
|
99
|
+
if notebook_path.suffix == ".ipynb":
|
|
100
|
+
notebook = jupytext.reads(new_content, fmt="py:percent")
|
|
101
|
+
jupytext.write(notebook, notebook_path)
|
|
102
|
+
else:
|
|
103
|
+
notebook_path.write_text(new_content)
|
|
104
|
+
|
|
105
|
+
query_by_uid = uid is not None
|
|
106
|
+
|
|
107
|
+
match entity:
|
|
108
|
+
case "transform":
|
|
109
|
+
if query_by_uid:
|
|
110
|
+
# we don't use .get here because DoesNotExist is hard to catch
|
|
111
|
+
# due to private django API
|
|
112
|
+
# here full uid is not expected anymore as before
|
|
113
|
+
# via ln.Transform.objects.get(uid=uid)
|
|
114
|
+
transforms = ln.Transform.objects.filter(uid__startswith=uid)
|
|
115
|
+
else:
|
|
116
|
+
# if below, we take is_latest=True as the criterion, we might get draft notebooks
|
|
117
|
+
# hence, we use source_code__isnull=False and order by created_at instead
|
|
118
|
+
transforms = ln.Transform.objects.filter(
|
|
119
|
+
key=key, source_code__isnull=False
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (n_transforms := len(transforms)) == 0:
|
|
123
|
+
err_msg = f"uid {uid}" if query_by_uid else f"key={key} and source_code"
|
|
124
|
+
raise SystemExit(f"Transform with {err_msg} does not exist.")
|
|
125
|
+
|
|
126
|
+
if n_transforms > 1:
|
|
127
|
+
transforms = transforms.order_by("-created_at")
|
|
128
|
+
transform = transforms.first()
|
|
129
|
+
|
|
130
|
+
target_path = Path(transform.key)
|
|
131
|
+
if ln_setup.settings.dev_dir is not None:
|
|
132
|
+
target_path = ln_setup.settings.dev_dir / target_path
|
|
133
|
+
if len(target_path.parents) > 1:
|
|
134
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
if target_path.exists():
|
|
136
|
+
response = input(f"! {target_path} exists: replace? (y/n)")
|
|
137
|
+
if response != "y":
|
|
138
|
+
raise SystemExit("Aborted.")
|
|
139
|
+
|
|
140
|
+
if transform.source_code is not None:
|
|
141
|
+
if target_path.suffix in (".ipynb", ".Rmd", ".qmd"):
|
|
142
|
+
script_to_notebook(transform, target_path, bump_revision=True)
|
|
143
|
+
else:
|
|
144
|
+
target_path.write_text(transform.source_code)
|
|
145
|
+
else:
|
|
146
|
+
raise SystemExit("No source code available for this transform.")
|
|
147
|
+
|
|
148
|
+
logger.important(f"{transform.type} is here: {target_path}")
|
|
149
|
+
|
|
150
|
+
if with_env:
|
|
151
|
+
ln.settings.track_run_inputs = False
|
|
152
|
+
if (
|
|
153
|
+
transform.latest_run is not None
|
|
154
|
+
and transform.latest_run.environment is not None
|
|
155
|
+
):
|
|
156
|
+
filepath_env_cache = transform.latest_run.environment.cache()
|
|
157
|
+
target_env_filename = (
|
|
158
|
+
target_path.parent / f"{target_path.stem}__requirements.txt"
|
|
159
|
+
)
|
|
160
|
+
shutil.move(filepath_env_cache, target_env_filename)
|
|
161
|
+
logger.important(f"environment is here: {target_env_filename}")
|
|
162
|
+
else:
|
|
163
|
+
logger.warning(
|
|
164
|
+
"latest transform run with environment doesn't exist"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return target_path
|
|
168
|
+
case "artifact" | "collection":
|
|
169
|
+
ln.settings.track_run_inputs = False
|
|
170
|
+
|
|
171
|
+
EntityClass = ln.Artifact if entity == "artifact" else ln.Collection
|
|
172
|
+
|
|
173
|
+
# we don't use .get here because DoesNotExist is hard to catch due to private django API
|
|
174
|
+
# we use `.objects` here because we don't want to exclude kind = __lamindb_run__ artifacts
|
|
175
|
+
if query_by_uid:
|
|
176
|
+
entities = EntityClass.objects.filter(uid__startswith=uid)
|
|
177
|
+
else:
|
|
178
|
+
entities = EntityClass.objects.filter(key=key)
|
|
179
|
+
|
|
180
|
+
if (n_entities := len(entities)) == 0:
|
|
181
|
+
err_msg = f"uid={uid}" if query_by_uid else f"key={key}"
|
|
182
|
+
raise SystemExit(
|
|
183
|
+
f"{entity.capitalize()} with {err_msg} does not exist."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if n_entities > 1:
|
|
187
|
+
entities = entities.order_by("-created_at")
|
|
188
|
+
|
|
189
|
+
entity_obj = entities.first()
|
|
190
|
+
cache_path = entity_obj.cache(is_run_input=current_run)
|
|
191
|
+
|
|
192
|
+
# collection gives us a list of paths
|
|
193
|
+
if isinstance(cache_path, list):
|
|
194
|
+
logger.important(f"{entity} paths ({len(cache_path)} files):")
|
|
195
|
+
for i, path in enumerate(cache_path):
|
|
196
|
+
if i < 5 or i >= len(cache_path) - 5:
|
|
197
|
+
logger.important(f" [{i + 1}/{len(cache_path)}] {path}")
|
|
198
|
+
elif i == 5:
|
|
199
|
+
logger.important(f" ... {len(cache_path) - 10} more files ...")
|
|
200
|
+
else:
|
|
201
|
+
logger.important(f"{entity} is here: {cache_path}")
|
|
202
|
+
case _:
|
|
203
|
+
raise AssertionError(f"unknown entity {entity}")
|
lamin_cli/_migration.py
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
if os.environ.get("NO_RICH"):
|
|
6
|
-
import click as click
|
|
7
|
-
else:
|
|
8
|
-
import rich_click as click
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@click.group()
|
|
12
|
-
def migrate():
|
|
13
|
-
"""Manage database schema migrations."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@migrate.command("create")
|
|
17
|
-
def create():
|
|
18
|
-
"""Create a new migration."""
|
|
19
|
-
from lamindb_setup._migrate import migrate
|
|
20
|
-
|
|
21
|
-
return migrate.create()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@migrate.command("deploy")
|
|
25
|
-
@click.option("--package-name", type=str, default=None)
|
|
26
|
-
@click.option("--number", type=str, default=None)
|
|
27
|
-
def deploy(package_name: str | None = None, number: str | None = None):
|
|
28
|
-
"""Deploy migrations."""
|
|
29
|
-
from lamindb_setup._migrate import migrate
|
|
30
|
-
|
|
31
|
-
return migrate.deploy(package_name=package_name, number=number)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@migrate.command("squash")
|
|
35
|
-
@click.option("--package-name", type=str, default=None)
|
|
36
|
-
@click.option("--end-number", type=str, default=None)
|
|
37
|
-
@click.option("--start-number", type=str, default=None)
|
|
38
|
-
def squash(
|
|
39
|
-
package_name: str | None,
|
|
40
|
-
end_number: str | None,
|
|
41
|
-
start_number: str | None,
|
|
42
|
-
):
|
|
43
|
-
"""Squash migrations."""
|
|
44
|
-
from lamindb_setup._migrate import migrate
|
|
45
|
-
|
|
46
|
-
return migrate.squash(
|
|
47
|
-
package_name=package_name,
|
|
48
|
-
migration_nr=end_number,
|
|
49
|
-
start_migration_nr=start_number,
|
|
50
|
-
)
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
if os.environ.get("NO_RICH"):
|
|
6
|
+
import click as click
|
|
7
|
+
else:
|
|
8
|
+
import rich_click as click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def migrate():
|
|
13
|
+
"""Manage database schema migrations."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@migrate.command("create")
|
|
17
|
+
def create():
|
|
18
|
+
"""Create a new migration."""
|
|
19
|
+
from lamindb_setup._migrate import migrate
|
|
20
|
+
|
|
21
|
+
return migrate.create()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@migrate.command("deploy")
|
|
25
|
+
@click.option("--package-name", type=str, default=None)
|
|
26
|
+
@click.option("--number", type=str, default=None)
|
|
27
|
+
def deploy(package_name: str | None = None, number: str | None = None):
|
|
28
|
+
"""Deploy migrations."""
|
|
29
|
+
from lamindb_setup._migrate import migrate
|
|
30
|
+
|
|
31
|
+
return migrate.deploy(package_name=package_name, number=number)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@migrate.command("squash")
|
|
35
|
+
@click.option("--package-name", type=str, default=None)
|
|
36
|
+
@click.option("--end-number", type=str, default=None)
|
|
37
|
+
@click.option("--start-number", type=str, default=None)
|
|
38
|
+
def squash(
|
|
39
|
+
package_name: str | None,
|
|
40
|
+
end_number: str | None,
|
|
41
|
+
start_number: str | None,
|
|
42
|
+
):
|
|
43
|
+
"""Squash migrations."""
|
|
44
|
+
from lamindb_setup._migrate import migrate
|
|
45
|
+
|
|
46
|
+
return migrate.squash(
|
|
47
|
+
package_name=package_name,
|
|
48
|
+
migration_nr=end_number,
|
|
49
|
+
start_migration_nr=start_number,
|
|
50
|
+
)
|