lamindb 1.0.1__py3-none-any.whl → 1.0.3__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.
lamindb/__init__.py CHANGED
@@ -53,7 +53,7 @@ Modules and settings.
53
53
  """
54
54
 
55
55
  # denote a release candidate for 0.1.0 with 0.1rc1, 0.1a1, 0.1b1, etc.
56
- __version__ = "1.0.1"
56
+ __version__ = "1.0.3"
57
57
 
58
58
  from lamindb_setup._check_setup import InstanceNotSetupError as _InstanceNotSetupError
59
59
  from lamindb_setup._check_setup import _check_instance_setup
lamindb/_finish.py CHANGED
@@ -118,9 +118,8 @@ def notebook_to_script(
118
118
  def clean_r_notebook_html(file_path: Path) -> tuple[str | None, Path]:
119
119
  import re
120
120
 
121
- cleaned_content = (
122
- file_path.read_text()
123
- ) # at this point cleaned_content is still raw
121
+ cleaned_content = file_path.read_text()
122
+ # remove title from content
124
123
  pattern_title = r"<title>(.*?)</title>"
125
124
  title_match = re.search(pattern_title, cleaned_content)
126
125
  title_text = None
@@ -129,9 +128,18 @@ def clean_r_notebook_html(file_path: Path) -> tuple[str | None, Path]:
129
128
  pattern_h1 = f"<h1[^>]*>{re.escape(title_text)}</h1>"
130
129
  cleaned_content = re.sub(pattern_title, "", cleaned_content)
131
130
  cleaned_content = re.sub(pattern_h1, "", cleaned_content)
132
- cleaned_content = cleaned_content.replace(
133
- f"NotebookNotSaved: {get_save_notebook_message()}", ""
134
- )
131
+ # remove error message from content
132
+ if "NotebookNotSaved" in cleaned_content:
133
+ orig_error_message = f"NotebookNotSaved: {get_save_notebook_message()}"
134
+ # coming up with the regex for this is a bit tricky due to all the
135
+ # escape characters we'd need to insert into the message; hence,
136
+ # we do this with a replace() instead
137
+ cleaned_content = cleaned_content.replace(orig_error_message, "")
138
+ if "NotebookNotSaved" in cleaned_content:
139
+ orig_error_message = orig_error_message.replace(
140
+ " `finish()`", "\n`finish()`"
141
+ ) # RStudio might insert a newline
142
+ cleaned_content = cleaned_content.replace(orig_error_message, "")
135
143
  cleaned_path = file_path.parent / (f"{file_path.stem}.cleaned{file_path.suffix}")
136
144
  cleaned_path.write_text(cleaned_content)
137
145
  return title_text, cleaned_path
@@ -151,8 +159,6 @@ def save_context_core(
151
159
  format_field_value, # needs to come after lamindb was imported because of CLI use
152
160
  )
153
161
 
154
- from .core._context import context
155
-
156
162
  ln.settings.verbosity = "success"
157
163
 
158
164
  # for scripts, things are easy
@@ -202,7 +208,7 @@ def save_context_core(
202
208
  report_path = filepath.with_suffix(".html")
203
209
  else:
204
210
  logger.warning(
205
- f"no {filepath.with_suffix('.nb.html')} found, save your manually rendered .html report via the CLI: lamin save {filepath}"
211
+ f"no html report found; to attach one, create an .html export for your {filepath.suffix} file and then run: lamin save {filepath}"
206
212
  )
207
213
  if report_path is not None and not from_cli:
208
214
  if get_seconds_since_modified(report_path) > 2 and not ln_setup._TESTING:
@@ -292,10 +298,13 @@ def save_context_core(
292
298
  )
293
299
  report_file.save(upload=True, print_progress=False)
294
300
  run.report = report_file
301
+ if is_r_notebook:
302
+ # this is the "cleaned" report
303
+ report_path.unlink()
295
304
  logger.debug(
296
305
  f"saved transform.latest_run.report: {transform.latest_run.report}"
297
306
  )
298
- run.is_consecutive = is_consecutive
307
+ run._is_consecutive = is_consecutive
299
308
 
300
309
  # save both run & transform records if we arrive here
301
310
  run.save()
@@ -324,7 +333,4 @@ def save_context_core(
324
333
  logger.important(
325
334
  f"if you want to update your {thing} without re-running it, use `lamin save {filepath}`"
326
335
  )
327
- # because run & transform changed, update the global context
328
- context._run = run
329
- context._transform = transform
330
336
  return None
lamindb/core/_context.py CHANGED
@@ -395,6 +395,11 @@ class Context:
395
395
  path = Path(module.__file__)
396
396
  else:
397
397
  path = Path(path)
398
+ # for Rmd and qmd, we could also extract the title
399
+ # we don't do this for now as we're setting the title upon `ln.finish()` or `lamin save`
400
+ # by extracting it from the html while cleaning it: see clean_r_notebook_html()
401
+ # also see the script_to_notebook() in the CLI _load.py where the title is extracted
402
+ # from the source code YAML and updated with the transform description
398
403
  transform_type = "notebook" if path.suffix in {".Rmd", ".qmd"} else "script"
399
404
  reference = None
400
405
  reference_type = None
@@ -489,14 +494,30 @@ class Context:
489
494
  if aux_transform.key in self._path.as_posix():
490
495
  key = aux_transform.key
491
496
  if (
497
+ # if the transform source code wasn't yet saved
492
498
  aux_transform.source_code is None
493
- or aux_transform.hash == hash
499
+ # if the transform source code is unchanged
500
+ # if aux_transform.type == "notebook", we anticipate the user makes changes to the notebook source code
501
+ # in an interactive session, hence we *pro-actively bump* the version number by setting `revises`
502
+ # in the second part of the if condition even though the source code is unchanged at point of running track()
503
+ or (
504
+ aux_transform.hash == hash
505
+ and aux_transform.type != "notebook"
506
+ )
494
507
  ):
495
508
  uid = aux_transform.uid
496
509
  target_transform = aux_transform
497
510
  else:
498
511
  uid = f"{aux_transform.uid[:-4]}{increment_base62(aux_transform.uid[-4:])}"
499
- message = f"there already is a transform with key '{aux_transform.key}', creating new version '{uid}'"
512
+ message = f"there already is a {aux_transform.type} with `key` '{aux_transform.key}'"
513
+ if (
514
+ aux_transform.hash == hash
515
+ and aux_transform.type == "notebook"
516
+ ):
517
+ message += " -- notebook source code is unchanged, but anticipating changes during this run"
518
+ elif aux_transform.hash != hash:
519
+ message += " -- source code changed"
520
+ message += f", creating new version '{uid}'"
500
521
  revises = aux_transform
501
522
  found_key = True
502
523
  break
@@ -508,7 +529,7 @@ class Context:
508
529
  for transform in transforms
509
530
  ]
510
531
  )
511
- message = f"ignoring transform{plural_s} with same filedescription:\n{transforms_str}"
532
+ message = f"ignoring transform{plural_s} with same filename:\n{transforms_str}"
512
533
  if message != "":
513
534
  logger.important(message)
514
535
  self.uid, transform = uid, target_transform
@@ -581,7 +602,7 @@ class Context:
581
602
  # check whether the transform.key is consistent
582
603
  if transform.key != key:
583
604
  raise UpdateContext(get_key_clashing_message(transform, key))
584
- elif transform.description != description:
605
+ elif transform.description != description and description is not None:
585
606
  transform.description = description
586
607
  transform.save()
587
608
  self._logging_message_track += (
@@ -597,7 +618,9 @@ class Context:
597
618
  # check whether transform source code was already saved
598
619
  if transform_was_saved:
599
620
  bump_revision = False
600
- if is_run_from_ipython:
621
+ if transform.type == "notebook":
622
+ # we anticipate the user makes changes to the notebook source code
623
+ # in an interactive session, hence we pro-actively bump the version number
601
624
  bump_revision = True
602
625
  else:
603
626
  hash, _ = hash_file(self._path) # ignore hash_type for now
@@ -609,12 +632,12 @@ class Context:
609
632
  )
610
633
  if bump_revision:
611
634
  change_type = (
612
- "re-running saved notebook"
613
- if is_run_from_ipython
635
+ "re-running notebook with already-saved source code"
636
+ if transform.type == "notebook"
614
637
  else "source code changed"
615
638
  )
616
639
  raise UpdateContext(
617
- f'✗ {change_type}, run: ln.track("{uid[:-4]}{increment_base62(uid[-4:])}")'
640
+ f'✗ {change_type}, please update the `uid` argument in `track()` to "{uid[:-4]}{increment_base62(uid[-4:])}"'
618
641
  )
619
642
  else:
620
643
  self._logging_message_track += f"loaded Transform('{transform.uid}')"
@@ -683,6 +706,13 @@ class Context:
683
706
  )
684
707
  if self.transform.type != "notebook":
685
708
  self._stream_tracker.finish()
709
+ # reset the context attributes so that somebody who runs `track()` after finish
710
+ # starts fresh
711
+ self._uid = None
712
+ self._run = None
713
+ self._transform = None
714
+ self._version = None
715
+ self._description = None
686
716
 
687
717
 
688
718
  context = Context()
@@ -13,6 +13,7 @@ from lamin_utils import colors, logger
13
13
  from lamindb_setup.core._docs import doc_args
14
14
  from lamindb_setup.core.upath import UPath
15
15
 
16
+ from lamindb.base.types import FieldAttr # noqa
16
17
  from lamindb.models import (
17
18
  Artifact,
18
19
  Feature,
@@ -31,11 +32,6 @@ if TYPE_CHECKING:
31
32
 
32
33
  from lamindb_setup.core.types import UPathStr
33
34
  from mudata import MuData
34
- from spatialdata import SpatialData
35
-
36
- from lamindb.base.types import FieldAttr
37
-
38
- from ._spatial import SpatialDataCurator
39
35
 
40
36
 
41
37
  class CurateLookup:
@@ -1720,7 +1716,7 @@ class Curator(BaseCurator):
1720
1716
  @classmethod
1721
1717
  def from_spatialdata(
1722
1718
  cls,
1723
- sdata: SpatialData,
1719
+ sdata,
1724
1720
  var_index: dict[str, FieldAttr],
1725
1721
  categoricals: dict[str, dict[str, FieldAttr]] | None = None,
1726
1722
  using_key: str | None = None,
@@ -1730,7 +1726,7 @@ class Curator(BaseCurator):
1730
1726
  verbosity: str = "hint",
1731
1727
  *,
1732
1728
  sample_metadata_key: str = "sample",
1733
- ) -> SpatialDataCurator:
1729
+ ):
1734
1730
  """Curation flow for a ``Spatialdata`` object.
