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/__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
|
@@ -42,6 +42,10 @@ COMMAND_GROUPS = {
|
|
|
42
42
|
"name": "Load, save, create & delete data",
|
|
43
43
|
"commands": ["load", "save", "create", "delete"],
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Tracking within shell scripts",
|
|
47
|
+
"commands": ["track", "finish"],
|
|
48
|
+
},
|
|
45
49
|
{
|
|
46
50
|
"name": "Describe, annotate & list data",
|
|
47
51
|
"commands": ["describe", "annotate", "list"],
|
|
@@ -49,9 +53,7 @@ COMMAND_GROUPS = {
|
|
|
49
53
|
{
|
|
50
54
|
"name": "Configure",
|
|
51
55
|
"commands": [
|
|
52
|
-
"checkout",
|
|
53
56
|
"switch",
|
|
54
|
-
"cache",
|
|
55
57
|
"settings",
|
|
56
58
|
"migrate",
|
|
57
59
|
],
|
|
@@ -108,7 +110,6 @@ else:
|
|
|
108
110
|
|
|
109
111
|
from lamindb_setup._silence_loggers import silence_loggers
|
|
110
112
|
|
|
111
|
-
from lamin_cli._cache import cache
|
|
112
113
|
from lamin_cli._io import io
|
|
113
114
|
from lamin_cli._migration import migrate
|
|
114
115
|
from lamin_cli._settings import settings
|
|
@@ -142,7 +143,7 @@ def login(user: str, key: str | None):
|
|
|
142
143
|
|
|
143
144
|
After authenticating once, you can re-authenticate and switch between accounts via `lamin login myhandle`.
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
→ Python/R alternative: {func}`~lamindb.setup.login`
|
|
146
147
|
"""
|
|
147
148
|
return login_(user, key=key)
|
|
148
149
|
|
|
@@ -177,9 +178,19 @@ def init(
|
|
|
177
178
|
db: str | None,
|
|
178
179
|
modules: str | None,
|
|
179
180
|
):
|
|
180
|
-
"""
|
|
181
|
+
"""Initialize an instance.
|
|
182
|
+
|
|
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
|
+
```
|
|
181
192
|
|
|
182
|
-
|
|
193
|
+
→ Python/R alternative: {func}`~lamindb.setup.init`
|
|
183
194
|
"""
|
|
184
195
|
return init_(storage=storage, db=db, modules=modules, name=name)
|
|
185
196
|
|
|
@@ -187,73 +198,108 @@ def init(
|
|
|
187
198
|
# fmt: off
|
|
188
199
|
@main.command()
|
|
189
200
|
@click.argument("instance", type=str)
|
|
190
|
-
@click.option("--use_proxy_db", is_flag=True, help="Use proxy database connection.")
|
|
191
201
|
# fmt: on
|
|
192
|
-
def connect(instance: str
|
|
193
|
-
"""
|
|
202
|
+
def connect(instance: str):
|
|
203
|
+
"""Set a default instance for auto-connection.
|
|
194
204
|
|
|
195
|
-
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.
|
|
196
206
|
|
|
197
|
-
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:
|
|
198
208
|
|
|
199
|
-
|
|
209
|
+
```
|
|
210
|
+
lamin connect laminlabs/cellxgene
|
|
211
|
+
lamin connect https://lamin.ai/laminlabs/cellxgene
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
→ Python/R alternative: {func}`~lamindb.connect` the global default database or a database object via {class}`~lamindb.DB`
|
|
200
215
|
"""
|
|
201
|
-
return connect_(instance
|
|
216
|
+
return connect_(instance)
|
|
202
217
|
|
|
203
218
|
|
|
204
219
|
@main.command()
|
|
205
220
|
def disconnect():
|
|
206
|
-
"""
|
|
221
|
+
"""Unset the default instance for auto-connection.
|
|
222
|
+
|
|
223
|
+
Python/R sessions and CLI commands will no longer auto-connect to a LaminDB instance.
|
|
207
224
|
|
|
208
|
-
|
|
225
|
+
For example:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
lamin disconnect
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
→ Python/R alternative: {func}`~lamindb.setup.disconnect`
|
|
209
232
|
"""
|
|
210
233
|
return disconnect_()
|
|
211
234
|
|
|
212
235
|
|
|
213
236
|
# fmt: off
|
|
214
237
|
@main.command()
|
|
215
|
-
@click.argument("
|
|
216
|
-
@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.")
|
|
217
242
|
# fmt: on
|
|
218
|
-
def create(
|
|
219
|
-
"""
|
|
243
|
+
def create(
|
|
244
|
+
registry: Literal["branch", "project"],
|
|
245
|
+
name: str | None,
|
|
246
|
+
name_opt: str | None,
|
|
247
|
+
):
|
|
248
|
+
"""Create an object.
|
|
220
249
|
|
|
221
250
|
Currently only supports creating branches and projects.
|
|
222
251
|
|
|
223
252
|
```
|
|
224
|
-
lamin create branch
|
|
225
|
-
lamin create project
|
|
253
|
+
lamin create branch my_branch
|
|
254
|
+
lamin create project my_project
|
|
226
255
|
```
|
|
256
|
+
|
|
257
|
+
→ Python/R alternative: {class}`~lamindb.Branch` and {class}`~lamindb.Project`.
|
|
227
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
|
+
|
|
228
271
|
from lamindb.models import Branch, Project
|
|
229
272
|
|
|
230
|
-
if
|
|
231
|
-
record = Branch(name=
|
|
232
|
-
elif
|
|
233
|
-
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()
|
|
234
277
|
else:
|
|
235
|
-
raise NotImplementedError(f"Creating {
|
|
236
|
-
logger.important(f"created {
|
|
278
|
+
raise NotImplementedError(f"Creating {registry} object is not implemented.")
|
|
279
|
+
logger.important(f"created {registry}: {record.name}")
|
|
237
280
|
|
|
238
281
|
|
|
239
282
|
# fmt: off
|
|
240
283
|
@main.command(name="list")
|
|
241
|
-
@click.argument("
|
|
242
|
-
@click.option("--name", type=str, default=None, help="A name.")
|
|
284
|
+
@click.argument("registry", type=str)
|
|
243
285
|
# fmt: on
|
|
244
|
-
def list_(
|
|
245
|
-
"""List
|
|
286
|
+
def list_(registry: Literal["branch", "space"]):
|
|
287
|
+
"""List objects.
|
|
288
|
+
|
|
289
|
+
For example:
|
|
246
290
|
|
|
247
291
|
```
|
|
248
292
|
lamin list branch
|
|
249
293
|
lamin list space
|
|
250
294
|
```
|
|
295
|
+
|
|
296
|
+
→ Python/R alternative: {method}`~lamindb.Branch.to_dataframe()`
|
|
251
297
|
"""
|
|
252
|
-
assert
|
|
298
|
+
assert registry in {"branch", "space"}, "Currently only supports listing branches and spaces."
|
|
253
299
|
|
|
254
300
|
from lamindb.models import Branch, Space
|
|
255
301
|
|
|
256
|
-
if
|
|
302
|
+
if registry == "branch":
|
|
257
303
|
print(Branch.to_dataframe())
|
|
258
304
|
else:
|
|
259
305
|
print(Space.to_dataframe())
|
|
@@ -261,28 +307,56 @@ def list_(entity: Literal["branch"], name: str | None = None):
|
|
|
261
307
|
|
|
262
308
|
# fmt: off
|
|
263
309
|
@main.command()
|
|
264
|
-
@click.
|
|
265
|
-
@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.")
|
|
266
315
|
# fmt: on
|
|
267
|
-
def switch(
|
|
316
|
+
def switch(
|
|
317
|
+
registry: Literal["branch", "space"] | None,
|
|
318
|
+
name: str | None,
|
|
319
|
+
branch: str | None,
|
|
320
|
+
space: str | None,
|
|
321
|
+
):
|
|
268
322
|
"""Switch between branches or spaces.
|
|
269
323
|
|
|
324
|
+
Python/R sessions and CLI commands will use the current default branch or space, for example:
|
|
325
|
+
|
|
270
326
|
```
|
|
271
|
-
lamin switch
|
|
272
|
-
lamin switch
|
|
327
|
+
lamin switch branch my_branch
|
|
328
|
+
lamin switch space our_space
|
|
273
329
|
```
|
|
330
|
+
|
|
331
|
+
→ Python/R alternative: {attr}`~lamindb.setup.core.SetupSettings.branch` and {attr}`~lamindb.setup.core.SetupSettings.space`
|
|
274
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
|
+
|
|
275
347
|
from lamindb.setup import switch as switch_
|
|
276
348
|
|
|
277
349
|
switch_(branch=branch, space=space)
|
|
278
350
|
|
|
279
351
|
|
|
280
352
|
@main.command()
|
|
281
|
-
@click.option("--schema", is_flag=True, help="View database schema.")
|
|
353
|
+
@click.option("--schema", is_flag=True, help="View database schema via Django plugin.")
|
|
282
354
|
def info(schema: bool):
|
|
283
|
-
"""Show info about the
|
|
355
|
+
"""Show info about the instance, development & cache directories, branch, space, and user.
|
|
284
356
|
|
|
285
|
-
|
|
357
|
+
Manage settings via [lamin settings](https://docs.lamin.ai/cli#settings).
|
|
358
|
+
|
|
359
|
+
→ Python/R alternative: {func}`~lamindb.setup.settings`
|
|
286
360
|
"""
|
|
287
361
|
if schema:
|
|
288
362
|
from lamindb_setup._schema import view
|
|
@@ -297,31 +371,42 @@ def info(schema: bool):
|
|
|
297
371
|
|
|
298
372
|
# fmt: off
|
|
299
373
|
@main.command()
|
|
374
|
+
# entity can be a registry or an object in the registry
|
|
300
375
|
@click.argument("entity", type=str)
|
|
301
376
|
@click.option("--name", type=str, default=None)
|
|
302
377
|
@click.option("--uid", type=str, default=None)
|
|
303
|
-
@click.option("--
|
|
378
|
+
@click.option("--key", type=str, default=None, help="The key for the entity (artifact, transform).")
|
|
304
379
|
@click.option("--permanent", is_flag=True, default=None, help="Permanently delete the entity where applicable, e.g., for artifact, transform, collection.")
|
|
305
380
|
@click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation (only relevant for instance).")
|
|
306
381
|
# fmt: on
|
|
307
|
-
def delete(entity: str, name: str | None = None, uid: str | None = None, slug: str | None = None, permanent: bool | None = None, force: bool = False):
|
|
308
|
-
"""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.
|
|
309
384
|
|
|
310
385
|
Currently supported: `branch`, `artifact`, `transform`, `collection`, and `instance`. For example:
|
|
311
386
|
|
|
312
387
|
```
|
|
313
|
-
|
|
314
|
-
lamin delete
|
|
388
|
+
# via --key or --name
|
|
389
|
+
lamin delete artifact --key mydatasets/mytable.parquet
|
|
390
|
+
lamin delete transform --key myanalyses/analysis.ipynb
|
|
315
391
|
lamin delete branch --name my_branch
|
|
316
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
|
|
317
399
|
```
|
|
400
|
+
|
|
401
|
+
→ Python/R alternative: {method}`~lamindb.SQLRecord.delete` and {func}`~lamindb.setup.delete`
|
|
318
402
|
"""
|
|
319
403
|
from lamin_cli._delete import delete as delete_
|
|
320
404
|
|
|
321
|
-
return delete_(entity=entity, name=name, uid=uid,
|
|
405
|
+
return delete_(entity=entity, name=name, uid=uid, key=key, permanent=permanent, force=force)
|
|
322
406
|
|
|
323
407
|
|
|
324
408
|
@main.command()
|
|
409
|
+
# entity can be a registry or an object in the registry
|
|
325
410
|
@click.argument("entity", type=str, required=False)
|
|
326
411
|
@click.option("--uid", help="The uid for the entity.")
|
|
327
412
|
@click.option("--key", help="The key for the entity.")
|
|
@@ -329,23 +414,23 @@ def delete(entity: str, name: str | None = None, uid: str | None = None, slug: s
|
|
|
329
414
|
"--with-env", is_flag=True, help="Also return the environment for a tranform."
|
|
330
415
|
)
|
|
331
416
|
def load(entity: str | None = None, uid: str | None = None, key: str | None = None, with_env: bool = False):
|
|
332
|
-
"""
|
|
417
|
+
"""Sync a file/folder into a local cache (artifacts) or development directory (transforms).
|
|
333
418
|
|
|
334
419
|
Pass a URL or `--key`. For example:
|
|
335
420
|
|
|
336
421
|
```
|
|
337
|
-
|
|
422
|
+
# via key
|
|
338
423
|
lamin load --key mydatasets/mytable.parquet
|
|
339
424
|
lamin load --key analysis.ipynb
|
|
340
425
|
lamin load --key myanalyses/analysis.ipynb --with-env
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
You can also pass a uid and the entity type:
|
|
344
|
-
|
|
345
|
-
```
|
|
426
|
+
# via registry and --uid
|
|
346
427
|
lamin load artifact --uid e2G7k9EVul4JbfsE
|
|
347
428
|
lamin load transform --uid Vul4JbfsEYAy5
|
|
429
|
+
# via URL
|
|
430
|
+
lamin load https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsE
|
|
348
431
|
```
|
|
432
|
+
|
|
433
|
+
→ Python/R alternative: {func}`~lamindb.Artifact.load`, no equivalent for transforms
|
|
349
434
|
"""
|
|
350
435
|
from lamin_cli._load import load as load_
|
|
351
436
|
if entity is not None:
|
|
@@ -377,30 +462,36 @@ def _describe(entity: str = "artifact", uid: str | None = None, key: str | None
|
|
|
377
462
|
|
|
378
463
|
|
|
379
464
|
@main.command()
|
|
465
|
+
# entity can be a registry or an object in the registry
|
|
380
466
|
@click.argument("entity", type=str, default="artifact")
|
|
381
467
|
@click.option("--uid", help="The uid for the entity.")
|
|
382
468
|
@click.option("--key", help="The key for the entity.")
|
|
383
469
|
def describe(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
384
|
-
"""Describe an
|
|
470
|
+
"""Describe an object.
|
|
385
471
|
|
|
386
472
|
Examples:
|
|
387
473
|
|
|
388
474
|
```
|
|
475
|
+
# via --key
|
|
389
476
|
lamin describe --key example_datasets/mini_immuno/dataset1.h5ad
|
|
477
|
+
# via registry and --uid
|
|
478
|
+
lamin describe artifact --uid e2G7k9EVul4JbfsE
|
|
479
|
+
# via URL
|
|
390
480
|
lamin describe https://lamin.ai/laminlabs/lamin-site-assets/artifact/6sofuDVvTANB0f48
|
|
391
481
|
```
|
|
392
482
|
|
|
393
|
-
|
|
483
|
+
→ Python/R alternative: {meth}`~lamindb.Artifact.describe`
|
|
394
484
|
"""
|
|
395
485
|
_describe(entity=entity, uid=uid, key=key)
|
|
396
486
|
|
|
397
487
|
|
|
398
488
|
@main.command()
|
|
489
|
+
# entity can be a registry or an object in the registry
|
|
399
490
|
@click.argument("entity", type=str, default="artifact")
|
|
400
491
|
@click.option("--uid", help="The uid for the entity.")
|
|
401
492
|
@click.option("--key", help="The key for the entity.")
|
|
402
493
|
def get(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
403
|
-
"""Query metadata about an
|
|
494
|
+
"""Query metadata about an object.
|
|
404
495
|
|
|
405
496
|
Currently equivalent to `lamin describe`.
|
|
406
497
|
"""
|
|
@@ -416,11 +507,25 @@ def get(entity: str = "artifact", uid: str | None = None, key: str | None = None
|
|
|
416
507
|
@click.option("--project", type=str, default=None, help="A valid project name or uid.")
|
|
417
508
|
@click.option("--space", type=str, default=None, help="A valid space name or uid.")
|
|
418
509
|
@click.option("--branch", type=str, default=None, help="A valid branch name or uid.")
|
|
419
|
-
@click.option(
|
|
420
|
-
|
|
421
|
-
"""
|
|
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.
|
|
422
527
|
|
|
423
|
-
Example:
|
|
528
|
+
Example:
|
|
424
529
|
|
|
425
530
|
```
|
|
426
531
|
lamin save my_table.csv --key my_tables/my_table.csv --project my_project
|
|
@@ -429,61 +534,124 @@ def save(path: str, key: str, description: str, stem_uid: str, project: str, spa
|
|
|
429
534
|
By passing a `--project` identifier, the artifact will be labeled with the corresponding project.
|
|
430
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`.
|
|
431
536
|
|
|
432
|
-
|
|
537
|
+
Defaults to saving `.py`, `.ipynb`, `.R`, `.Rmd`, and `.qmd` as {class}`~lamindb.Transform` and
|
|
433
538
|
other file types and folders as {class}`~lamindb.Artifact`. You can enforce saving a file as
|
|
434
539
|
an {class}`~lamindb.Artifact` by passing `--registry artifact`.
|
|
540
|
+
|
|
541
|
+
→ Python/R alternative: {class}`~lamindb.Artifact` and {class}`~lamindb.Transform`
|
|
435
542
|
"""
|
|
436
543
|
if save_(path=path, key=key, description=description, stem_uid=stem_uid, project=project, space=space, branch=branch, registry=registry) is not None:
|
|
437
544
|
sys.exit(1)
|
|
438
545
|
|
|
546
|
+
@main.command()
|
|
547
|
+
def track():
|
|
548
|
+
"""Start tracking a run of a shell script.
|
|
549
|
+
|
|
550
|
+
This command works like {func}`~lamindb.track()` in a Python session. Here is an example script:
|
|
551
|
+
|
|
552
|
+
```
|
|
553
|
+
# my_script.sh
|
|
554
|
+
set -e # exit on error
|
|
555
|
+
lamin track # initiate a tracked shell script run
|
|
556
|
+
lamin load --key raw/file1.txt
|
|
557
|
+
# do something
|
|
558
|
+
lamin save processed_file1.txt --key processed/file1.txt
|
|
559
|
+
lamin finish # mark the shell script run as finished
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
If you run that script, it will track the run of the script, and save the input and output artifacts:
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
sh my_script.sh
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
→ Python/R alternative: {func}`~lamindb.track` and {func}`~lamindb.finish` for (non-shell) scripts or notebooks
|
|
569
|
+
"""
|
|
570
|
+
from lamin_cli._context import track as track_
|
|
571
|
+
return track_()
|
|
572
|
+
|
|
439
573
|
|
|
440
574
|
@main.command()
|
|
575
|
+
def finish():
|
|
576
|
+
"""Finish a currently tracked run of a shell script.
|
|
577
|
+
|
|
578
|
+
→ Python/R alternative: {func}`~lamindb.finish()`
|
|
579
|
+
"""
|
|
580
|
+
from lamin_cli._context import finish as finish_
|
|
581
|
+
return finish_()
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@main.command()
|
|
585
|
+
# entity can be a registry or an object in the registry
|
|
441
586
|
@click.argument("entity", type=str, default=None, required=False)
|
|
442
587
|
@click.option("--key", type=str, default=None, help="The key of an artifact or transform.")
|
|
443
588
|
@click.option("--uid", type=str, default=None, help="The uid of an artifact or transform.")
|
|
444
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.")
|
|
445
592
|
@click.option("--features", multiple=True, help="Feature annotations. Supports: feature=value, feature=val1,val2, or feature=\"val1\",\"val2\"")
|
|
446
|
-
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):
|
|
447
594
|
"""Annotate an artifact or transform.
|
|
448
595
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
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,
|
|
452
597
|
|
|
453
598
|
```
|
|
599
|
+
# via --key
|
|
454
600
|
lamin annotate --key raw/sample.fastq --project "My Project"
|
|
601
|
+
lamin annotate --key raw/sample.fastq --ulabel "My ULabel" --record "Experiment 1"
|
|
455
602
|
lamin annotate --key raw/sample.fastq --features perturbation=IFNG,DMSO cell_line=HEK297
|
|
456
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"
|
|
457
608
|
```
|
|
458
|
-
"""
|
|
459
|
-
import lamindb as ln
|
|
460
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
|
+
"""
|
|
461
612
|
from lamin_cli._annotate import _parse_features_list
|
|
462
613
|
from lamin_cli._save import infer_registry_from_path
|
|
463
614
|
|
|
464
|
-
#
|
|
465
|
-
if not
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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)
|
|
473
624
|
else:
|
|
474
|
-
|
|
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
|
+
|
|
475
641
|
if registry == "artifact":
|
|
476
642
|
model = ln.Artifact
|
|
477
643
|
else:
|
|
478
644
|
model = ln.Transform
|
|
479
645
|
|
|
480
|
-
# Get the artifact
|
|
646
|
+
# Get the artifact or transform
|
|
481
647
|
if key is not None:
|
|
482
648
|
artifact = model.get(key=key)
|
|
483
649
|
elif uid is not None:
|
|
484
650
|
artifact = model.get(uid) # do not use uid=uid, because then no truncated uids would work
|
|
485
651
|
else:
|
|
486
|
-
raise ln.errors.InvalidArgument(
|
|
652
|
+
raise ln.errors.InvalidArgument(
|
|
653
|
+
"Either pass a URL as entity or provide --key or --uid"
|
|
654
|
+
)
|
|
487
655
|
|
|
488
656
|
# Handle project annotation
|
|
489
657
|
if project is not None:
|
|
@@ -496,6 +664,28 @@ def annotate(entity: str | None, key: str, uid: str, project: str, features: tup
|
|
|
496
664
|
)
|
|
497
665
|
artifact.projects.add(project_record)
|
|
498
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
|
+
|
|
499
689
|
# Handle feature annotations
|
|
500
690
|
if features:
|
|
501
691
|
feature_dict = _parse_features_list(features)
|
|
@@ -509,7 +699,7 @@ def annotate(entity: str | None, key: str, uid: str, project: str, features: tup
|
|
|
509
699
|
@click.argument("filepath", type=str)
|
|
510
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)
|
|
511
701
|
@click.option("--image-url", type=str, default=None, help="A URL to the base docker image to use.")
|
|
512
|
-
@click.option("--packages", type=str, default=
|
|
702
|
+
@click.option("--packages", type=str, default=None, help="A comma-separated list of additional packages to install.")
|
|
513
703
|
@click.option("--cpu", type=float, default=None, help="Configuration for the CPU.")
|
|
514
704
|
@click.option("--gpu", type=str, default=None, help="The type of GPU to use (only compatible with cuda images).")
|
|
515
705
|
def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gpu: str | None):
|
|
@@ -522,6 +712,8 @@ def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gp
|
|
|
522
712
|
```
|
|
523
713
|
lamin run my_script.py --project my_project
|
|
524
714
|
```
|
|
715
|
+
|
|
716
|
+
→ Python/R alternative: no equivalent
|
|
525
717
|
"""
|
|
526
718
|
from lamin_cli.compute.modal import Runner
|
|
527
719
|
|
|
@@ -550,10 +742,54 @@ def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gp
|
|
|
550
742
|
|
|
551
743
|
|
|
552
744
|
main.add_command(settings)
|
|
553
|
-
main.add_command(cache)
|
|
554
745
|
main.add_command(migrate)
|
|
555
746
|
main.add_command(io)
|
|
556
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
|
+
|
|
557
793
|
# https://stackoverflow.com/questions/57810659/automatically-generate-all-help-documentation-for-click-commands
|
|
558
794
|
# https://claude.ai/chat/73c28487-bec3-4073-8110-50d1a2dd6b84
|
|
559
795
|
def _generate_help():
|
|
@@ -562,6 +798,8 @@ def _generate_help():
|
|
|
562
798
|
def recursive_help(
|
|
563
799
|
cmd: Command, parent: Context | None = None, name: tuple[str, ...] = ()
|
|
564
800
|
):
|
|
801
|
+
if getattr(cmd, "hidden", False):
|
|
802
|
+
return
|
|
565
803
|
ctx = click.Context(cmd, info_name=cmd.name, parent=parent)
|
|
566
804
|
assert cmd.name
|
|
567
805
|
name = (*name, cmd.name)
|
|
@@ -576,6 +814,8 @@ def _generate_help():
|
|
|
576
814
|
}
|
|
577
815
|
|
|
578
816
|
for sub in getattr(cmd, "commands", {}).values():
|
|
817
|
+
if getattr(sub, "hidden", False):
|
|
818
|
+
continue
|
|
579
819
|
recursive_help(sub, ctx, name=name)
|
|
580
820
|
|
|
581
821
|
recursive_help(main)
|