lamin_cli 1.11.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 +285 -84
- lamin_cli/_cache.py +1 -1
- lamin_cli/_delete.py +52 -11
- lamin_cli/_io.py +7 -4
- lamin_cli/_save.py +32 -7
- 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.11.0.dist-info → lamin_cli-1.12.0.dist-info}/METADATA +1 -1
- lamin_cli-1.12.0.dist-info/RECORD +22 -0
- lamin_cli-1.11.0.dist-info/RECORD +0 -22
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.0.dist-info}/LICENSE +0 -0
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.0.dist-info}/WHEEL +0 -0
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.0.dist-info}/entry_points.txt +0 -0
lamin_cli/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ The interface is defined in `__main__.py`.
|
|
|
6
6
|
The root API here is used by LaminR to replicate the CLI functionality.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "1.
|
|
9
|
+
__version__ = "1.12.0"
|
|
10
10
|
|
|
11
11
|
from lamindb_setup import disconnect, logout
|
|
12
12
|
from lamindb_setup._connect_instance import _connect_cli as connect
|
lamin_cli/__main__.py
CHANGED
|
@@ -53,9 +53,7 @@ COMMAND_GROUPS = {
|
|
|
53
53
|
{
|
|
54
54
|
"name": "Configure",
|
|
55
55
|
"commands": [
|
|
56
|
-
"checkout",
|
|
57
56
|
"switch",
|
|
58
|
-
"cache",
|
|
59
57
|
"settings",
|
|
60
58
|
"migrate",
|
|
61
59
|
],
|
|
@@ -112,7 +110,6 @@ else:
|
|
|
112
110
|
|
|
113
111
|
from lamindb_setup._silence_loggers import silence_loggers
|
|
114
112
|
|
|
115
|
-
from lamin_cli._cache import cache
|
|
116
113
|
from lamin_cli._io import io
|
|
117
114
|
from lamin_cli._migration import migrate
|
|
118
115
|
from lamin_cli._settings import settings
|
|
@@ -146,7 +143,7 @@ def login(user: str, key: str | None):
|
|
|
146
143
|
|
|
147
144
|
After authenticating once, you can re-authenticate and switch between accounts via `lamin login myhandle`.
|
|
148
145
|
|
|
149
|
-
|
|
146
|
+
→ Python/R alternative: {func}`~lamindb.setup.login`
|
|
150
147
|
"""
|
|
151
148
|
return login_(user, key=key)
|
|
152
149
|
|
|
@@ -181,9 +178,19 @@ def init(
|
|
|
181
178
|
db: str | None,
|
|
182
179
|
modules: str | None,
|
|
183
180
|
):
|
|
184
|
-
"""
|
|
181
|
+
"""Initialize an instance.
|
|
185
182
|
|
|
186
|
-
|
|
183
|
+
This initializes a LaminDB instance, for example:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
lamin init --storage ./mydata
|
|
187
|
+
lamin init --storage s3://my-bucket
|
|
188
|
+
lamin init --storage gs://my-bucket
|
|
189
|
+
lamin init --storage ./mydata --modules bionty
|
|
190
|
+
lamin init --storage ./mydata --modules bionty,pertdb
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
→ Python/R alternative: {func}`~lamindb.setup.init`
|
|
187
194
|
"""
|
|
188
195
|
return init_(storage=storage, db=db, modules=modules, name=name)
|
|
189
196
|
|
|
@@ -191,73 +198,108 @@ def init(
|
|
|
191
198
|
# fmt: off
|
|
192
199
|
@main.command()
|
|
193
200
|
@click.argument("instance", type=str)
|
|
194
|
-
@click.option("--use_proxy_db", is_flag=True, help="Use proxy database connection.")
|
|
195
201
|
# fmt: on
|
|
196
|
-
def connect(instance: str
|
|
197
|
-
"""
|
|
202
|
+
def connect(instance: str):
|
|
203
|
+
"""Set a default instance for auto-connection.
|
|
198
204
|
|
|
199
|
-
Python/R sessions and CLI commands will then auto-connect to
|
|
205
|
+
Python/R sessions and CLI commands will then auto-connect to this LaminDB instance.
|
|
200
206
|
|
|
201
|
-
Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`)
|
|
207
|
+
Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`), for example:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
lamin connect laminlabs/cellxgene
|
|
211
|
+
lamin connect https://lamin.ai/laminlabs/cellxgene
|
|
212
|
+
```
|
|
202
213
|
|
|
203
|
-
|
|
214
|
+
→ Python/R alternative: {func}`~lamindb.connect` the global default database or a database object via {class}`~lamindb.DB`
|
|
204
215
|
"""
|
|
205
|
-
return connect_(instance
|
|
216
|
+
return connect_(instance)
|
|
206
217
|
|
|
207
218
|
|
|
208
219
|
@main.command()
|
|
209
220
|
def disconnect():
|
|
210
|
-
"""
|
|
221
|
+
"""Unset the default instance for auto-connection.
|
|
211
222
|
|
|
212
|
-
|
|
223
|
+
Python/R sessions and CLI commands will no longer auto-connect to a LaminDB instance.
|
|
224
|
+
|
|
225
|
+
For example:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
lamin disconnect
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
→ Python/R alternative: {func}`~lamindb.setup.disconnect`
|
|
213
232
|
"""
|
|
214
233
|
return disconnect_()
|
|
215
234
|
|
|
216
235
|
|
|
217
236
|
# fmt: off
|
|
218
237
|
@main.command()
|
|
219
|
-
@click.argument("
|
|
220
|
-
@click.
|
|
238
|
+
@click.argument("registry", type=click.Choice(["branch", "project"]))
|
|
239
|
+
@click.argument("name", type=str, required=False)
|
|
240
|
+
# below is deprecated, for backward compatibility
|
|
241
|
+
@click.option("--name", "name_opt", type=str, default=None, hidden=True, help="A name.")
|
|
221
242
|
# fmt: on
|
|
222
|
-
def create(
|
|
223
|
-
"""
|
|
243
|
+
def create(
|
|
244
|
+
registry: Literal["branch", "project"],
|
|
245
|
+
name: str | None,
|
|
246
|
+
name_opt: str | None,
|
|
247
|
+
):
|
|
248
|
+
"""Create an object.
|
|
224
249
|
|
|
225
250
|
Currently only supports creating branches and projects.
|
|
226
251
|
|
|
227
252
|
```
|
|
228
|
-
lamin create branch
|
|
229
|
-
lamin create project
|
|
253
|
+
lamin create branch my_branch
|
|
254
|
+
lamin create project my_project
|
|
230
255
|
```
|
|
256
|
+
|
|
257
|
+
→ Python/R alternative: {class}`~lamindb.Branch` and {class}`~lamindb.Project`.
|
|
231
258
|
"""
|
|
259
|
+
resolved_name = name if name is not None else name_opt
|
|
260
|
+
if resolved_name is None:
|
|
261
|
+
raise click.UsageError(
|
|
262
|
+
"Specify a name. Examples: lamin create branch my_branch, lamin create project my_project"
|
|
263
|
+
)
|
|
264
|
+
if name_opt is not None:
|
|
265
|
+
warnings.warn(
|
|
266
|
+
"lamin create --name is deprecated; use 'lamin create <registry> <name>' instead, e.g. lamin create branch my_branch.",
|
|
267
|
+
DeprecationWarning,
|
|
268
|
+
stacklevel=2,
|
|
269
|
+
)
|
|
270
|
+
|
|
232
271
|
from lamindb.models import Branch, Project
|
|
233
272
|
|
|
234
|
-
if
|
|
235
|
-
record = Branch(name=
|
|
236
|
-
elif
|
|
237
|
-
record = Project(name=
|
|
273
|
+
if registry == "branch":
|
|
274
|
+
record = Branch(name=resolved_name).save()
|
|
275
|
+
elif registry == "project":
|
|
276
|
+
record = Project(name=resolved_name).save()
|
|
238
277
|
else:
|
|
239
|
-
raise NotImplementedError(f"Creating {
|
|
240
|
-
logger.important(f"created {
|
|
278
|
+
raise NotImplementedError(f"Creating {registry} object is not implemented.")
|
|
279
|
+
logger.important(f"created {registry}: {record.name}")
|
|
241
280
|
|
|
242
281
|
|
|
243
282
|
# fmt: off
|
|
244
283
|
@main.command(name="list")
|
|
245
|
-
@click.argument("
|
|
246
|
-
@click.option("--name", type=str, default=None, help="A name.")
|
|
284
|
+
@click.argument("registry", type=str)
|
|
247
285
|
# fmt: on
|
|
248
|
-
def list_(
|
|
249
|
-
"""List
|
|
286
|
+
def list_(registry: Literal["branch", "space"]):
|
|
287
|
+
"""List objects.
|
|
288
|
+
|
|
289
|
+
For example:
|
|
250
290
|
|
|
251
291
|
```
|
|
252
292
|
lamin list branch
|
|
253
293
|
lamin list space
|
|
254
294
|
```
|
|
295
|
+
|
|
296
|
+
→ Python/R alternative: {method}`~lamindb.Branch.to_dataframe()`
|
|
255
297
|
"""
|
|
256
|
-
assert
|
|
298
|
+
assert registry in {"branch", "space"}, "Currently only supports listing branches and spaces."
|
|
257
299
|
|
|
258
300
|
from lamindb.models import Branch, Space
|
|
259
301
|
|
|
260
|
-
if
|
|
302
|
+
if registry == "branch":
|
|
261
303
|
print(Branch.to_dataframe())
|
|
262
304
|
else:
|
|
263
305
|
print(Space.to_dataframe())
|
|
@@ -265,28 +307,56 @@ def list_(entity: Literal["branch"], name: str | None = None):
|
|
|
265
307
|
|
|
266
308
|
# fmt: off
|
|
267
309
|
@main.command()
|
|
268
|
-
@click.
|
|
269
|
-
@click.
|
|
310
|
+
@click.argument("registry", type=click.Choice(["branch", "space"]), required=False)
|
|
311
|
+
@click.argument("name", type=str, required=False)
|
|
312
|
+
# below are deprecated, for backward compatibility
|
|
313
|
+
@click.option("--branch", type=str, default=None, hidden=True, help="A valid branch name or uid.")
|
|
314
|
+
@click.option("--space", type=str, default=None, hidden=True, help="A valid space name or uid.")
|
|
270
315
|
# fmt: on
|
|
271
|
-
def switch(
|
|
316
|
+
def switch(
|
|
317
|
+
registry: Literal["branch", "space"] | None,
|
|
318
|
+
name: str | None,
|
|
319
|
+
branch: str | None,
|
|
320
|
+
space: str | None,
|
|
321
|
+
):
|
|
272
322
|
"""Switch between branches or spaces.
|
|
273
323
|
|
|
324
|
+
Python/R sessions and CLI commands will use the current default branch or space, for example:
|
|
325
|
+
|
|
274
326
|
```
|
|
275
|
-
lamin switch
|
|
276
|
-
lamin switch
|
|
327
|
+
lamin switch branch my_branch
|
|
328
|
+
lamin switch space our_space
|
|
277
329
|
```
|
|
330
|
+
|
|
331
|
+
→ Python/R alternative: {attr}`~lamindb.setup.core.SetupSettings.branch` and {attr}`~lamindb.setup.core.SetupSettings.space`
|
|
278
332
|
"""
|
|
333
|
+
if registry is not None and name is not None:
|
|
334
|
+
branch = name if registry == "branch" else None
|
|
335
|
+
space = name if registry == "space" else None
|
|
336
|
+
elif branch is None and space is None:
|
|
337
|
+
raise click.UsageError(
|
|
338
|
+
"Specify branch or space. Examples: lamin switch branch my_branch, lamin switch space our_space"
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
warnings.warn(
|
|
342
|
+
"lamin switch --branch and --space are deprecated; use 'lamin switch branch <name>' or 'lamin switch space <name>' instead.",
|
|
343
|
+
DeprecationWarning,
|
|
344
|
+
stacklevel=2,
|
|
345
|
+
)
|
|
346
|
+
|
|
279
347
|
from lamindb.setup import switch as switch_
|
|
280
348
|
|
|
281
349
|
switch_(branch=branch, space=space)
|
|
282
350
|
|
|
283
351
|
|
|
284
352
|
@main.command()
|
|
285
|
-
@click.option("--schema", is_flag=True, help="View database schema.")
|
|
353
|
+
@click.option("--schema", is_flag=True, help="View database schema via Django plugin.")
|
|
286
354
|
def info(schema: bool):
|
|
287
|
-
"""Show info about the
|
|
355
|
+
"""Show info about the instance, development & cache directories, branch, space, and user.
|
|
288
356
|
|
|
289
|
-
|
|
357
|
+
Manage settings via [lamin settings](https://docs.lamin.ai/cli#settings).
|
|
358
|
+
|
|
359
|
+
→ Python/R alternative: {func}`~lamindb.setup.settings`
|
|
290
360
|
"""
|
|
291
361
|
if schema:
|
|
292
362
|
from lamindb_setup._schema import view
|
|
@@ -301,31 +371,42 @@ def info(schema: bool):
|
|
|
301
371
|
|
|
302
372
|
# fmt: off
|
|
303
373
|
@main.command()
|
|
374
|
+
# entity can be a registry or an object in the registry
|
|
304
375
|
@click.argument("entity", type=str)
|
|
305
376
|
@click.option("--name", type=str, default=None)
|
|
306
377
|
@click.option("--uid", type=str, default=None)
|
|
307
|
-
@click.option("--
|
|
378
|
+
@click.option("--key", type=str, default=None, help="The key for the entity (artifact, transform).")
|
|
308
379
|
@click.option("--permanent", is_flag=True, default=None, help="Permanently delete the entity where applicable, e.g., for artifact, transform, collection.")
|
|
309
380
|
@click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation (only relevant for instance).")
|
|
310
381
|
# fmt: on
|
|
311
|
-
def delete(entity: str, name: str | None = None, uid: str | None = None, slug: str | None = None, permanent: bool | None = None, force: bool = False):
|
|
312
|
-
"""Delete an
|
|
382
|
+
def delete(entity: str, name: str | None = None, uid: str | None = None, key: str | None = None, slug: str | None = None, permanent: bool | None = None, force: bool = False):
|
|
383
|
+
"""Delete an object.
|
|
313
384
|
|
|
314
385
|
Currently supported: `branch`, `artifact`, `transform`, `collection`, and `instance`. For example:
|
|
315
386
|
|
|
316
387
|
```
|
|
317
|
-
|
|
318
|
-
lamin delete
|
|
388
|
+
# via --key or --name
|
|
389
|
+
lamin delete artifact --key mydatasets/mytable.parquet
|
|
390
|
+
lamin delete transform --key myanalyses/analysis.ipynb
|
|
319
391
|
lamin delete branch --name my_branch
|
|
320
392
|
lamin delete instance --slug account/name
|
|
393
|
+
# via registry and --uid
|
|
394
|
+
lamin delete artifact --uid e2G7k9EVul4JbfsE
|
|
395
|
+
lamin delete transform --uid Vul4JbfsEYAy5
|
|
396
|
+
# via URL
|
|
397
|
+
lamin delete https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsEYAy5
|
|
398
|
+
lamin delete https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsEYAy5 --permanent
|
|
321
399
|
```
|
|
400
|
+
|
|
401
|
+
→ Python/R alternative: {method}`~lamindb.SQLRecord.delete` and {func}`~lamindb.setup.delete`
|
|
322
402
|
"""
|
|
323
403
|
from lamin_cli._delete import delete as delete_
|
|
324
404
|
|
|
325
|
-
return delete_(entity=entity, name=name, uid=uid,
|
|
405
|
+
return delete_(entity=entity, name=name, uid=uid, key=key, permanent=permanent, force=force)
|
|
326
406
|
|
|
327
407
|
|
|
328
408
|
@main.command()
|
|
409
|
+
# entity can be a registry or an object in the registry
|
|
329
410
|
@click.argument("entity", type=str, required=False)
|
|
330
411
|
@click.option("--uid", help="The uid for the entity.")
|
|
331
412
|
@click.option("--key", help="The key for the entity.")
|
|
@@ -333,23 +414,23 @@ def delete(entity: str, name: str | None = None, uid: str | None = None, slug: s
|
|
|
333
414
|
"--with-env", is_flag=True, help="Also return the environment for a tranform."
|
|
334
415
|
)
|
|
335
416
|
def load(entity: str | None = None, uid: str | None = None, key: str | None = None, with_env: bool = False):
|
|
336
|
-
"""
|
|
417
|
+
"""Sync a file/folder into a local cache (artifacts) or development directory (transforms).
|
|
337
418
|
|
|
338
419
|
Pass a URL or `--key`. For example:
|
|
339
420
|
|
|
340
421
|
```
|
|
341
|
-
|
|
422
|
+
# via key
|
|
342
423
|
lamin load --key mydatasets/mytable.parquet
|
|
343
424
|
lamin load --key analysis.ipynb
|
|
344
425
|
lamin load --key myanalyses/analysis.ipynb --with-env
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
You can also pass a uid and the entity type:
|
|
348
|
-
|
|
349
|
-
```
|
|
426
|
+
# via registry and --uid
|
|
350
427
|
lamin load artifact --uid e2G7k9EVul4JbfsE
|
|
351
428
|
lamin load transform --uid Vul4JbfsEYAy5
|
|
429
|
+
# via URL
|
|
430
|
+
lamin load https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsE
|
|
352
431
|
```
|
|
432
|
+
|
|
433
|
+
→ Python/R alternative: {func}`~lamindb.Artifact.load`, no equivalent for transforms
|
|
353
434
|
"""
|
|
354
435
|
from lamin_cli._load import load as load_
|
|
355
436
|
if entity is not None:
|
|
@@ -381,30 +462,36 @@ def _describe(entity: str = "artifact", uid: str | None = None, key: str | None
|
|
|
381
462
|
|
|
382
463
|
|
|
383
464
|
@main.command()
|
|
465
|
+
# entity can be a registry or an object in the registry
|
|
384
466
|
@click.argument("entity", type=str, default="artifact")
|
|
385
467
|
@click.option("--uid", help="The uid for the entity.")
|
|
386
468
|
@click.option("--key", help="The key for the entity.")
|
|
387
469
|
def describe(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
388
|
-
"""Describe an
|
|
470
|
+
"""Describe an object.
|
|
389
471
|
|
|
390
472
|
Examples:
|
|
391
473
|
|
|
392
474
|
```
|
|
475
|
+
# via --key
|
|
393
476
|
lamin describe --key example_datasets/mini_immuno/dataset1.h5ad
|
|
477
|
+
# via registry and --uid
|
|
478
|
+
lamin describe artifact --uid e2G7k9EVul4JbfsE
|
|
479
|
+
# via URL
|
|
394
480
|
lamin describe https://lamin.ai/laminlabs/lamin-site-assets/artifact/6sofuDVvTANB0f48
|
|
395
481
|
```
|
|
396
482
|
|
|
397
|
-
|
|
483
|
+
→ Python/R alternative: {meth}`~lamindb.Artifact.describe`
|
|
398
484
|
"""
|
|
399
485
|
_describe(entity=entity, uid=uid, key=key)
|
|
400
486
|
|
|
401
487
|
|
|
402
488
|
@main.command()
|
|
489
|
+
# entity can be a registry or an object in the registry
|
|
403
490
|
@click.argument("entity", type=str, default="artifact")
|
|
404
491
|
@click.option("--uid", help="The uid for the entity.")
|
|
405
492
|
@click.option("--key", help="The key for the entity.")
|
|
406
493
|
def get(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
407
|
-
"""Query metadata about an
|
|
494
|
+
"""Query metadata about an object.
|
|
408
495
|
|
|
409
496
|
Currently equivalent to `lamin describe`.
|
|
410
497
|
"""
|
|
@@ -420,11 +507,25 @@ def get(entity: str = "artifact", uid: str | None = None, key: str | None = None
|
|
|
420
507
|
@click.option("--project", type=str, default=None, help="A valid project name or uid.")
|
|
421
508
|
@click.option("--space", type=str, default=None, help="A valid space name or uid.")
|
|
422
509
|
@click.option("--branch", type=str, default=None, help="A valid branch name or uid.")
|
|
423
|
-
@click.option(
|
|
424
|
-
|
|
425
|
-
"""
|
|
510
|
+
@click.option(
|
|
511
|
+
"--registry",
|
|
512
|
+
type=click.Choice(["artifact", "transform"]),
|
|
513
|
+
default=None,
|
|
514
|
+
help="Either 'artifact' or 'transform'. If not passed, chooses based on path suffix.",
|
|
515
|
+
)
|
|
516
|
+
def save(
|
|
517
|
+
path: str,
|
|
518
|
+
key: str,
|
|
519
|
+
description: str,
|
|
520
|
+
stem_uid: str,
|
|
521
|
+
project: str,
|
|
522
|
+
space: str,
|
|
523
|
+
branch: str,
|
|
524
|
+
registry: Literal["artifact", "transform"] | None,
|
|
525
|
+
):
|
|
526
|
+
"""Save a file or folder as an artifact or transform.
|
|
426
527
|
|
|
427
|
-
Example:
|
|
528
|
+
Example:
|
|
428
529
|
|
|
429
530
|
```
|
|
430
531
|
lamin save my_table.csv --key my_tables/my_table.csv --project my_project
|
|
@@ -433,9 +534,11 @@ def save(path: str, key: str, description: str, stem_uid: str, project: str, spa
|
|
|
433
534
|
By passing a `--project` identifier, the artifact will be labeled with the corresponding project.
|
|
434
535
|
If you pass a `--space` or `--branch` identifier, you save the artifact in the corresponding {class}`~lamindb.Space` or on the corresponding {class}`~lamindb.Branch`.
|
|
435
536
|
|
|
436
|
-
|
|
537
|
+
Defaults to saving `.py`, `.ipynb`, `.R`, `.Rmd`, and `.qmd` as {class}`~lamindb.Transform` and
|
|
437
538
|
other file types and folders as {class}`~lamindb.Artifact`. You can enforce saving a file as
|
|
438
539
|
an {class}`~lamindb.Artifact` by passing `--registry artifact`.
|
|
540
|
+
|
|
541
|
+
→ Python/R alternative: {class}`~lamindb.Artifact` and {class}`~lamindb.Transform`
|
|
439
542
|
"""
|
|
440
543
|
if save_(path=path, key=key, description=description, stem_uid=stem_uid, project=project, space=space, branch=branch, registry=registry) is not None:
|
|
441
544
|
sys.exit(1)
|
|
@@ -461,6 +564,8 @@ def track():
|
|
|
461
564
|
```
|
|
462
565
|
sh my_script.sh
|
|
463
566
|
```
|
|
567
|
+
|
|
568
|
+
→ Python/R alternative: {func}`~lamindb.track` and {func}`~lamindb.finish` for (non-shell) scripts or notebooks
|
|
464
569
|
"""
|
|
465
570
|
from lamin_cli._context import track as track_
|
|
466
571
|
return track_()
|
|
@@ -468,61 +573,85 @@ def track():
|
|
|
468
573
|
|
|
469
574
|
@main.command()
|
|
470
575
|
def finish():
|
|
471
|
-
"""Finish
|
|
576
|
+
"""Finish a currently tracked run of a shell script.
|
|
472
577
|
|
|
473
|
-
|
|
578
|
+
→ Python/R alternative: {func}`~lamindb.finish()`
|
|
474
579
|
"""
|
|
475
580
|
from lamin_cli._context import finish as finish_
|
|
476
581
|
return finish_()
|
|
477
582
|
|
|
478
583
|
|
|
479
584
|
@main.command()
|
|
585
|
+
# entity can be a registry or an object in the registry
|
|
480
586
|
@click.argument("entity", type=str, default=None, required=False)
|
|
481
587
|
@click.option("--key", type=str, default=None, help="The key of an artifact or transform.")
|
|
482
588
|
@click.option("--uid", type=str, default=None, help="The uid of an artifact or transform.")
|
|
483
589
|
@click.option("--project", type=str, default=None, help="A valid project name or uid.")
|
|
590
|
+
@click.option("--ulabel", type=str, default=None, help="A valid ulabel name or uid.")
|
|
591
|
+
@click.option("--record", type=str, default=None, help="A valid record name or uid.")
|
|
484
592
|
@click.option("--features", multiple=True, help="Feature annotations. Supports: feature=value, feature=val1,val2, or feature=\"val1\",\"val2\"")
|
|
485
|
-
def annotate(entity: str | None, key: str, uid: str, project: str, features: tuple):
|
|
593
|
+
def annotate(entity: str | None, key: str, uid: str, project: str, ulabel: str, record: str, features: tuple):
|
|
486
594
|
"""Annotate an artifact or transform.
|
|
487
595
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
You can annotate with projects and valid features & values. For example,
|
|
596
|
+
You can annotate with projects, ulabels, records, and valid features & values. For example,
|
|
491
597
|
|
|
492
598
|
```
|
|
599
|
+
# via --key
|
|
493
600
|
lamin annotate --key raw/sample.fastq --project "My Project"
|
|
601
|
+
lamin annotate --key raw/sample.fastq --ulabel "My ULabel" --record "Experiment 1"
|
|
494
602
|
lamin annotate --key raw/sample.fastq --features perturbation=IFNG,DMSO cell_line=HEK297
|
|
495
603
|
lamin annotate --key my-notebook.ipynb --project "My Project"
|
|
604
|
+
# via registry and --uid
|
|
605
|
+
lamin annotate artifact --uid e2G7k9EVul4JbfsE --project "My Project"
|
|
606
|
+
# via URL
|
|
607
|
+
lamin annotate https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsE --project "My Project"
|
|
496
608
|
```
|
|
497
|
-
"""
|
|
498
|
-
import lamindb as ln
|
|
499
609
|
|
|
610
|
+
→ Python/R alternative: `artifact.features.add_values()` via {meth}`~lamindb.models.FeatureManager.add_values` and `artifact.projects.add()`, `artifact.ulabels.add()`, `artifact.records.add()`, ... via {meth}`~lamindb.models.RelatedManager.add`
|
|
611
|
+
"""
|
|
500
612
|
from lamin_cli._annotate import _parse_features_list
|
|
501
613
|
from lamin_cli._save import infer_registry_from_path
|
|
502
614
|
|
|
503
|
-
#
|
|
504
|
-
if not
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
615
|
+
# Handle URL: decompose and connect (same pattern as load/delete)
|
|
616
|
+
if entity is not None and entity.startswith("https://"):
|
|
617
|
+
url = entity
|
|
618
|
+
instance, registry, uid = decompose_url(url)
|
|
619
|
+
if registry not in {"artifact", "transform"}:
|
|
620
|
+
raise click.ClickException(
|
|
621
|
+
f"Annotate does not support {registry}. Use artifact or transform URLs."
|
|
622
|
+
)
|
|
623
|
+
ln_setup.connect(instance)
|
|
512
624
|
else:
|
|
513
|
-
|
|
625
|
+
if not ln_setup.settings._instance_exists:
|
|
626
|
+
raise click.ClickException(
|
|
627
|
+
"Not connected to an instance. Please run: lamin connect account/name"
|
|
628
|
+
)
|
|
629
|
+
if entity is None:
|
|
630
|
+
registry = infer_registry_from_path(key) if key is not None else "artifact"
|
|
631
|
+
else:
|
|
632
|
+
registry = entity
|
|
633
|
+
if registry not in {"artifact", "transform"}:
|
|
634
|
+
raise click.ClickException(
|
|
635
|
+
f"Annotate does not support {registry}. Use artifact or transform URLs."
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# import lamindb after connect went through
|
|
639
|
+
import lamindb as ln
|
|
640
|
+
|
|
514
641
|
if registry == "artifact":
|
|
515
642
|
model = ln.Artifact
|
|
516
643
|
else:
|
|
517
644
|
model = ln.Transform
|
|
518
645
|
|
|
519
|
-
# Get the artifact
|
|
646
|
+
# Get the artifact or transform
|
|
520
647
|
if key is not None:
|
|
521
648
|
artifact = model.get(key=key)
|
|
522
649
|
elif uid is not None:
|
|
523
650
|
artifact = model.get(uid) # do not use uid=uid, because then no truncated uids would work
|
|
524
651
|
else:
|
|
525
|
-
raise ln.errors.InvalidArgument(
|
|
652
|
+
raise ln.errors.InvalidArgument(
|
|
653
|
+
"Either pass a URL as entity or provide --key or --uid"
|
|
654
|
+
)
|
|
526
655
|
|
|
527
656
|
# Handle project annotation
|
|
528
657
|
if project is not None:
|
|
@@ -535,6 +664,28 @@ def annotate(entity: str | None, key: str, uid: str, project: str, features: tup
|
|
|
535
664
|
)
|
|
536
665
|
artifact.projects.add(project_record)
|
|
537
666
|
|
|
667
|
+
# Handle ulabel annotation
|
|
668
|
+
if ulabel is not None:
|
|
669
|
+
ulabel_record = ln.ULabel.filter(
|
|
670
|
+
ln.Q(name=ulabel) | ln.Q(uid=ulabel)
|
|
671
|
+
).one_or_none()
|
|
672
|
+
if ulabel_record is None:
|
|
673
|
+
raise ln.errors.InvalidArgument(
|
|
674
|
+
f"ULabel '{ulabel}' not found, either create it with `ln.ULabel(name='...').save()` or fix typos."
|
|
675
|
+
)
|
|
676
|
+
artifact.ulabels.add(ulabel_record)
|
|
677
|
+
|
|
678
|
+
# Handle record annotation
|
|
679
|
+
if record is not None:
|
|
680
|
+
record_record = ln.Record.filter(
|
|
681
|
+
ln.Q(name=record) | ln.Q(uid=record)
|
|
682
|
+
).one_or_none()
|
|
683
|
+
if record_record is None:
|
|
684
|
+
raise ln.errors.InvalidArgument(
|
|
685
|
+
f"Record '{record}' not found, either create it with `ln.Record(name='...').save()` or fix typos."
|
|
686
|
+
)
|
|
687
|
+
artifact.records.add(record_record)
|
|
688
|
+
|
|
538
689
|
# Handle feature annotations
|
|
539
690
|
if features:
|
|
540
691
|
feature_dict = _parse_features_list(features)
|
|
@@ -548,7 +699,7 @@ def annotate(entity: str | None, key: str, uid: str, project: str, features: tup
|
|
|
548
699
|
@click.argument("filepath", type=str)
|
|
549
700
|
@click.option("--project", type=str, default=None, help="A valid project name or uid. When running on Modal, creates an app with the same name.", required=True)
|
|
550
701
|
@click.option("--image-url", type=str, default=None, help="A URL to the base docker image to use.")
|
|
551
|
-
@click.option("--packages", type=str, default=
|
|
702
|
+
@click.option("--packages", type=str, default=None, help="A comma-separated list of additional packages to install.")
|
|
552
703
|
@click.option("--cpu", type=float, default=None, help="Configuration for the CPU.")
|
|
553
704
|
@click.option("--gpu", type=str, default=None, help="The type of GPU to use (only compatible with cuda images).")
|
|
554
705
|
def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gpu: str | None):
|
|
@@ -561,6 +712,8 @@ def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gp
|
|
|
561
712
|
```
|
|
562
713
|
lamin run my_script.py --project my_project
|
|
563
714
|
```
|
|
715
|
+
|
|
716
|
+
→ Python/R alternative: no equivalent
|
|
564
717
|
"""
|
|
565
718
|
from lamin_cli.compute.modal import Runner
|
|
566
719
|
|
|
@@ -589,10 +742,54 @@ def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gp
|
|
|
589
742
|
|
|
590
743
|
|
|
591
744
|
main.add_command(settings)
|
|
592
|
-
main.add_command(cache)
|
|
593
745
|
main.add_command(migrate)
|
|
594
746
|
main.add_command(io)
|
|
595
747
|
|
|
748
|
+
|
|
749
|
+
def _deprecated_cache_set(cache_dir: str) -> None:
|
|
750
|
+
logger.warning("'lamin cache' is deprecated. Use 'lamin settings cache-dir' instead.")
|
|
751
|
+
from lamindb_setup._cache import set_cache_dir
|
|
752
|
+
|
|
753
|
+
set_cache_dir(cache_dir)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _deprecated_cache_clear() -> None:
|
|
757
|
+
logger.warning("'lamin cache' is deprecated. Use 'lamin settings cache-dir' instead.")
|
|
758
|
+
from lamindb_setup._cache import clear_cache_dir
|
|
759
|
+
|
|
760
|
+
clear_cache_dir()
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def _deprecated_cache_get() -> None:
|
|
764
|
+
logger.warning("'lamin cache' is deprecated. Use 'lamin settings cache-dir' instead.")
|
|
765
|
+
from lamindb_setup._cache import get_cache_dir
|
|
766
|
+
|
|
767
|
+
click.echo(f"The cache directory is {get_cache_dir()}")
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@main.group("cache", hidden=True)
|
|
771
|
+
def deprecated_cache():
|
|
772
|
+
"""Deprecated. Use 'lamin settings cache-dir' instead."""
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
@deprecated_cache.command("set")
|
|
776
|
+
@click.argument(
|
|
777
|
+
"cache_dir",
|
|
778
|
+
type=click.Path(dir_okay=True, file_okay=False),
|
|
779
|
+
)
|
|
780
|
+
def _deprecated_cache_set_cmd(cache_dir: str) -> None:
|
|
781
|
+
_deprecated_cache_set(cache_dir)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
@deprecated_cache.command("clear")
|
|
785
|
+
def _deprecated_cache_clear_cmd() -> None:
|
|
786
|
+
_deprecated_cache_clear()
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
@deprecated_cache.command("get")
|
|
790
|
+
def _deprecated_cache_get_cmd() -> None:
|
|
791
|
+
_deprecated_cache_get()
|
|
792
|
+
|
|
596
793
|
# https://stackoverflow.com/questions/57810659/automatically-generate-all-help-documentation-for-click-commands
|
|
597
794
|
# https://claude.ai/chat/73c28487-bec3-4073-8110-50d1a2dd6b84
|
|
598
795
|
def _generate_help():
|
|
@@ -601,6 +798,8 @@ def _generate_help():
|
|
|
601
798
|
def recursive_help(
|
|
602
799
|
cmd: Command, parent: Context | None = None, name: tuple[str, ...] = ()
|
|
603
800
|
):
|
|
801
|
+
if getattr(cmd, "hidden", False):
|
|
802
|
+
return
|
|
604
803
|
ctx = click.Context(cmd, info_name=cmd.name, parent=parent)
|
|
605
804
|
assert cmd.name
|
|
606
805
|
name = (*name, cmd.name)
|
|
@@ -615,6 +814,8 @@ def _generate_help():
|
|
|
615
814
|
}
|
|
616
815
|
|
|
617
816
|
for sub in getattr(cmd, "commands", {}).values():
|
|
817
|
+
if getattr(sub, "hidden", False):
|
|
818
|
+
continue
|
|
618
819
|
recursive_help(sub, ctx, name=name)
|
|
619
820
|
|
|
620
821
|
recursive_help(main)
|
lamin_cli/_cache.py
CHANGED
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
|
@@ -31,6 +31,7 @@ def io():
|
|
|
31
31
|
# fmt: on
|
|
32
32
|
def snapshot(upload: bool, track: bool) -> None:
|
|
33
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
|
|
|
@@ -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/_save.py
CHANGED
|
@@ -51,7 +51,7 @@ def parse_uid_from_code(content: str, suffix: str) -> str | None:
|
|
|
51
51
|
elif suffix == ".sh":
|
|
52
52
|
return None
|
|
53
53
|
else:
|
|
54
|
-
raise
|
|
54
|
+
raise click.ClickException(
|
|
55
55
|
"Only .py, .ipynb, .R, .qmd, .Rmd, .sh files are supported for saving"
|
|
56
56
|
" transforms."
|
|
57
57
|
)
|
|
@@ -113,14 +113,14 @@ def save(
|
|
|
113
113
|
ln.Q(name=project) | ln.Q(uid=project)
|
|
114
114
|
).one_or_none()
|
|
115
115
|
if project_record is None:
|
|
116
|
-
raise
|
|
116
|
+
raise click.ClickException(
|
|
117
117
|
f"Project '{project}' not found, either create it with `ln.Project(name='...').save()` or fix typos."
|
|
118
118
|
)
|
|
119
119
|
space_record = None
|
|
120
120
|
if space is not None:
|
|
121
121
|
space_record = ln.Space.filter(ln.Q(name=space) | ln.Q(uid=space)).one_or_none()
|
|
122
122
|
if space_record is None:
|
|
123
|
-
raise
|
|
123
|
+
raise click.ClickException(
|
|
124
124
|
f"Space '{space}' not found, either create it on LaminHub or fix typos."
|
|
125
125
|
)
|
|
126
126
|
branch_record = None
|
|
@@ -129,7 +129,7 @@ def save(
|
|
|
129
129
|
ln.Q(name=branch) | ln.Q(uid=branch)
|
|
130
130
|
).one_or_none()
|
|
131
131
|
if branch_record is None:
|
|
132
|
-
raise
|
|
132
|
+
raise click.ClickException(
|
|
133
133
|
f"Branch '{branch}' not found, either create it with `ln.Branch(name='...').save()` or fix typos."
|
|
134
134
|
)
|
|
135
135
|
|
|
@@ -145,7 +145,7 @@ def save(
|
|
|
145
145
|
.first()
|
|
146
146
|
)
|
|
147
147
|
if revises is None:
|
|
148
|
-
raise
|
|
148
|
+
raise click.ClickException("The stem uid is not found.")
|
|
149
149
|
|
|
150
150
|
if is_cloud_path:
|
|
151
151
|
if key is not None:
|
|
@@ -266,7 +266,30 @@ def save(
|
|
|
266
266
|
.first()
|
|
267
267
|
)
|
|
268
268
|
if revises is None:
|
|
269
|
-
|
|
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.")
|
|
270
293
|
if transform is None:
|
|
271
294
|
if ppath.suffix == ".ipynb":
|
|
272
295
|
from nbproject.dev import read_notebook
|
|
@@ -322,4 +345,6 @@ def save(
|
|
|
322
345
|
)
|
|
323
346
|
return return_code
|
|
324
347
|
else:
|
|
325
|
-
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]
|
|
@@ -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,,
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
lamin_cli/__init__.py,sha256=bd6la8_wxlClB7o8h3FNAjQ6z8HPcZ4QqxvVp4Cvycg,605
|
|
2
|
-
lamin_cli/__main__.py,sha256=DEifHuxyyffXHVpBjn9-LVpVUsuSnRhicxTfsuZ0YUw,20791
|
|
3
|
-
lamin_cli/_annotate.py,sha256=ZD76__K-mQt7UpYqyM1I2lKCs-DraTmnkjsByIHmD-g,1839
|
|
4
|
-
lamin_cli/_cache.py,sha256=oplwE8AcS_9PYptQUZxff2qTIdNFS81clGPkJNWk098,800
|
|
5
|
-
lamin_cli/_context.py,sha256=JLhv98isasX_M_o6tIDkeBWcNH8ryRrl3nk2rmwsuD4,2392
|
|
6
|
-
lamin_cli/_delete.py,sha256=1wp8LQdiWHJznRrm4abxEhApB9derBWEm25ufrzjvIg,1531
|
|
7
|
-
lamin_cli/_io.py,sha256=DboDoecZN5OLCHe5vJzOLuwztW8hxbdRffWj1kmJ9WI,5036
|
|
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=8dM4blz6caKJtbqND-6xJXUMlGIvo2FwvSu1CDcBPig,12517
|
|
11
|
-
lamin_cli/_settings.py,sha256=nqVM8FO9kjL0SXYqP8x-Xfww6Qth4I86HKKeTirsgc4,2657
|
|
12
|
-
lamin_cli/urls.py,sha256=gc72s4SpaAQA8J50CtCIWlr49DWOSL_a6OM9lXfPouM,367
|
|
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=yv2mLkHdRUao_IkZO_4Swu5w4Nieh1gMrBwPbyn4c1o,1544
|
|
16
|
-
lamin_cli/compute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
lamin_cli/compute/modal.py,sha256=QnR7GyyvWWWkLnou95HxS9xxSQfw1k-SiefM_qRVnU0,6010
|
|
18
|
-
lamin_cli-1.11.0.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
19
|
-
lamin_cli-1.11.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
20
|
-
lamin_cli-1.11.0.dist-info/WHEEL,sha256=ssQ84EZ5gH1pCOujd3iW7HClo_O_aDaClUbX4B8bjKY,100
|
|
21
|
-
lamin_cli-1.11.0.dist-info/METADATA,sha256=4TK6hWuABuBxr1PYF_iBpm7KP8xNH7g4LjMWUkHgDKE,338
|
|
22
|
-
lamin_cli-1.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|