1735
1731
 
1736
1732
  See also :class:`~lamindb.Curator`.
@@ -0,0 +1,534 @@
1
+ # Generated by Django 5.2 on 2025-01-20 04:42
2
+
3
+ import django.db.models.deletion
4
+ import django.db.models.functions.datetime
5
+ from django.db import migrations, models
6
+
7
+ import lamindb.base.fields
8
+ import lamindb.base.ids
9
+ import lamindb.base.users
10
+ import lamindb.models
11
+
12
+
13
+ class Migration(migrations.Migration):
14
+ dependencies = [
15
+ ("lamindb", "0079_alter_rundata_value_json_and_more"),
16
+ ]
17
+
18
+ operations = [
19
+ migrations.RemoveField(
20
+ model_name="project",
21
+ name="persons",
22
+ ),
23
+ migrations.AddField(
24
+ model_name="project",
25
+ name="is_type",
26
+ field=lamindb.base.fields.BooleanField(
27
+ blank=True, db_index=True, default=None, null=True
28
+ ),
29
+ ),
30
+ migrations.AddField(
31
+ model_name="project",
32
+ name="predecessors",
33
+ field=models.ManyToManyField(
34
+ related_name="successors", to="lamindb.project"
35
+ ),
36
+ ),
37
+ migrations.AlterField(
38
+ model_name="artifact",
39
+ name="uid",
40
+ field=lamindb.base.fields.CharField(
41
+ blank=True,
42
+ db_index=True,
43
+ default=None,
44
+ editable=False,
45
+ max_length=20,
46
+ unique=True,
47
+ ),
48
+ ),
49
+ migrations.AlterField(
50
+ model_name="collection",
51
+ name="uid",
52
+ field=lamindb.base.fields.CharField(
53
+ blank=True,
54
+ db_index=True,
55
+ default=lamindb.base.ids.base62_20,
56
+ editable=False,
57
+ max_length=20,
58
+ unique=True,
59
+ ),
60
+ ),
61
+ migrations.AlterField(
62
+ model_name="feature",
63
+ name="uid",
64
+ field=lamindb.base.fields.CharField(
65
+ blank=True,
66
+ db_index=True,
67
+ default=lamindb.base.ids.base62_12,
68
+ editable=False,
69
+ max_length=12,
70
+ unique=True,
71
+ ),
72
+ ),
73
+ migrations.AlterField(
74
+ model_name="person",
75
+ name="uid",
76
+ field=lamindb.base.fields.CharField(
77
+ blank=True,
78
+ db_index=True,
79
+ default=lamindb.base.ids.base62_8,
80
+ editable=False,
81
+ max_length=8,
82
+ unique=True,
83
+ ),
84
+ ),
85
+ migrations.AlterField(
86
+ model_name="project",
87
+ name="type",
88
+ field=lamindb.base.fields.ForeignKey(
89
+ blank=True,
90
+ null=True,
91
+ on_delete=django.db.models.deletion.PROTECT,
92
+ related_name="records",
93
+ to="lamindb.project",
94
+ ),
95
+ ),
96
+ migrations.AlterField(
97
+ model_name="project",
98
+ name="uid",
99
+ field=lamindb.base.fields.CharField(
100
+ blank=True,
101
+ db_index=True,
102
+ default=lamindb.base.ids.base62_12,
103
+ editable=False,
104
+ max_length=12,
105
+ unique=True,
106
+ ),
107
+ ),
108
+ migrations.AlterField(
109
+ model_name="reference",
110
+ name="uid",
111
+ field=lamindb.base.fields.CharField(
112
+ blank=True,
113
+ db_index=True,
114
+ default=lamindb.base.ids.base62_12,
115
+ editable=False,
116
+ max_length=12,
117
+ unique=True,
118
+ ),
119
+ ),
120
+ migrations.AlterField(
121
+ model_name="run",
122
+ name="uid",
123
+ field=lamindb.base.fields.CharField(
124
+ blank=True,
125
+ db_index=True,
126
+ default=lamindb.base.ids.base62_20,
127
+ editable=False,
128
+ max_length=20,
129
+ unique=True,
130
+ ),
131
+ ),
132
+ migrations.AlterField(
133
+ model_name="schema",
134
+ name="uid",
135
+ field=lamindb.base.fields.CharField(
136
+ blank=True,
137
+ db_index=True,
138
+ default=None,
139
+ editable=False,
140
+ max_length=20,
141
+ unique=True,
142
+ ),
143
+ ),
144
+ migrations.AlterField(
145
+ model_name="space",
146
+ name="uid",
147
+ field=lamindb.base.fields.CharField(
148
+ blank=True,
149
+ db_default="00000000",
150
+ db_index=True,
151
+ default="00000000",
152
+ editable=False,
153
+ max_length=12,
154
+ unique=True,
155
+ ),
156
+ ),
157
+ migrations.AlterField(
158
+ model_name="storage",
159
+ name="uid",
160
+ field=lamindb.base.fields.CharField(
161
+ blank=True,
162
+ db_index=True,
163
+ default=lamindb.base.ids.base62_12,
164
+ editable=False,
165
+ max_length=12,
166
+ unique=True,
167
+ ),
168
+ ),
169
+ migrations.AlterField(
170
+ model_name="tidytable",
171
+ name="uid",
172
+ field=lamindb.base.fields.CharField(
173
+ blank=True,
174
+ db_index=True,
175
+ default=lamindb.base.ids.base62_12,
176
+ editable=False,
177
+ max_length=12,
178
+ unique=True,
179
+ ),
180
+ ),
181
+ migrations.AlterField(
182
+ model_name="transform",
183
+ name="uid",
184
+ field=lamindb.base.fields.CharField(
185
+ blank=True,
186
+ db_index=True,
187
+ default=None,
188
+ editable=False,
189
+ max_length=16,
190
+ unique=True,
191
+ ),
192
+ ),
193
+ migrations.AlterField(
194
+ model_name="ulabel",
195
+ name="uid",
196
+ field=lamindb.base.fields.CharField(
197
+ blank=True,
198
+ db_index=True,
199
+ default=lamindb.base.ids.base62_8,
200
+ editable=False,
201
+ max_length=8,
202
+ unique=True,
203
+ ),
204
+ ),
205
+ migrations.AlterField(
206
+ model_name="user",
207
+ name="uid",
208
+ field=lamindb.base.fields.CharField(
209
+ blank=True,
210
+ db_index=True,
211
+ default=None,
212
+ editable=False,
213
+ max_length=8,
214
+ unique=True,
215
+ ),
216
+ ),
217
+ migrations.CreateModel(
218
+ name="PersonProject",
219
+ fields=[
220
+ (
221
+ "created_at",
222
+ lamindb.base.fields.DateTimeField(
223
+ blank=True,
224
+ db_default=django.db.models.functions.datetime.Now(),
225
+ db_index=True,
226
+ editable=False,
227
+ ),
228
+ ),
229
+ ("id", models.BigAutoField(primary_key=True, serialize=False)),
230
+ (
231
+ "role",
232
+ lamindb.base.fields.CharField(
233
+ blank=True, default=None, max_length=255, null=True
234
+ ),
235
+ ),
236
+ (
237
+ "created_by",
238
+ lamindb.base.fields.ForeignKey(
239
+ blank=True,
240
+ default=lamindb.base.users.current_user_id,
241
+ editable=False,
242
+ on_delete=django.db.models.deletion.PROTECT,
243
+ related_name="+",
244
+ to="lamindb.user",
245
+ ),
246
+ ),
247
+ (
248
+ "person",
249
+ lamindb.base.fields.ForeignKey(
250
+ blank=True,
251
+ on_delete=django.db.models.deletion.CASCADE,
252
+ related_name="links_project",
253
+ to="lamindb.person",
254
+ ),
255
+ ),
256
+ (
257
+ "project",
258
+ lamindb.base.fields.ForeignKey(
259
+ blank=True,
260
+ on_delete=django.db.models.deletion.PROTECT,
261
+ related_name="links_person",
262
+ to="lamindb.project",
263
+ ),
264
+ ),
265
+ (
266
+ "run",
267
+ lamindb.base.fields.ForeignKey(
268
+ blank=True,
269
+ default=lamindb.models.current_run,
270
+ null=True,
271
+ on_delete=django.db.models.deletion.PROTECT,
272
+ related_name="+",
273
+ to="lamindb.run",
274
+ ),
275
+ ),
276
+ ],
277
+ options={
278
+ "unique_together": {("person", "project")},
279
+ },
280
+ bases=(lamindb.models.LinkORM, models.Model),
281
+ ),
282
+ migrations.AddField(
283
+ model_name="project",
284
+ name="people",
285
+ field=models.ManyToManyField(
286
+ related_name="projects",
287
+ through="lamindb.PersonProject",
288
+ to="lamindb.person",
289
+ ),
290
+ ),
291
+ migrations.AlterField(
292
+ model_name="rundata",
293
+ name="run",
294
+ field=models.ForeignKey(
295
+ on_delete=django.db.models.deletion.CASCADE,
296
+ related_name="_rundata",
297
+ to="lamindb.run",
298
+ ),
299
+ ),
300
+ migrations.AddField(
301
+ model_name="schema",
302
+ name="description",
303
+ field=lamindb.base.fields.CharField(
304
+ blank=True, db_index=True, default=None, max_length=255, null=True
305
+ ),
306
+ ),
307
+ migrations.AlterField(
308
+ model_name="collection",
309
+ name="description",
310
+ field=lamindb.base.fields.CharField(
311
+ blank=True, db_index=True, default=None, max_length=255, null=True
312
+ ),
313
+ ),
314
+ migrations.AlterField(
315
+ model_name="feature",
316
+ name="description",
317
+ field=lamindb.base.fields.CharField(
318
+ blank=True, db_index=True, default=None, max_length=255, null=True
319
+ ),
320
+ ),
321
+ migrations.AlterField(
322
+ model_name="paramvalue",
323
+ name="created_at",
324
+ field=lamindb.base.fields.DateTimeField(
325
+ blank=True,
326
+ db_default=django.db.models.functions.datetime.Now(),
327
+ db_index=True,
328
+ editable=False,
329
+ ),
330
+ ),
331
+ migrations.AlterField(
332
+ model_name="reference",
333
+ name="description",
334
+ field=lamindb.base.fields.CharField(
335
+ blank=True, db_index=True, default=None, max_length=255, null=True
336
+ ),
337
+ ),
338
+ migrations.AlterField(
339
+ model_name="run",
340
+ name="created_at",
341
+ field=lamindb.base.fields.DateTimeField(
342
+ blank=True,
343
+ db_default=django.db.models.functions.datetime.Now(),
344
+ db_index=True,
345
+ editable=False,
346
+ ),
347
+ ),
348
+ migrations.AlterField(
349
+ model_name="run",
350
+ name="started_at",
351
+ field=lamindb.base.fields.DateTimeField(
352
+ blank=True,
353
+ db_default=django.db.models.functions.datetime.Now(),
354
+ db_index=True,
355
+ editable=False,
356
+ ),
357
+ ),
358
+ migrations.AlterField(
359
+ model_name="runparamvalue",
360
+ name="created_at",
361
+ field=lamindb.base.fields.DateTimeField(
362
+ blank=True,
363
+ db_default=django.db.models.functions.datetime.Now(),
364
+ db_index=True,
365
+ editable=False,
366
+ ),
367
+ ),
368
+ migrations.AlterField(
369
+ model_name="schema",
370
+ name="name",
371
+ field=lamindb.base.fields.CharField(
372
+ blank=True, db_index=True, default=None, max_length=150, null=True
373
+ ),
374
+ ),
375
+ migrations.AlterField(
376
+ model_name="space",
377
+ name="created_at",
378
+ field=lamindb.base.fields.DateTimeField(
379
+ blank=True,
380
+ db_default=django.db.models.functions.datetime.Now(),
381
+ db_index=True,
382
+ editable=False,
383
+ ),
384
+ ),
385
+ migrations.AlterField(
386
+ model_name="tidytable",
387
+ name="description",
388
+ field=lamindb.base.fields.CharField(
389
+ blank=True, db_index=True, default=None, max_length=255, null=True
390
+ ),
391
+ ),
392
+ migrations.AlterField(
393
+ model_name="transform",
394
+ name="created_at",
395
+ field=lamindb.base.fields.DateTimeField(
396
+ blank=True,
397
+ db_default=django.db.models.functions.datetime.Now(),
398
+ db_index=True,
399
+ editable=False,
400
+ ),
401
+ ),
402
+ migrations.AlterField(
403
+ model_name="transform",
404
+ name="updated_at",
405
+ field=lamindb.base.fields.DateTimeField(
406
+ blank=True,
407
+ db_default=django.db.models.functions.datetime.Now(),
408
+ db_index=True,
409
+ editable=False,
410
+ ),
411
+ ),
412
+ migrations.AlterField(
413
+ model_name="ulabel",
414
+ name="description",
415
+ field=lamindb.base.fields.CharField(
416
+ blank=True, db_index=True, default=None, max_length=255, null=True
417
+ ),
418
+ ),
419
+ migrations.AlterField(
420
+ model_name="user",
421
+ name="created_at",
422
+ field=lamindb.base.fields.DateTimeField(
423
+ blank=True,
424
+ db_default=django.db.models.functions.datetime.Now(),
425
+ db_index=True,
426
+ editable=False,
427
+ ),
428
+ ),
429
+ migrations.AlterField(
430
+ model_name="user",
431
+ name="updated_at",
432
+ field=lamindb.base.fields.DateTimeField(
433
+ blank=True,
434
+ db_default=django.db.models.functions.datetime.Now(),
435
+ db_index=True,
436
+ editable=False,
437
+ ),
438
+ ),
439
+ migrations.CreateModel(
440
+ name="RunULabel",
441
+ fields=[
442
+ ("id", models.BigAutoField(primary_key=True, serialize=False)),
443
+ (
444
+ "created_at",
445
+ lamindb.base.fields.DateTimeField(
446
+ blank=True,
447
+ db_default=django.db.models.functions.datetime.Now(),
448
+ db_index=True,
449
+ editable=False,
450
+ ),
451
+ ),
452
+ (
453
+ "created_by",
454
+ lamindb.base.fields.ForeignKey(
455
+ blank=True,
456
+ default=lamindb.base.users.current_user_id,
457
+ on_delete=django.db.models.deletion.PROTECT,
458
+ related_name="+",
459
+ to="lamindb.user",
460
+ ),
461
+ ),
462
+ (
463
+ "run",
464
+ lamindb.base.fields.ForeignKey(
465
+ blank=True,
466
+ on_delete=django.db.models.deletion.CASCADE,
467
+ related_name="links_ulabel",
468
+ to="lamindb.run",
469
+ ),
470
+ ),
471
+ (
472
+ "ulabel",
473
+ lamindb.base.fields.ForeignKey(
474
+ blank=True,
475
+ on_delete=django.db.models.deletion.PROTECT,
476
+ related_name="links_run",
477
+ to="lamindb.ulabel",
478
+ ),
479
+ ),
480
+ ],
481
+ options={
482
+ "unique_together": {("run", "ulabel")},
483
+ },
484
+ bases=(models.Model, lamindb.models.LinkORM),
485
+ ),
486
+ migrations.AddField(
487
+ model_name="run",
488
+ name="ulabels",
489
+ field=models.ManyToManyField(
490
+ related_name="runs", through="lamindb.RunULabel", to="lamindb.ulabel"
491
+ ),
492
+ ),
493
+ migrations.RenameModel(
494
+ old_name="TidyTable",
495
+ new_name="FlexTable",
496
+ ),
497
+ migrations.RenameModel(
498
+ old_name="TidyTableData",
499
+ new_name="FlexTableData",
500
+ ),
501
+ migrations.RenameIndex(
502
+ model_name="flextable",
503
+ new_name="lamindb_fle_uid_e6f216_idx",
504
+ old_name="lamindb_tid_uid_3a6e54_idx",
505
+ ),
506
+ migrations.RenameIndex(
507
+ model_name="flextable",
508
+ new_name="lamindb_fle_name_568594_idx",
509
+ old_name="lamindb_tid_name_50c5de_idx",
510
+ ),
511
+ migrations.RenameIndex(
512
+ model_name="flextabledata",
513
+ new_name="lamindb_fle_tidytab_1674c1_idx",
514
+ old_name="lamindb_tid_tidytab_b35a4d_idx",
515
+ ),
516
+ migrations.RenameIndex(
517
+ model_name="flextabledata",
518
+ new_name="lamindb_fle_feature_830e49_idx",
519
+ old_name="lamindb_tid_feature_5a0b1f_idx",
520
+ ),
521
+ migrations.RenameIndex(
522
+ model_name="flextabledata",
523
+ new_name="lamindb_fle_param_i_4149cb_idx",
524
+ old_name="lamindb_tid_param_i_16c884_idx",
525
+ ),
526
+ migrations.RemoveField(
527
+ model_name="flextabledata",
528
+ name="value_upath",
529
+ ),
530
+ migrations.RemoveField(
531
+ model_name="rundata",
532
+ name="value_upath",
533
+ ),
534
+ ]
lamindb/models.py CHANGED
@@ -824,6 +824,7 @@ class Space(BasicRecord):
824
824
  name: str = models.CharField(max_length=100, db_index=True)
