lamin_cli 1.11.0__py2.py3-none-any.whl → 1.12.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lamin_cli/__init__.py +27 -27
- lamin_cli/__main__.py +826 -625
- lamin_cli/_annotate.py +47 -47
- lamin_cli/_cache.py +49 -41
- lamin_cli/_context.py +76 -76
- lamin_cli/_delete.py +85 -44
- lamin_cli/_io.py +147 -144
- lamin_cli/_load.py +203 -203
- lamin_cli/_migration.py +50 -50
- lamin_cli/_save.py +350 -325
- lamin_cli/_settings.py +154 -96
- lamin_cli/clone/_clone_verification.py +56 -56
- lamin_cli/clone/create_sqlite_clone_and_import_db.py +53 -51
- lamin_cli/compute/modal.py +174 -175
- lamin_cli/urls.py +10 -8
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info}/METADATA +3 -2
- lamin_cli-1.12.1.dist-info/RECORD +22 -0
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info}/WHEEL +1 -1
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info/licenses}/LICENSE +201 -201
- lamin_cli-1.11.0.dist-info/RECORD +0 -22
- {lamin_cli-1.11.0.dist-info → lamin_cli-1.12.1.dist-info}/entry_points.txt +0 -0
lamin_cli/__main__.py
CHANGED
|
@@ -1,625 +1,826 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
4
|
-
import os
|
|
5
|
-
import shutil
|
|
6
|
-
import sys
|
|
7
|
-
import warnings
|
|
8
|
-
from collections import OrderedDict
|
|
9
|
-
from functools import wraps
|
|
10
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import TYPE_CHECKING, Literal
|
|
13
|
-
|
|
14
|
-
import lamindb_setup as ln_setup
|
|
15
|
-
from lamin_utils import logger
|
|
16
|
-
from lamindb_setup._init_instance import (
|
|
17
|
-
DOC_DB,
|
|
18
|
-
DOC_INSTANCE_NAME,
|
|
19
|
-
DOC_MODULES,
|
|
20
|
-
DOC_STORAGE_ARG,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
from lamin_cli import connect as connect_
|
|
24
|
-
from lamin_cli import disconnect as disconnect_
|
|
25
|
-
from lamin_cli import init as init_
|
|
26
|
-
from lamin_cli import login as login_
|
|
27
|
-
from lamin_cli import logout as logout_
|
|
28
|
-
from lamin_cli import save as save_
|
|
29
|
-
|
|
30
|
-
from .urls import decompose_url
|
|
31
|
-
|
|
32
|
-
if TYPE_CHECKING:
|
|
33
|
-
from collections.abc import Mapping
|
|
34
|
-
|
|
35
|
-
COMMAND_GROUPS = {
|
|
36
|
-
"lamin": [
|
|
37
|
-
{
|
|
38
|
-
"name": "Manage connections",
|
|
39
|
-
"commands": ["connect", "info", "init", "disconnect"],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "Load, save, create & delete data",
|
|
43
|
-
"commands": ["load", "save", "create", "delete"],
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "Tracking within shell scripts",
|
|
47
|
-
"commands": ["track", "finish"],
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"name": "Describe, annotate & list data",
|
|
51
|
-
"commands": ["describe", "annotate", "list"],
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"name": "Configure",
|
|
55
|
-
"commands": [
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
**kwargs
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
from
|
|
114
|
-
|
|
115
|
-
from lamin_cli.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
from
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@
|
|
173
|
-
@click.option("--
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
```
|
|
228
|
-
lamin
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
lamin
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
@
|
|
285
|
-
|
|
286
|
-
def
|
|
287
|
-
"""
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
@
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"""
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
lamin
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
lamin
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
"""
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
import warnings
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
from functools import wraps
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Literal
|
|
13
|
+
|
|
14
|
+
import lamindb_setup as ln_setup
|
|
15
|
+
from lamin_utils import logger
|
|
16
|
+
from lamindb_setup._init_instance import (
|
|
17
|
+
DOC_DB,
|
|
18
|
+
DOC_INSTANCE_NAME,
|
|
19
|
+
DOC_MODULES,
|
|
20
|
+
DOC_STORAGE_ARG,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from lamin_cli import connect as connect_
|
|
24
|
+
from lamin_cli import disconnect as disconnect_
|
|
25
|
+
from lamin_cli import init as init_
|
|
26
|
+
from lamin_cli import login as login_
|
|
27
|
+
from lamin_cli import logout as logout_
|
|
28
|
+
from lamin_cli import save as save_
|
|
29
|
+
|
|
30
|
+
from .urls import decompose_url
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from collections.abc import Mapping
|
|
34
|
+
|
|
35
|
+
COMMAND_GROUPS = {
|
|
36
|
+
"lamin": [
|
|
37
|
+
{
|
|
38
|
+
"name": "Manage connections",
|
|
39
|
+
"commands": ["connect", "info", "init", "disconnect"],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "Load, save, create & delete data",
|
|
43
|
+
"commands": ["load", "save", "create", "delete"],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Tracking within shell scripts",
|
|
47
|
+
"commands": ["track", "finish"],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "Describe, annotate & list data",
|
|
51
|
+
"commands": ["describe", "annotate", "list"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "Configure",
|
|
55
|
+
"commands": [
|
|
56
|
+
"switch",
|
|
57
|
+
"settings",
|
|
58
|
+
"migrate",
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "Auth",
|
|
63
|
+
"commands": [
|
|
64
|
+
"login",
|
|
65
|
+
"logout",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# https://github.com/ewels/rich-click/issues/19
|
|
72
|
+
# Otherwise rich-click takes over the formatting.
|
|
73
|
+
if os.environ.get("NO_RICH"):
|
|
74
|
+
import click as click
|
|
75
|
+
|
|
76
|
+
class OrderedGroup(click.Group):
|
|
77
|
+
"""Overwrites list_commands to return commands in order of definition."""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
name: str | None = None,
|
|
82
|
+
commands: Mapping[str, click.Command] | None = None,
|
|
83
|
+
**kwargs,
|
|
84
|
+
):
|
|
85
|
+
super().__init__(name, commands, **kwargs)
|
|
86
|
+
self.commands = commands or OrderedDict()
|
|
87
|
+
|
|
88
|
+
def list_commands(self, ctx: click.Context) -> Mapping[str, click.Command]:
|
|
89
|
+
return self.commands
|
|
90
|
+
|
|
91
|
+
lamin_group_decorator = click.group(cls=OrderedGroup)
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
import rich_click as click
|
|
95
|
+
|
|
96
|
+
def lamin_group_decorator(f):
|
|
97
|
+
@click.rich_config(
|
|
98
|
+
help_config=click.RichHelpConfiguration(
|
|
99
|
+
command_groups=COMMAND_GROUPS,
|
|
100
|
+
style_commands_table_column_width_ratio=(1, 10),
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
@click.group()
|
|
104
|
+
@wraps(f)
|
|
105
|
+
def wrapper(*args, **kwargs):
|
|
106
|
+
return f(*args, **kwargs)
|
|
107
|
+
|
|
108
|
+
return wrapper
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
from lamindb_setup._silence_loggers import silence_loggers
|
|
112
|
+
|
|
113
|
+
from lamin_cli._io import io
|
|
114
|
+
from lamin_cli._migration import migrate
|
|
115
|
+
from lamin_cli._settings import settings
|
|
116
|
+
|
|
117
|
+
if TYPE_CHECKING:
|
|
118
|
+
from click import Command, Context
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
lamindb_version = version("lamindb")
|
|
122
|
+
except PackageNotFoundError:
|
|
123
|
+
lamindb_version = "lamindb installation not found"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@lamin_group_decorator
|
|
127
|
+
@click.version_option(version=lamindb_version, prog_name="lamindb")
|
|
128
|
+
def main():
|
|
129
|
+
"""Manage data with LaminDB instances."""
|
|
130
|
+
silence_loggers()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@main.command()
|
|
134
|
+
@click.argument("user", type=str, default=None, required=False)
|
|
135
|
+
@click.option("--key", type=str, default=None, hidden=True, help="The legacy API key.")
|
|
136
|
+
def login(user: str, key: str | None):
|
|
137
|
+
# note that the docstring needs to be synced with ln.setup.login()
|
|
138
|
+
"""Log into LaminHub.
|
|
139
|
+
|
|
140
|
+
`lamin login` prompts for your API key unless you set it via environment variable `LAMIN_API_KEY`.
|
|
141
|
+
|
|
142
|
+
You can create your API key in your account settings on LaminHub (top right corner).
|
|
143
|
+
|
|
144
|
+
After authenticating once, you can re-authenticate and switch between accounts via `lamin login myhandle`.
|
|
145
|
+
|
|
146
|
+
→ Python/R alternative: {func}`~lamindb.setup.login`
|
|
147
|
+
"""
|
|
148
|
+
return login_(user, key=key)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@main.command()
|
|
152
|
+
def logout():
|
|
153
|
+
"""Log out of LaminHub."""
|
|
154
|
+
return logout_()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def schema_to_modules_callback(ctx, param, value):
|
|
158
|
+
if param.name == "schema" and value is not None:
|
|
159
|
+
warnings.warn(
|
|
160
|
+
"The --schema option is deprecated and will be removed in a future version."
|
|
161
|
+
" Please use --modules instead.",
|
|
162
|
+
DeprecationWarning,
|
|
163
|
+
stacklevel=2,
|
|
164
|
+
)
|
|
165
|
+
return value
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# fmt: off
|
|
169
|
+
@main.command()
|
|
170
|
+
@click.option("--storage", type=str, default = ".", help=DOC_STORAGE_ARG)
|
|
171
|
+
@click.option("--name", type=str, default=None, help=DOC_INSTANCE_NAME)
|
|
172
|
+
@click.option("--db", type=str, default=None, help=DOC_DB)
|
|
173
|
+
@click.option("--modules", type=str, default=None, help=DOC_MODULES)
|
|
174
|
+
# fmt: on
|
|
175
|
+
def init(
|
|
176
|
+
storage: str,
|
|
177
|
+
name: str | None,
|
|
178
|
+
db: str | None,
|
|
179
|
+
modules: str | None,
|
|
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
|
+
```
|
|
192
|
+
|
|
193
|
+
→ Python/R alternative: {func}`~lamindb.setup.init`
|
|
194
|
+
"""
|
|
195
|
+
return init_(storage=storage, db=db, modules=modules, name=name)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# fmt: off
|
|
199
|
+
@main.command()
|
|
200
|
+
@click.argument("instance", type=str)
|
|
201
|
+
# fmt: on
|
|
202
|
+
def connect(instance: str):
|
|
203
|
+
"""Set a default instance for auto-connection.
|
|
204
|
+
|
|
205
|
+
Python/R sessions and CLI commands will then auto-connect to this LaminDB instance.
|
|
206
|
+
|
|
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
|
+
```
|
|
213
|
+
|
|
214
|
+
→ Python/R alternative: {func}`~lamindb.connect` the global default database or a database object via {class}`~lamindb.DB`
|
|
215
|
+
"""
|
|
216
|
+
return connect_(instance)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@main.command()
|
|
220
|
+
def disconnect():
|
|
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.
|
|
224
|
+
|
|
225
|
+
For example:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
lamin disconnect
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
→ Python/R alternative: {func}`~lamindb.setup.disconnect`
|
|
232
|
+
"""
|
|
233
|
+
return disconnect_()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# fmt: off
|
|
237
|
+
@main.command()
|
|
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.")
|
|
242
|
+
# fmt: on
|
|
243
|
+
def create(
|
|
244
|
+
registry: Literal["branch", "project"],
|
|
245
|
+
name: str | None,
|
|
246
|
+
name_opt: str | None,
|
|
247
|
+
):
|
|
248
|
+
"""Create an object.
|
|
249
|
+
|
|
250
|
+
Currently only supports creating branches and projects.
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
lamin create branch my_branch
|
|
254
|
+
lamin create project my_project
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
→ Python/R alternative: {class}`~lamindb.Branch` and {class}`~lamindb.Project`.
|
|
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
|
+
|
|
271
|
+
from lamindb.models import Branch, Project
|
|
272
|
+
|
|
273
|
+
if registry == "branch":
|
|
274
|
+
record = Branch(name=resolved_name).save()
|
|
275
|
+
elif registry == "project":
|
|
276
|
+
record = Project(name=resolved_name).save()
|
|
277
|
+
else:
|
|
278
|
+
raise NotImplementedError(f"Creating {registry} object is not implemented.")
|
|
279
|
+
logger.important(f"created {registry}: {record.name}")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# fmt: off
|
|
283
|
+
@main.command(name="list")
|
|
284
|
+
@click.argument("registry", type=str)
|
|
285
|
+
# fmt: on
|
|
286
|
+
def list_(registry: Literal["branch", "space"]):
|
|
287
|
+
"""List objects.
|
|
288
|
+
|
|
289
|
+
For example:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
lamin list branch
|
|
293
|
+
lamin list space
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
→ Python/R alternative: {meth}`~lamindb.Branch.to_dataframe()`
|
|
297
|
+
"""
|
|
298
|
+
assert registry in {"branch", "space"}, "Currently only supports listing branches and spaces."
|
|
299
|
+
|
|
300
|
+
from lamindb.models import Branch, Space
|
|
301
|
+
|
|
302
|
+
if registry == "branch":
|
|
303
|
+
print(Branch.to_dataframe())
|
|
304
|
+
else:
|
|
305
|
+
print(Space.to_dataframe())
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# fmt: off
|
|
309
|
+
@main.command()
|
|
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.")
|
|
315
|
+
# fmt: on
|
|
316
|
+
def switch(
|
|
317
|
+
registry: Literal["branch", "space"] | None,
|
|
318
|
+
name: str | None,
|
|
319
|
+
branch: str | None,
|
|
320
|
+
space: str | None,
|
|
321
|
+
):
|
|
322
|
+
"""Switch between branches or spaces.
|
|
323
|
+
|
|
324
|
+
Python/R sessions and CLI commands will use the current default branch or space, for example:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
lamin switch branch my_branch
|
|
328
|
+
lamin switch space our_space
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
→ Python/R alternative: {attr}`~lamindb.setup.core.SetupSettings.branch` and {attr}`~lamindb.setup.core.SetupSettings.space`
|
|
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
|
+
|
|
347
|
+
from lamindb.setup import switch as switch_
|
|
348
|
+
|
|
349
|
+
switch_(branch=branch, space=space)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@main.command()
|
|
353
|
+
@click.option("--schema", is_flag=True, help="View database schema via Django plugin.")
|
|
354
|
+
def info(schema: bool):
|
|
355
|
+
"""Show info about the instance, development & cache directories, branch, space, and user.
|
|
356
|
+
|
|
357
|
+
Manage settings via [lamin settings](https://docs.lamin.ai/cli#settings).
|
|
358
|
+
|
|
359
|
+
→ Python/R alternative: {func}`~lamindb.setup.settings`
|
|
360
|
+
"""
|
|
361
|
+
if schema:
|
|
362
|
+
from lamindb_setup._schema import view
|
|
363
|
+
|
|
364
|
+
click.echo("Open in browser: http://127.0.0.1:8000/schema/")
|
|
365
|
+
return view()
|
|
366
|
+
else:
|
|
367
|
+
from lamindb_setup import settings as settings_
|
|
368
|
+
|
|
369
|
+
click.echo(settings_)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# fmt: off
|
|
373
|
+
@main.command()
|
|
374
|
+
# entity can be a registry or an object in the registry
|
|
375
|
+
@click.argument("entity", type=str)
|
|
376
|
+
@click.option("--name", type=str, default=None)
|
|
377
|
+
@click.option("--uid", type=str, default=None)
|
|
378
|
+
@click.option("--key", type=str, default=None, help="The key for the entity (artifact, transform).")
|
|
379
|
+
@click.option("--permanent", is_flag=True, default=None, help="Permanently delete the entity where applicable, e.g., for artifact, transform, collection.")
|
|
380
|
+
@click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation (only relevant for instance).")
|
|
381
|
+
# fmt: on
|
|
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.
|
|
384
|
+
|
|
385
|
+
Currently supported: `branch`, `artifact`, `transform`, `collection`, and `instance`. For example:
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
# via --key or --name
|
|
389
|
+
lamin delete artifact --key mydatasets/mytable.parquet
|
|
390
|
+
lamin delete transform --key myanalyses/analysis.ipynb
|
|
391
|
+
lamin delete branch --name my_branch
|
|
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
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
→ Python/R alternative: {meth}`~lamindb.SQLRecord.delete` and {func}`~lamindb.setup.delete`
|
|
402
|
+
"""
|
|
403
|
+
from lamin_cli._delete import delete as delete_
|
|
404
|
+
|
|
405
|
+
return delete_(entity=entity, name=name, uid=uid, key=key, permanent=permanent, force=force)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@main.command()
|
|
409
|
+
# entity can be a registry or an object in the registry
|
|
410
|
+
@click.argument("entity", type=str, required=False)
|
|
411
|
+
@click.option("--uid", help="The uid for the entity.")
|
|
412
|
+
@click.option("--key", help="The key for the entity.")
|
|
413
|
+
@click.option(
|
|
414
|
+
"--with-env", is_flag=True, help="Also return the environment for a tranform."
|
|
415
|
+
)
|
|
416
|
+
def load(entity: str | None = None, uid: str | None = None, key: str | None = None, with_env: bool = False):
|
|
417
|
+
"""Sync a file/folder into a local cache (artifacts) or development directory (transforms).
|
|
418
|
+
|
|
419
|
+
Pass a URL or `--key`. For example:
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
# via key
|
|
423
|
+
lamin load --key mydatasets/mytable.parquet
|
|
424
|
+
lamin load --key analysis.ipynb
|
|
425
|
+
lamin load --key myanalyses/analysis.ipynb --with-env
|
|
426
|
+
# via registry and --uid
|
|
427
|
+
lamin load artifact --uid e2G7k9EVul4JbfsE
|
|
428
|
+
lamin load transform --uid Vul4JbfsEYAy5
|
|
429
|
+
# via URL
|
|
430
|
+
lamin load https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsE
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
→ Python/R alternative: {func}`~lamindb.Artifact.load`, no equivalent for transforms
|
|
434
|
+
"""
|
|
435
|
+
from lamin_cli._load import load as load_
|
|
436
|
+
if entity is not None:
|
|
437
|
+
is_slug = entity.count("/") == 1
|
|
438
|
+
if is_slug:
|
|
439
|
+
from lamindb_setup._connect_instance import _connect_cli
|
|
440
|
+
# for backward compat
|
|
441
|
+
return _connect_cli(entity)
|
|
442
|
+
return load_(entity, uid=uid, key=key, with_env=with_env)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _describe(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
446
|
+
if entity.startswith("https://") and "lamin" in entity:
|
|
447
|
+
url = entity
|
|
448
|
+
instance, entity, uid = decompose_url(url)
|
|
449
|
+
elif entity not in {"artifact"}:
|
|
450
|
+
raise SystemExit("Entity has to be a laminhub URL or 'artifact'")
|
|
451
|
+
else:
|
|
452
|
+
instance = ln_setup.settings.instance.slug
|
|
453
|
+
|
|
454
|
+
ln_setup.connect(instance)
|
|
455
|
+
import lamindb as ln
|
|
456
|
+
|
|
457
|
+
if uid is not None:
|
|
458
|
+
artifact = ln.Artifact.get(uid)
|
|
459
|
+
else:
|
|
460
|
+
artifact = ln.Artifact.get(key=key)
|
|
461
|
+
artifact.describe()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@main.command()
|
|
465
|
+
# entity can be a registry or an object in the registry
|
|
466
|
+
@click.argument("entity", type=str, default="artifact")
|
|
467
|
+
@click.option("--uid", help="The uid for the entity.")
|
|
468
|
+
@click.option("--key", help="The key for the entity.")
|
|
469
|
+
def describe(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
470
|
+
"""Describe an object.
|
|
471
|
+
|
|
472
|
+
Examples:
|
|
473
|
+
|
|
474
|
+
```
|
|
475
|
+
# via --key
|
|
476
|
+
lamin describe --key example_datasets/mini_immuno/dataset1.h5ad
|
|
477
|
+
# via registry and --uid
|
|
478
|
+
lamin describe artifact --uid e2G7k9EVul4JbfsE
|
|
479
|
+
# via URL
|
|
480
|
+
lamin describe https://lamin.ai/laminlabs/lamin-site-assets/artifact/6sofuDVvTANB0f48
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
→ Python/R alternative: {meth}`~lamindb.Artifact.describe`
|
|
484
|
+
"""
|
|
485
|
+
_describe(entity=entity, uid=uid, key=key)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@main.command()
|
|
489
|
+
# entity can be a registry or an object in the registry
|
|
490
|
+
@click.argument("entity", type=str, default="artifact")
|
|
491
|
+
@click.option("--uid", help="The uid for the entity.")
|
|
492
|
+
@click.option("--key", help="The key for the entity.")
|
|
493
|
+
def get(entity: str = "artifact", uid: str | None = None, key: str | None = None):
|
|
494
|
+
"""Query metadata about an object.
|
|
495
|
+
|
|
496
|
+
Currently equivalent to `lamin describe`.
|
|
497
|
+
"""
|
|
498
|
+
logger.warning("please use `lamin describe` instead of `lamin get` to describe")
|
|
499
|
+
_describe(entity=entity, uid=uid, key=key)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@main.command()
|
|
503
|
+
@click.argument("path", type=str)
|
|
504
|
+
@click.option("--key", type=str, default=None, help="The key of the artifact or transform.")
|
|
505
|
+
@click.option("--description", type=str, default=None, help="A description of the artifact or transform.")
|
|
506
|
+
@click.option("--stem-uid", type=str, default=None, help="The stem uid of the artifact or transform.")
|
|
507
|
+
@click.option("--project", type=str, default=None, help="A valid project name or uid.")
|
|
508
|
+
@click.option("--space", type=str, default=None, help="A valid space name or uid.")
|
|
509
|
+
@click.option("--branch", type=str, default=None, help="A valid branch name or uid.")
|
|
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.
|
|
527
|
+
|
|
528
|
+
Example:
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
lamin save my_table.csv --key my_tables/my_table.csv --project my_project
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
By passing a `--project` identifier, the artifact will be labeled with the corresponding project.
|
|
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`.
|
|
536
|
+
|
|
537
|
+
Defaults to saving `.py`, `.ipynb`, `.R`, `.Rmd`, and `.qmd` as {class}`~lamindb.Transform` and
|
|
538
|
+
other file types and folders as {class}`~lamindb.Artifact`. You can enforce saving a file as
|
|
539
|
+
an {class}`~lamindb.Artifact` by passing `--registry artifact`.
|
|
540
|
+
|
|
541
|
+
→ Python/R alternative: {class}`~lamindb.Artifact` and {class}`~lamindb.Transform`
|
|
542
|
+
"""
|
|
543
|
+
if save_(path=path, key=key, description=description, stem_uid=stem_uid, project=project, space=space, branch=branch, registry=registry) is not None:
|
|
544
|
+
sys.exit(1)
|
|
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
|
+
|
|
573
|
+
|
|
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
|
|
586
|
+
@click.argument("entity", type=str, default=None, required=False)
|
|
587
|
+
@click.option("--key", type=str, default=None, help="The key of an artifact or transform.")
|
|
588
|
+
@click.option("--uid", type=str, default=None, help="The uid of an artifact or transform.")
|
|
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.")
|
|
592
|
+
@click.option("--features", multiple=True, help="Feature annotations. Supports: feature=value, feature=val1,val2, or feature=\"val1\",\"val2\"")
|
|
593
|
+
def annotate(entity: str | None, key: str, uid: str, project: str, ulabel: str, record: str, features: tuple):
|
|
594
|
+
"""Annotate an artifact or transform.
|
|
595
|
+
|
|
596
|
+
You can annotate with projects, ulabels, records, and valid features & values. For example,
|
|
597
|
+
|
|
598
|
+
```
|
|
599
|
+
# via --key
|
|
600
|
+
lamin annotate --key raw/sample.fastq --project "My Project"
|
|
601
|
+
lamin annotate --key raw/sample.fastq --ulabel "My ULabel" --record "Experiment 1"
|
|
602
|
+
lamin annotate --key raw/sample.fastq --features perturbation=IFNG,DMSO cell_line=HEK297
|
|
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"
|
|
608
|
+
```
|
|
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
|
+
"""
|
|
612
|
+
from lamin_cli._annotate import _parse_features_list
|
|
613
|
+
from lamin_cli._save import infer_registry_from_path
|
|
614
|
+
|
|
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)
|
|
624
|
+
else:
|
|
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
|
+
|
|
641
|
+
if registry == "artifact":
|
|
642
|
+
model = ln.Artifact
|
|
643
|
+
else:
|
|
644
|
+
model = ln.Transform
|
|
645
|
+
|
|
646
|
+
# Get the artifact or transform
|
|
647
|
+
if key is not None:
|
|
648
|
+
artifact = model.get(key=key)
|
|
649
|
+
elif uid is not None:
|
|
650
|
+
artifact = model.get(uid) # do not use uid=uid, because then no truncated uids would work
|
|
651
|
+
else:
|
|
652
|
+
raise ln.errors.InvalidArgument(
|
|
653
|
+
"Either pass a URL as entity or provide --key or --uid"
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
# Handle project annotation
|
|
657
|
+
if project is not None:
|
|
658
|
+
project_record = ln.Project.filter(
|
|
659
|
+
ln.Q(name=project) | ln.Q(uid=project)
|
|
660
|
+
).one_or_none()
|
|
661
|
+
if project_record is None:
|
|
662
|
+
raise ln.errors.InvalidArgument(
|
|
663
|
+
f"Project '{project}' not found, either create it with `ln.Project(name='...').save()` or fix typos."
|
|
664
|
+
)
|
|
665
|
+
artifact.projects.add(project_record)
|
|
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
|
+
|
|
689
|
+
# Handle feature annotations
|
|
690
|
+
if features:
|
|
691
|
+
feature_dict = _parse_features_list(features)
|
|
692
|
+
artifact.features.add_values(feature_dict)
|
|
693
|
+
|
|
694
|
+
artifact_rep = artifact.key if artifact.key else artifact.description if artifact.description else artifact.uid
|
|
695
|
+
logger.important(f"annotated {registry}: {artifact_rep}")
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
@main.command()
|
|
699
|
+
@click.argument("filepath", type=str)
|
|
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)
|
|
701
|
+
@click.option("--image-url", type=str, default=None, help="A URL to the base docker image to use.")
|
|
702
|
+
@click.option("--packages", type=str, default=None, help="A comma-separated list of additional packages to install.")
|
|
703
|
+
@click.option("--cpu", type=float, default=None, help="Configuration for the CPU.")
|
|
704
|
+
@click.option("--gpu", type=str, default=None, help="The type of GPU to use (only compatible with cuda images).")
|
|
705
|
+
def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gpu: str | None):
|
|
706
|
+
"""Run a compute job in the cloud.
|
|
707
|
+
|
|
708
|
+
This is an EXPERIMENTAL feature that enables to run a script on Modal.
|
|
709
|
+
|
|
710
|
+
Example: Given a valid project name "my_project",
|
|
711
|
+
|
|
712
|
+
```
|
|
713
|
+
lamin run my_script.py --project my_project
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
→ Python/R alternative: no equivalent
|
|
717
|
+
"""
|
|
718
|
+
from lamin_cli.compute.modal import Runner
|
|
719
|
+
|
|
720
|
+
default_mount_dir = Path('./modal_mount_dir')
|
|
721
|
+
if not default_mount_dir.is_dir():
|
|
722
|
+
default_mount_dir.mkdir(parents=True, exist_ok=True)
|
|
723
|
+
|
|
724
|
+
shutil.copy(filepath, default_mount_dir)
|
|
725
|
+
|
|
726
|
+
filepath_in_mount_dir = default_mount_dir / Path(filepath).name
|
|
727
|
+
|
|
728
|
+
package_list = []
|
|
729
|
+
if packages:
|
|
730
|
+
package_list = [package.strip() for package in packages.split(',')]
|
|
731
|
+
|
|
732
|
+
runner = Runner(
|
|
733
|
+
local_mount_dir=default_mount_dir,
|
|
734
|
+
app_name=project,
|
|
735
|
+
packages=package_list,
|
|
736
|
+
image_url=image_url,
|
|
737
|
+
cpu=cpu,
|
|
738
|
+
gpu=gpu
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
runner.run(filepath_in_mount_dir)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
main.add_command(settings)
|
|
745
|
+
main.add_command(migrate)
|
|
746
|
+
main.add_command(io)
|
|
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
|
+
|
|
793
|
+
# https://stackoverflow.com/questions/57810659/automatically-generate-all-help-documentation-for-click-commands
|
|
794
|
+
# https://claude.ai/chat/73c28487-bec3-4073-8110-50d1a2dd6b84
|
|
795
|
+
def _generate_help():
|
|
796
|
+
out: dict[str, dict[str, str | None]] = {}
|
|
797
|
+
|
|
798
|
+
def recursive_help(
|
|
799
|
+
cmd: Command, parent: Context | None = None, name: tuple[str, ...] = ()
|
|
800
|
+
):
|
|
801
|
+
if getattr(cmd, "hidden", False):
|
|
802
|
+
return
|
|
803
|
+
ctx = click.Context(cmd, info_name=cmd.name, parent=parent)
|
|
804
|
+
assert cmd.name
|
|
805
|
+
name = (*name, cmd.name)
|
|
806
|
+
command_name = " ".join(name)
|
|
807
|
+
|
|
808
|
+
docstring = inspect.getdoc(cmd.callback)
|
|
809
|
+
usage = cmd.get_help(ctx).split("\n")[0]
|
|
810
|
+
options = cmd.get_help(ctx).split("Options:")[1]
|
|
811
|
+
out[command_name] = {
|
|
812
|
+
"help": usage + "\n\nOptions:" + options,
|
|
813
|
+
"docstring": docstring,
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
for sub in getattr(cmd, "commands", {}).values():
|
|
817
|
+
if getattr(sub, "hidden", False):
|
|
818
|
+
continue
|
|
819
|
+
recursive_help(sub, ctx, name=name)
|
|
820
|
+
|
|
821
|
+
recursive_help(main)
|
|
822
|
+
return out
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
if __name__ == "__main__":
|
|
826
|
+
main()
|