825
825
  """Name of space."""
826
826
  uid: str = CharField(
827
+ editable=False,
827
828
  unique=True,
828
829
  max_length=12,
829
830
  default="00000000",
@@ -833,7 +834,9 @@ class Space(BasicRecord):
833
834
  """Universal id."""
834
835
  description: str | None = CharField(null=True)
835
836
  """Description of space."""
836
- created_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
837
+ created_at: datetime = DateTimeField(
838
+ editable=False, db_default=models.functions.Now(), db_index=True
839
+ )
837
840
  """Time of creation of record."""
838
841
  created_by: User = ForeignKey(
839
842
  "User", CASCADE, default=None, related_name="+", null=True
@@ -979,7 +982,7 @@ class User(BasicRecord, CanCurate):
979
982
 
980
983
  id: int = models.AutoField(primary_key=True)
981
984
  """Internal id, valid only in one DB instance."""
982
- uid: str = CharField(unique=True, db_index=True, max_length=8)
985
+ uid: str = CharField(editable=False, unique=True, db_index=True, max_length=8)
983
986
  """Universal id, valid across DB instances."""
984
987
  handle: str = CharField(max_length=30, unique=True, db_index=True)
985
988
  """Universal handle, valid across DB instances (required)."""
@@ -991,9 +994,13 @@ class User(BasicRecord, CanCurate):
991
994
  """Transforms created by user."""
992
995
  created_runs: Run
993
996
  """Runs created by user."""
994
- created_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
997
+ created_at: datetime = DateTimeField(
998
+ editable=False, db_default=models.functions.Now(), db_index=True
999
+ )
995
1000
  """Time of creation of record."""
996
- updated_at: datetime = DateTimeField(auto_now=True, db_index=True)
1001
+ updated_at: datetime = DateTimeField(
1002
+ editable=False, db_default=models.functions.Now(), db_index=True
1003
+ )
997
1004
  """Time of last update to record."""
998
1005
 
999
1006
  @overload
@@ -1070,12 +1077,14 @@ class Storage(Record, TracksRun, TracksUpdates):
1070
1077
 
1071
1078
  id: int = models.AutoField(primary_key=True)
1072
1079
  """Internal id, valid only in one DB instance."""
1073
- uid: str = CharField(unique=True, max_length=12, default=base62_12, db_index=True)
1080
+ uid: str = CharField(
1081
+ editable=False, unique=True, max_length=12, default=base62_12, db_index=True
1082
+ )
1074
1083
  """Universal id, valid across DB instances."""
1075
1084
  # we are very conservative here with 255 characters
1076
- root: str = CharField(max_length=255, db_index=True, unique=True)
1085
+ root: str = CharField(db_index=True, unique=True)
1077
1086
  """Root path of storage. n s3 path. local path, etc. (required)."""
1078
- description: str | None = CharField(max_length=255, db_index=True, null=True)
1087
+ description: str | None = CharField(db_index=True, null=True)
1079
1088
  """A description of what the storage location is used for (optional)."""
1080
1089
  type: str = CharField(max_length=30, db_index=True)
1081
1090
  """Can be "local" vs. "s3" vs. "gs"."""
@@ -1198,7 +1207,9 @@ class Transform(Record, IsVersioned):
1198
1207
 
1199
1208
  id: int = models.AutoField(primary_key=True)
1200
1209
  """Internal id, valid only in one DB instance."""
1201
- uid: str = CharField(unique=True, db_index=True, max_length=_len_full_uid)
1210
+ uid: str = CharField(
1211
+ editable=False, unique=True, db_index=True, max_length=_len_full_uid
1212
+ )
1202
1213
  """Universal id."""
1203
1214
  key: str | None = CharField(db_index=True, null=True)
1204
1215
  """A name or "/"-separated path-like string.
@@ -1259,9 +1270,17 @@ class Transform(Record, IsVersioned):
1259
1270
 
1260
1271
  If you're looking for the outputs of a single run, see :attr:`lamindb.Run.output_collections`.
1261
1272
  """
1262
- created_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
1273
+ projects: Project
1274
+ """Associated projects."""
1275
+ references: Reference
1276
+ """Associated references."""
1277
+ created_at: datetime = DateTimeField(
1278
+ editable=False, db_default=models.functions.Now(), db_index=True
1279
+ )
1263
1280
  """Time of creation of record."""
1264
- updated_at: datetime = DateTimeField(auto_now=True, db_index=True)
1281
+ updated_at: datetime = DateTimeField(
1282
+ editable=False, db_default=models.functions.Now(), db_index=True
1283
+ )
1265
1284
  """Time of last update to record."""
1266
1285
  created_by: User = ForeignKey(
1267
1286
  User, PROTECT, default=current_user_id, related_name="created_transforms"
@@ -1381,7 +1400,9 @@ class ParamValue(Record):
1381
1400
  # hence, ParamValue does _not_ inherit from TracksRun but manually
1382
1401
  # adds created_at & created_by
1383
1402
  # because ParamValue cannot be updated, we don't need updated_at
1384
- created_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
1403
+ created_at: datetime = DateTimeField(
1404
+ editable=False, db_default=models.functions.Now(), db_index=True
1405
+ )
1385
1406
  """Time of creation of record."""
1386
1407
  created_by: User = ForeignKey(
1387
1408
  User, PROTECT, default=current_user_id, related_name="+"
@@ -1476,13 +1497,17 @@ class Run(Record):
1476
1497
 
1477
1498
  id: int = models.BigAutoField(primary_key=True)
1478
1499
  """Internal id, valid only in one DB instance."""
1479
- uid: str = CharField(unique=True, db_index=True, max_length=20, default=base62_20)
1500
+ uid: str = CharField(
1501
+ editable=False, unique=True, db_index=True, max_length=20, default=base62_20
1502
+ )
1480
1503
  """Universal id, valid across DB instances."""
1481
1504
  name: str | None = CharField(max_length=150, null=True)
1482
1505
  """A name."""
1483
1506
  transform = ForeignKey(Transform, CASCADE, related_name="runs")
1484
1507
  """The transform :class:`~lamindb.Transform` that is being run."""
1485
- started_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
1508
+ started_at: datetime = DateTimeField(
1509
+ editable=False, db_default=models.functions.Now(), db_index=True
1510
+ )
1486
1511
  """Start time of run."""
1487
1512
  finished_at: datetime | None = DateTimeField(db_index=True, null=True, default=None)
1488
1513
  """Finished time of run."""
@@ -1525,12 +1550,18 @@ class Run(Record):
1525
1550
  """A reference like a URL or external ID (such as from a workflow manager)."""
1526
1551
  reference_type: str | None = CharField(max_length=25, db_index=True, null=True)
1527
1552
  """Type of reference such as a workflow manager execution ID."""
1528
- created_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
1553
+ created_at: datetime = DateTimeField(
1554
+ editable=False, db_default=models.functions.Now(), db_index=True
1555
+ )
1529
1556
  """Time of first creation. Mismatches ``started_at`` if the run is re-run."""
1530
1557
  created_by: User = ForeignKey(
1531
1558
  User, CASCADE, default=current_user_id, related_name="created_runs"
1532
1559
  )
1533
1560
  """Creator of run."""
1561
+ ulabels: ULabel = models.ManyToManyField(
1562
+ "ULabel", through="RunULabel", related_name="runs"
1563
+ )
1564
+ """ULabel annotations of this transform."""
1534
1565
  initiated_by_run: Run | None = ForeignKey(
1535
1566
  "Run", CASCADE, null=True, related_name="initiated_runs", default=None
1536
1567
  )
@@ -1542,8 +1573,8 @@ class Run(Record):
1542
1573
 
1543
1574
  Be careful with using this field at this point.
1544
1575
  """
1545
- children: Run
1546
- """The runs that are triggered by this run."""
1576
+ initiated_runs: Run
1577
+ """Runs that were initiated by this run."""
1547
1578
  _is_consecutive: bool | None = BooleanField(null=True)
1548
1579
  """Indicates whether code was consecutively executed. Is relevant for notebooks."""
1549
1580
  _status_code: int = models.SmallIntegerField(default=0, db_index=True)
@@ -1643,7 +1674,9 @@ class ULabel(Record, HasParents, CanCurate, TracksRun, TracksUpdates):
1643
1674
 
1644
1675
  id: int = models.AutoField(primary_key=True)
1645
1676
  """Internal id, valid only in one DB instance."""
1646
- uid: str = CharField(unique=True, db_index=True, max_length=8, default=base62_8)
1677
+ uid: str = CharField(
1678
+ editable=False, unique=True, db_index=True, max_length=8, default=base62_8
1679
+ )
1647
1680
  """A universal random id, valid across DB instances."""
1648
1681
  name: str = CharField(max_length=150, db_index=True)
1649
1682
  """Name or title of ulabel (`unique=True`)."""
@@ -1659,7 +1692,7 @@ class ULabel(Record, HasParents, CanCurate, TracksRun, TracksUpdates):
1659
1692
 
1660
1693
  For example, a ulabel "Project" would be a type, and the actual projects "Project 1", "Project 2", would be records of that `type`.
1661
1694
  """
1662
- description: str | None = TextField(null=True)
1695
+ description: str | None = CharField(null=True, db_index=True)
1663
1696
  """A description (optional)."""
1664
1697
  reference: str | None = CharField(max_length=255, db_index=True, null=True)
1665
1698
  """A reference like URL or external ID."""
@@ -1681,10 +1714,14 @@ class ULabel(Record, HasParents, CanCurate, TracksRun, TracksUpdates):
1681
1714
  """
1682
1715
  transforms: Transform
1683
1716
  """Transforms annotated with this ulabel."""
1717
+ runs: Transform
1718
+ """Runs annotated with this ulabel."""
1684
1719
  artifacts: Artifact
1685
1720
  """Artifacts annotated with this ulabel."""
1686
1721
  collections: Collection
1687
1722
  """Collections annotated with this ulabel."""
1723
+ projects: Project
1724
+ """Associated projects."""
1688
1725
 
1689
1726
  @overload
1690
1727
  def __init__(
@@ -1801,7 +1838,9 @@ class Feature(Record, CanCurate, TracksRun, TracksUpdates):
1801
1838
 
1802
1839
  id: int = models.AutoField(primary_key=True)
1803
1840
  """Internal id, valid only in one DB instance."""
1804
- uid: str = CharField(unique=True, db_index=True, max_length=12, default=base62_12)
1841
+ uid: str = CharField(
1842
+ editable=False, unique=True, db_index=True, max_length=12, default=base62_12
1843
+ )
1805
1844
  """Universal id, valid across DB instances."""
1806
1845
  name: str = CharField(max_length=150, db_index=True, unique=True)
1807
1846
  """Name of feature (`unique=True`)."""
@@ -1825,7 +1864,7 @@ class Feature(Record, CanCurate, TracksRun, TracksUpdates):
1825
1864
  """Distinguish types from instances of the type."""
1826
1865
  unit: str | None = CharField(max_length=30, db_index=True, null=True)
1827
1866
  """Unit of measure, ideally SI (`m`, `s`, `kg`, etc.) or 'normalized' etc. (optional)."""
1828
- description: str | None = TextField(db_index=True, null=True)
1867
+ description: str | None = CharField(db_index=True, null=True)
1829
1868
  """A description."""
1830
1869
  array_rank: int = models.SmallIntegerField(default=0, db_index=True)
1831
1870
  """Rank of feature.
@@ -2051,10 +2090,12 @@ class Schema(Record, CanCurate, TracksRun):
2051
2090
 
2052
2091
  id: int = models.AutoField(primary_key=True)
2053
2092
  """Internal id, valid only in one DB instance."""
2054
- uid: str = CharField(unique=True, db_index=True, max_length=20)
2093
+ uid: str = CharField(editable=False, unique=True, db_index=True, max_length=20)
2055
2094
  """A universal id (hash of the set of feature values)."""
2056
- name: str | None = CharField(max_length=150, null=True)
2095
+ name: str | None = CharField(max_length=150, null=True, db_index=True)
2057
2096
  """A name."""
2097
+ description: str | None = CharField(null=True, db_index=True)
2098
+ """A description."""
2058
2099
  n = IntegerField()
2059
2100
  """Number of features in the set."""
2060
2101
  dtype: str | None = CharField(max_length=64, null=True)
@@ -2398,7 +2439,9 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2398
2439
 
2399
2440
  id: int = models.AutoField(primary_key=True)
2400
2441
  """Internal id, valid only in one DB instance."""
2401
- uid: str = CharField(unique=True, db_index=True, max_length=_len_full_uid)
2442
+ uid: str = CharField(
2443
+ editable=False, unique=True, db_index=True, max_length=_len_full_uid
2444
+ )
2402
2445
  """A universal random id."""
2403
2446
  key: str | None = CharField(db_index=True, null=True)
2404
2447
  """A (virtual) relative file path within the artifact's storage location.
@@ -2411,10 +2454,7 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2411
2454
  actual filepath on the underyling filesytem or object store.
2412
2455
  """
2413
2456
  description: str | None = CharField(db_index=True, null=True)
2414
- """A description.
2415
-
2416
- LaminDB doesn't require you to pass a key, you can
2417
- """
2457
+ """A description."""
2418
2458
  storage: Storage = ForeignKey(Storage, PROTECT, related_name="artifacts")
2419
2459
  """Storage location, e.g. an S3 or GCP bucket or a local directory."""
2420
2460
  suffix: str = CharField(max_length=30, db_index=True)
@@ -2510,6 +2550,10 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2510
2550
 
2511
2551
  It defaults to False for file-like artifacts and to True for folder-like artifacts.
2512
2552
  """
2553
+ projects: Project
2554
+ """Associated projects."""
2555
+ references: Reference
2556
+ """Associated references."""
2513
2557
 
2514
2558
  @overload
2515
2559
  def __init__(
@@ -2955,12 +2999,16 @@ class Collection(Record, IsVersioned, TracksRun, TracksUpdates):
2955
2999
  id: int = models.AutoField(primary_key=True)
2956
3000
  """Internal id, valid only in one DB instance."""
2957
3001
  uid: str = CharField(
2958
- unique=True, db_index=True, max_length=_len_full_uid, default=base62_20
3002
+ editable=False,
3003
+ unique=True,
3004
+ db_index=True,
3005
+ max_length=_len_full_uid,
3006
+ default=base62_20,
2959
3007
  )
2960
3008
  """Universal id, valid across DB instances."""
2961
3009
  key: str = CharField(db_index=True)
2962
3010
  """Name or path-like key."""
2963
- description: str | None = TextField(null=True)
3011
+ description: str | None = CharField(null=True, db_index=True)
2964
3012
  """A description or title."""
2965
3013
  hash: str | None = CharField(max_length=HASH_LENGTH, db_index=True, null=True)
2966
3014
  """Hash of collection content. 86 base64 chars allow to store 64 bytes, 512 bits."""
@@ -3239,7 +3287,9 @@ class Person(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields):
3239
3287
 
3240
3288
  id: int = models.AutoField(primary_key=True)
3241
3289
  """Internal id, valid only in one DB instance."""
3242
- uid: str = CharField(unique=True, max_length=8, db_index=True, default=base62_8)
3290
+ uid: str = CharField(
3291
+ editable=False, unique=True, max_length=8, db_index=True, default=base62_8
3292
+ )
3243
3293
  """Universal id, valid across DB instances."""
3244
3294
  name: str = CharField(db_index=True)
3245
3295
  """Name of the person (forename(s) lastname)."""
@@ -3265,12 +3315,20 @@ class Project(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields):
3265
3315
 
3266
3316
  id: int = models.AutoField(primary_key=True)
3267
3317
  """Internal id, valid only in one DB instance."""
3268
- uid: str = CharField(unique=True, max_length=12, db_index=True, default=base62_12)
3318
+ uid: str = CharField(
3319
+ editable=False, unique=True, max_length=12, db_index=True, default=base62_12
3320
+ )
3269
3321
  """Universal id, valid across DB instances."""
3270
3322
  name: str = CharField(db_index=True)
3271
3323
  """Title or name of the Project."""
3272
- type: str | None = CharField(max_length=64, db_index=True, null=True)
3273
- """A free-form type."""
3324
+ type: Project | None = ForeignKey(
3325
+ "self", PROTECT, null=True, related_name="records"
3326
+ )
3327
+ """Type of project (e.g., 'Program', 'Project', 'GithubIssue', 'Task')."""
3328
+ records: Project
3329
+ """Records of this type."""
3330
+ is_type: bool = BooleanField(default=None, db_index=True, null=True)
3331
+ """Distinguish types from instances of the type."""
3274
3332
  abbr: str | None = CharField(max_length=32, db_index=True, null=True)
3275
3333
  """An abbreviation."""
3276
3334
  url: str | None = URLField(max_length=255, null=True, default=None)
@@ -3282,12 +3340,25 @@ class Project(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields):
3282
3340
  parents: Project = models.ManyToManyField(
3283
3341
  "self", symmetrical=False, related_name="children"
3284
3342
  )
3285
- """Parent projects."""
3343
+ """Parent projects, the super-projects owning this project."""
3286
3344
  children: Project
3287
- """Child projects.
3345
+ """Child projects, the sub-projects owned by this project.
3288
3346
 
3289
- Reverse accessor for parents.
3347
+ Reverse accessor for `.parents`.
3348
+ """
3349
+ predecessors: Project = models.ManyToManyField(
3350
+ "self", symmetrical=False, related_name="successors"
3351
+ )
3352
+ """The preceding projects required by this project."""
3353
+ successors: Project
3354
+ """The succeeding projects requiring this project.
3355
+
3356
+ Reverse accessor for `.predecessors`.
3290
3357
  """
3358
+ people: Person = models.ManyToManyField(
3359
+ Person, through="PersonProject", related_name="projects"
3360
+ )
3361
+ """People associated with this project."""
3291
3362
  artifacts: Artifact = models.ManyToManyField(
3292
3363
  Artifact, through="ArtifactProject", related_name="projects"
3293
3364
  )
@@ -3312,8 +3383,6 @@ class Project(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields):
3312
3383
  Collection, through="CollectionProject", related_name="projects"
3313
3384
  )
3314
3385
  """Collections associated with this project."""
3315
- persons: Person = models.ManyToManyField(Person, related_name="projects")
3316
- """Persons associated with this project."""
3317
3386
  references: Reference = models.ManyToManyField("Reference", related_name="projects")
3318
3387
  """References associated with this project."""
3319
3388
  _status_code: int = models.SmallIntegerField(default=0, db_index=True)
@@ -3341,7 +3410,9 @@ class Reference(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields):
3341
3410
 
3342
3411
  id: int = models.AutoField(primary_key=True)
3343
3412
  """Internal id, valid only in one DB instance."""
3344
- uid: str = CharField(unique=True, max_length=12, db_index=True, default=base62_12)
3413
+ uid: str = CharField(
3414
+ editable=False, unique=True, max_length=12, db_index=True, default=base62_12
3415
+ )
3345
3416
  """Universal id, valid across DB instances."""
3346
3417
  name: str = CharField(db_index=True)
3347
3418
  """Title or name of the reference document."""
@@ -3377,7 +3448,7 @@ class Reference(Record, CanCurate, TracksRun, TracksUpdates, ValidateFields):
3377
3448
  ],
3378
3449
  )
3379
3450
  """Digital Object Identifier (DOI) for the reference."""
3380
- description: str | None = TextField(null=True)
3451
+ description: str | None = CharField(null=True, db_index=True)
3381
3452
  """Description of the reference."""
3382
3453
  text: str | None = TextField(null=True)
3383
3454
  """Abstract or full text of the reference to make it searchable."""
@@ -3421,7 +3492,6 @@ class DataMixin(models.Model):
3421
3492
  value_int = models.BigIntegerField(null=True, blank=True)
3422
3493
  value_float = models.FloatField(null=True, blank=True)
3423
3494
  value_str = models.TextField(null=True, blank=True)
3424
- value_upath = models.CharField(max_length=255, null=True, blank=True)
3425
3495
  value_datetime = models.DateTimeField(null=True, blank=True)
3426
3496
  value_ulabel = models.ForeignKey(
3427
3497
  ULabel, null=True, blank=True, on_delete=models.CASCADE, related_name="+"
@@ -3453,7 +3523,6 @@ class DataMixin(models.Model):
3453
3523
  self.value_int,
3454
3524
  self.value_float,
3455
3525
  self.value_str,
3456
- self.value_upath,
3457
3526
  self.value_datetime,
3458
3527
  self.value_ulabel,
3459
3528
  self.value_artifact,
@@ -3466,7 +3535,7 @@ class DataMixin(models.Model):
3466
3535
 
3467
3536
 
3468
3537
  class RunData(BasicRecord, DataMixin):
3469
- run = models.ForeignKey("Run", on_delete=models.CASCADE, related_name="data")
3538
+ run = models.ForeignKey("Run", on_delete=models.CASCADE, related_name="_rundata")
3470
3539
 
3471
3540
  class Meta:
3472
3541
  constraints = [
@@ -3488,13 +3557,15 @@ class RunData(BasicRecord, DataMixin):
3488
3557
  ]
3489
3558
 
3490
3559
 
3491
- class TidyTable(Record, TracksRun, TracksUpdates):
3492
- uid: str = CharField(unique=True, max_length=12, db_index=True, default=base62_12)
3560
+ class FlexTable(Record, TracksRun, TracksUpdates):
3561
+ uid: str = CharField(
3562
+ editable=False, unique=True, max_length=12, db_index=True, default=base62_12
3563
+ )
3493
3564
  name = CharField()
3494
3565
  schema: Schema | None = ForeignKey(
3495
3566
  Schema, null=True, on_delete=models.SET_NULL, related_name="_tidytables"
3496
3567
  )
3497
- type: TidyTable | None = ForeignKey(
3568
+ type: FlexTable | None = ForeignKey(
3498
3569
  "self", PROTECT, null=True, related_name="records"
3499
3570
  )
3500
3571
  """Type of tidy table, e.g., `Cell`, `SampleSheet`, etc."""
@@ -3502,7 +3573,8 @@ class TidyTable(Record, TracksRun, TracksUpdates):
3502
3573
  """Records of this type."""
3503
3574
  is_type: bool = BooleanField(default=None, db_index=True, null=True)
3504
3575
  """Distinguish types from instances of the type."""
3505
- description: str = TextField()
3576
+ description: str = CharField(null=True, db_index=True)
3577
+ """A description."""
3506
3578
  projects: Project = ManyToManyField(Project, related_name="_tidytables")
3507
3579
  ulabels: Project = ManyToManyField(ULabel, related_name="_tidytables")
3508
3580
 
@@ -3510,9 +3582,9 @@ class TidyTable(Record, TracksRun, TracksUpdates):
3510
3582
  indexes = [models.Index(fields=["uid"]), models.Index(fields=["name"])]
3511
3583
 
3512
3584
 
3513
- class TidyTableData(BasicRecord, DataMixin):
3585
+ class FlexTableData(BasicRecord, DataMixin):
3514
3586
  tidytable = models.ForeignKey(
3515
- TidyTable, on_delete=models.CASCADE, related_name="data"
3587
+ FlexTable, on_delete=models.CASCADE, related_name="data"
3516
3588
  )
3517
3589
 
3518
3590
  class Meta:
@@ -3612,6 +3684,23 @@ class TransformULabel(BasicRecord, LinkORM, TracksRun):
3612
3684
  unique_together = ("transform", "ulabel")
3613
3685
 
3614
3686
 
3687
+ class RunULabel(BasicRecord, LinkORM):
3688
+ id: int = models.BigAutoField(primary_key=True)
3689
+ run: Run = ForeignKey(Run, CASCADE, related_name="links_ulabel")
3690
+ ulabel: ULabel = ForeignKey(ULabel, PROTECT, related_name="links_run")
3691
+ created_at: datetime = DateTimeField(
3692
+ editable=False, db_default=models.functions.Now(), db_index=True
3693
+ )
3694
+ """Time of creation of record."""
3695
+ created_by: User = ForeignKey(
3696
+ "lamindb.User", PROTECT, default=current_user_id, related_name="+"
3697
+ )
3698
+ """Creator of record."""
3699
+
3700
+ class Meta:
3701
+ unique_together = ("run", "ulabel")
3702
+
3703
+
3615
3704
  class CollectionULabel(BasicRecord, LinkORM, TracksRun):
3616
3705
  id: int = models.BigAutoField(primary_key=True)
3617
3706
  collection: Collection = ForeignKey(
@@ -3643,7 +3732,9 @@ class RunParamValue(BasicRecord, LinkORM):
3643
3732
  run: Run = ForeignKey(Run, CASCADE, related_name="+")
3644
3733
  # we follow the lower() case convention rather than snake case for link models
3645
3734
  paramvalue: ParamValue = ForeignKey(ParamValue, PROTECT, related_name="+")
3646
- created_at: datetime = DateTimeField(auto_now_add=True, db_index=True)
3735
+ created_at: datetime = DateTimeField(
3736
+ editable=False, db_default=models.functions.Now(), db_index=True
3737
+ )
3647
3738
  """Time of creation of record."""
3648
3739
  created_by: User = ForeignKey(
3649
3740
  "lamindb.User", PROTECT, default=current_user_id, related_name="+"
@@ -3716,6 +3807,16 @@ class ULabelProject(BasicRecord, LinkORM, TracksRun):
3716
3807
  unique_together = ("ulabel", "project")
3717
3808
 
3718
3809
 
3810
+ class PersonProject(BasicRecord, LinkORM, TracksRun):
3811
+ id: int = models.BigAutoField(primary_key=True)
3812
+ person: Transform = ForeignKey(Person, CASCADE, related_name="links_project")
3813
+ project: Project = ForeignKey(Project, PROTECT, related_name="links_person")
3814
+ role: str | None = CharField(null=True, default=None)
3815
+
3816
+ class Meta:
3817
+ unique_together = ("person", "project")
3818
+
3819
+
3719
3820
  class FeatureProject(BasicRecord, LinkORM, TracksRun):
3720
3821
  id: int = models.BigAutoField(primary_key=True)
3721
3822
  feature: Feature = ForeignKey(Feature, CASCADE, related_name="links_project")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: A data framework for biology.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10,<3.13
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3.10
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Requires-Dist: lamin_utils==0.13.10
12
- Requires-Dist: lamin_cli==1.0.2
12
+ Requires-Dist: lamin_cli==1.0.4
13
13
  Requires-Dist: lamindb_setup[aws]==1.0.3
14
14
  Requires-Dist: pyarrow
15
15
  Requires-Dist: typing_extensions!=4.6.0
@@ -1,9 +1,9 @@
1
- lamindb/__init__.py,sha256=zyukYAtc-MvRSctXlkUefaL7uppSiSuZIdTw7O9HFjQ,2255
1
+ lamindb/__init__.py,sha256=uV_nFvYAz9x_edJcb9bqtnDx2C9wHhbDbnvMN2EIQHM,2255
2
2
  lamindb/_artifact.py,sha256=22pKCA05PoIAgP2xszCHUovZ1VbMGIyrQqNLs5xDG_s,46580
3
3
  lamindb/_can_curate.py,sha256=pIu9Ylgq5biUd_67rRbAHg9tkXSQrxMRM8TnVboL9YA,20341
4
4
  lamindb/_collection.py,sha256=j2yTfR9v-NGUI85JfQk7vOQNExkWE5H_ulsSlBh1AOI,14456
5
5
  lamindb/_feature.py,sha256=qpUZfmdxUpQFLq5GciTn6KgALL19tCs0raHhzJgm7Lo,6311
6
- lamindb/_finish.py,sha256=LjeGY9QsC9zbQxMr5K8YCYN-rtlzTqtZQVTtkWHHEp8,12832
6
+ lamindb/_finish.py,sha256=aMN9CwTGAXeqWSDvCumVLztHtKttwI9z52mdz4ue5Uw,13362
7
7
  lamindb/_from_values.py,sha256=uO3IfYzAI8VDDTqlbLzsZtawSYFS-Qzd_ZWwKGhH90o,14231
8
8
  lamindb/_is_versioned.py,sha256=6_LBAKD_fng6BReqitJUIxTUaQok3AeIpNnE_D8kHnQ,1293
9
9
  lamindb/_parents.py,sha256=PA--_ZH3PNqIVW0PpuYk9d4DAVlHUBPN-dN0rFUKUN0,17238
@@ -18,7 +18,7 @@ lamindb/_transform.py,sha256=LYFf8gScJrYLMZJECLYZ5nrW2vLPObdzRP47md-Tq-s,5731
18
18
  lamindb/_ulabel.py,sha256=YTiUCYrcEqyUKD8nZO4iOqiyYnUP5bW_r7yry4KSeWA,2068
19
19
  lamindb/_utils.py,sha256=LGdiW4k3GClLz65vKAVRkL6Tw-Gkx9DWAdez1jyA5bE,428
20
20
  lamindb/_view.py,sha256=c4eN5hcBlg3TVnljKefbyWAq0eBncjMp2xQcb5OaGWg,4982
21
- lamindb/models.py,sha256=W3htUmj4R5YJ_bFM8wly57AxR5Ip8JuiAx7mRya9q1o,144513
21
+ lamindb/models.py,sha256=85eBwsFmoKfZfefviUt8DmfW1n6kyFQO2VfP3pa-xUc,147536
22
22
  lamindb/base/__init__.py,sha256=J0UpYObi9hJBFyBpAXp4wB3DaJx48R2SaUeB4wjiFvc,267
23
23
  lamindb/base/fields.py,sha256=RdwYHQmB7B-jopD_K2QNL5vjhOelu7DWGgqQItXr3pg,8024
24
24
  lamindb/base/ids.py,sha256=WzHWiHZtlRUKqxz_p-76ks_JSW669ztvriE7Z3A0yHg,1736
@@ -26,7 +26,7 @@ lamindb/base/types.py,sha256=JfZk0xmhLsWusU0s4SNjhRnQ52mn-cSiG5Gf4SsACBs,1227
26
26
  lamindb/base/users.py,sha256=g4ZLQb6eey66FO9eEumbfDpJi_FZZsiLVe2Frz9JuLI,978
27
27
  lamindb/base/validation.py,sha256=Azz9y2-x0cPum4yULXMG3Yzra03mcVYzcKTiI23HgxE,2287
28
28
  lamindb/core/__init__.py,sha256=4AGZqt5g8k3jFX53IXdezQR4Gf7JmMBLZRyTJJzS4sI,1628
29
- lamindb/core/_context.py,sha256=FTeWD-Ku1apAzabPIzs2RU-pXkQk5Wl_vsrL0kph8rI,26461
29
+ lamindb/core/_context.py,sha256=na1n_Lpj7Dvxjk1KB5Di4jEdhH-d6Jf757fvY1awEGM,28591
30
30
  lamindb/core/_data.py,sha256=xg_St591OPCzLhaZAlGxVd8QkDxZsxDc_yEwY8Kop8w,19017
31
31
  lamindb/core/_describe.py,sha256=3Z1xi9eKIBkYuPW9ctdWvFaGZym8mI9FcwyZF3a6YVo,4885
32
32
  lamindb/core/_django.py,sha256=vPY4wJ4bf3a1uz5bhukCCF_mngR_9w2Ot3nvWZpa204,7629
@@ -58,7 +58,7 @@ lamindb/core/storage/objects.py,sha256=5vM2T_upuzrXt2b7fQeQ2FUO710-FRbubxTzKzV2E
58
58
  lamindb/core/storage/paths.py,sha256=XXEy51qCw0z497y6ZEN_SULY3oXtA4XlanHo-TGK7jY,6302
59
59
  lamindb/core/subsettings/__init__.py,sha256=j6G9WAJLK-x9FzPSFw-HJUmOseZKGTbK-oLTKI_X_zs,126
60
60
  lamindb/core/subsettings/_creation_settings.py,sha256=54mfMH_osC753hpxcl7Dq1rwBD2LHnWveXtQpkLBITE,1194
61
- lamindb/curators/__init__.py,sha256=BVdmWVTC2wZq_mduxxhT-2z9uNKDEycNK-fbYSrIsrU,92475
61
+ lamindb/curators/__init__.py,sha256=XGHnTEFB-Q4XphR0AWLXhITqhAZqkqEzuJJq1Ypb-zU,92357
62
62
  lamindb/curators/_spatial.py,sha256=JgveK3aC1kFEplumEwU4Yyj-2tgHGfSAO52lHniJq5s,21212
63
63
  lamindb/integrations/__init__.py,sha256=RWGMYYIzr8zvmNPyVB4m-p4gMDhxdRbjES2Ed23OItw,215
64
64
  lamindb/integrations/_vitessce.py,sha256=nbfOsEO-W4f0BVnpFBDaA45577cfdvQehVx1hAUP544,3978
@@ -91,10 +91,11 @@ lamindb/migrations/0076_lamindbv1_part6.py,sha256=G_Wgog-OgquE0-h_CykjiDWUyPdYlC
91
91
  lamindb/migrations/0077_lamindbv1_part6b.py,sha256=v7k8OZX9o5ppSJU_yhHlIXGTobTm30bo1dAIi8tUkEI,8211
92
92
  lamindb/migrations/0078_lamindbv1_part6c.py,sha256=RWRXBwyyQ_rFTN5kwstBziV6tqHJcGYI2vsFmuYCCz0,17084
93
93
  lamindb/migrations/0079_alter_rundata_value_json_and_more.py,sha256=yQmbs8yWrFLOVQJqAfzLNMZOqTSnXyG-mQgpO7ls1u8,995
94
+ lamindb/migrations/0080_polish_lamindbv1.py,sha256=VfCwJtHlBsMPIyFQ2oh24oWkiRXjDvXRpKe5fBZ63aM,17660
94
95
  lamindb/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
96
  lamindb/setup/__init__.py,sha256=OwZpZzPDv5lPPGXZP7-zK6UdO4FHvvuBh439yZvIp3A,410
96
97
  lamindb/setup/core/__init__.py,sha256=SevlVrc2AZWL3uALbE5sopxBnIZPWZ1IB0NBDudiAL8,167
97
- lamindb-1.0.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
98
- lamindb-1.0.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
99
- lamindb-1.0.1.dist-info/METADATA,sha256=mDU_9jeyNkNgbADDIJ9H3AKUCSrevbvF0Sc8EXz2XlY,2611
100
- lamindb-1.0.1.dist-info/RECORD,,
98
+ lamindb-1.0.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
99
+ lamindb-1.0.3.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
100
+ lamindb-1.0.3.dist-info/METADATA,sha256=SoOQz-aPtNZiT7IUmNBqKeoj9PZgedTPwNPl_iRyftY,2611
101
+ lamindb-1.0.3.dist-info/RECORD,,