lamindb 1.3.2__py3-none-any.whl → 1.4.0__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
@@ -72,7 +72,7 @@ Backward compatibility.
72
72
 
73
73
  # ruff: noqa: I001
74
74
  # denote a release candidate for 0.1.0 with 0.1rc1, 0.1a1, 0.1b1, etc.
75
- __version__ = "1.3.2"
75
+ __version__ = "1.4.0"
76
76
 
77
77
  import warnings
78
78
 
@@ -123,8 +123,8 @@ if _check_instance_setup(from_module="lamindb"):
123
123
  from . import integrations
124
124
  from . import curators
125
125
 
126
- track = context.track # simple access
127
- finish = context.finish # simple access
126
+ track = context.track
127
+ finish = context.finish
128
128
  settings.__doc__ = """Global settings (:class:`~lamindb.core.Settings`)."""
129
129
  context.__doc__ = """Global run context (:class:`~lamindb.core.Context`).
130
130
 
lamindb/core/_context.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import builtins
4
4
  import hashlib
5
+ import os
5
6
  import signal
6
7
  import sys
7
8
  import threading
@@ -22,7 +23,6 @@ from lamindb.models import Run, Transform, format_field_value
22
23
 
23
24
  from ..core._settings import settings
24
25
  from ..errors import (
25
- InconsistentKey,
26
26
  InvalidArgument,
27
27
  TrackNotCalled,
28
28
  UpdateContext,
@@ -30,7 +30,6 @@ from ..errors import (
30
30
  from ..models._is_versioned import bump_version as bump_version_function
31
31
  from ..models._is_versioned import (
32
32
  increment_base62,
33
- message_update_key_in_version_family,
34
33
  )
35
34
  from ._sync_git import get_transform_reference_from_git_repo
36
35
  from ._track_environment import track_environment
@@ -39,7 +38,7 @@ if TYPE_CHECKING:
39
38
  from lamindb_setup.core.types import UPathStr
40
39
 
41
40
  from lamindb.base.types import TransformType
42
- from lamindb.models import Project
41
+ from lamindb.models import Project, Space
43
42
 
44
43
  is_run_from_ipython = getattr(builtins, "__IPYTHON__", False)
45
44
 
@@ -204,6 +203,7 @@ class Context:
204
203
  self._path: Path | None = None
205
204
  """A local path to the script that's running."""
206
205
  self._project: Project | None = None
206
+ self._space: Space | None = None
207
207
  self._logging_message_track: str = ""
208
208
  self._logging_message_imports: str = ""
209
209
  self._stream_tracker: LogStreamTracker = LogStreamTracker()
@@ -255,6 +255,11 @@ class Context:
255
255
  """Project to label entities created during the run."""
256
256
  return self._project
257
257
 
258
+ @property
259
+ def space(self) -> Space | None:
260
+ """The space in which entities are created during the run."""
261
+ return self._space
262
+
258
263
  @property
259
264
  def run(self) -> Run | None:
260
265
  """Managed run of context."""
@@ -265,23 +270,26 @@ class Context:
265
270
  transform: str | Transform | None = None,
266
271
  *,
267
272
  project: str | None = None,
273
+ space: str | None = None,
268
274
  params: dict | None = None,
269
275
  new_run: bool | None = None,
270
276
  path: str | None = None,
271
277
  ) -> None:
272
- """Track a global run of your Python session.
278
+ """Track a global run in your compute session.
273
279
 
274
280
  - sets :attr:`~lamindb.core.Context.transform` &
275
281
  :attr:`~lamindb.core.Context.run` by creating or loading `Transform` &
276
282
  `Run` records
277
- - saves Python environment as a `requirements.txt` file: `run.environment`
283
+ - writes compute environment to prepare populating: `run.environment`
284
+ - if :attr:`~lamindb.core.Settings.sync_git_repo` is set, checks whether a script-like
285
+ transform exists in a git repository and links it
278
286
 
279
- If :attr:`~lamindb.core.Settings.sync_git_repo` is set, checks whether a
280
- script-like transform exists in a git repository and links it.
287
+ Guide: :doc:`/track`
281
288
 
282
289
  Args:
283
- transform: A transform `uid` or record. If `None`, creates a `uid`.
290
+ transform: A transform `uid` or record. If `None`, manages the `transform` based on the script or notebook that calls `ln.track()`.
284
291
  project: A project `name` or `uid` for labeling entities created during the run.
292
+ space: A space `name` or `uid` for creating entities during the run. This doesn't affect bionty entities given they should typically be commonly accessible.
285
293
  params: A dictionary of parameters to track for the run.
286
294
  new_run: If `False`, loads the latest run of transform
287
295
  (default notebook), if `True`, creates new run (default non-notebook).
@@ -290,16 +298,16 @@ class Context:
290
298
 
291
299
  Examples:
292
300
 
293
- To track the run of a notebook or script, call:
301
+ To track the run of a notebook or script, call::
294
302
 
295
- >>> ln.track()
303
+ ln.track()
296
304
 
297
- If you want to ensure a single version history across renames of the notebook or script, pass the auto-generated `uid` that you'll find in the logs:
305
+ If you want to ensure a single version history across renames of the notebook or script, pass the auto-generated `uid` that you'll find in the logs::
298
306
 
299
- >>> ln.track("Onv04I53OgtT0000") # example uid, the last four characters encode the version of the transform
307
+ ln.track("Onv04I53OgtT0000") # example uid, the last four characters encode the version of the transform
300
308
 
301
309
  """
302
- from lamindb.models import Project
310
+ from lamindb.models import Project, Space
303
311
 
304
312
  instance_settings = ln_setup.settings.instance
305
313
  # similar logic here: https://github.com/laminlabs/lamindb/pull/2527
@@ -307,6 +315,8 @@ class Context:
307
315
  if instance_settings.dialect == "postgresql" and "read" in instance_settings.db:
308
316
  logger.warning("skipping track(), connected in read-only mode")
309
317
  return None
318
+ if project is None:
319
+ project = os.environ.get("LAMIN_CURRENT_PROJECT")
310
320
  if project is not None:
311
321
  project_record = Project.filter(
312
322
  Q(name=project) | Q(uid=project)
@@ -316,6 +326,13 @@ class Context:
316
326
  f"Project '{project}' not found, either create it with `ln.Project(name='...').save()` or fix typos."
317
327
  )
318
328
  self._project = project_record
329
+ if space is not None:
330
+ space_record = Space.filter(Q(name=space) | Q(uid=space)).one_or_none()
331
+ if space_record is None:
332
+ raise InvalidArgument(
333
+ f"Space '{space}', please check on the hub UI whether you have the correct `uid` or `name`."
334
+ )
335
+ self._space = space_record
319
336
  self._logging_message_track = ""
320
337
  self._logging_message_imports = ""
321
338
  if transform is not None and isinstance(transform, str):
@@ -495,19 +512,6 @@ class Context:
495
512
  transform_ref_type: str | None = None,
496
513
  transform_type: TransformType = None,
497
514
  ):
498
- def get_key_clashing_message(transform: Transform, key: str) -> str:
499
- update_key_note = message_update_key_in_version_family(
500
- suid=transform.stem_uid,
501
- existing_key=transform.key,
502
- new_key=key,
503
- registry="Transform",
504
- )
505
- return (
506
- f'Filepath "{key}" clashes with the existing key "{transform.key}" for uid "{transform.uid[:-4]}...."\n\nEither init a new transform with a new uid:\n\n'
507
- f'ln.track("{ids.base62_12()}0000")\n\n{update_key_note}'
508
- )
509
-
510
- revises = None
511
515
  # the user did not pass the uid
512
516
  if self.uid is None:
513
517
 
@@ -531,27 +535,28 @@ class Context:
531
535
  for aux_transform in transforms:
532
536
  if aux_transform.key in self._path.as_posix():
533
537
  key = aux_transform.key
538
+ # first part of the if condition: no version bump, second part: version bump
534
539
  if (
535
- # has to be the same user
536
- aux_transform.created_by_id == ln_setup.settings.user.id
537
- and (
538
- # if the transform source code wasn't yet saved
540
+ # if a user hasn't yet saved the transform source code, needs to be same user
541
+ (
539
542
  aux_transform.source_code is None
540
- # if the transform source code is unchanged
541
- # if aux_transform.type == "notebook", we anticipate the user makes changes to the notebook source code
542
- # in an interactive session, hence we *pro-actively bump* the version number by setting `revises`
543
- # in the second part of the if condition even though the source code is unchanged at point of running track()
544
- or (
545
- aux_transform.hash == hash
546
- and aux_transform.type != "notebook"
547
- )
543
+ and aux_transform.created_by_id
544
+ == ln_setup.settings.user.id
545
+ )
546
+ # if the transform source code is unchanged
547
+ # if aux_transform.type == "notebook", we anticipate the user makes changes to the notebook source code
548
+ # in an interactive session, hence we *pro-actively bump* the version number by setting `revises`
549
+ # in the second part of the if condition even though the source code is unchanged at point of running track()
550
+ or (
551
+ aux_transform.hash == hash
552
+ and aux_transform.type != "notebook"
548
553
  )
549
554
  ):
550
555
  uid = aux_transform.uid
551
556
  target_transform = aux_transform
552
557
  else:
553
558
  uid = f"{aux_transform.uid[:-4]}{increment_base62(aux_transform.uid[-4:])}"
554
- message = f"there already is a {aux_transform.type} with `key` '{aux_transform.key}'"
559
+ message = f"there already is a {aux_transform.type} with key '{aux_transform.key}'"
555
560
  if (
556
561
  aux_transform.hash == hash
557
562
  and aux_transform.type == "notebook"
@@ -564,7 +569,6 @@ class Context:
564
569
  ):
565
570
  message += f" -- {aux_transform.created_by.handle} already works on this draft"
566
571
  message += f", creating new version '{uid}'"
567
- revises = aux_transform
568
572
  found_key = True
569
573
  break
570
574
  if not found_key:
@@ -585,15 +589,14 @@ class Context:
585
589
  if transform is not None:
586
590
  if transform.key not in self._path.as_posix():
587
591
  n_parts = len(Path(transform.key).parts)
588
- last_path_elements = (
592
+ (
589
593
  Path(*self._path.parts[-n_parts:]).as_posix()
590
594
  if n_parts > 0
591
595
  else ""
592
596
  )
593
- raise UpdateContext(
594
- get_key_clashing_message(transform, last_path_elements)
595
- )
596
- key = transform.key # type: ignore
597
+ key = self._path.name
598
+ else:
599
+ key = transform.key # type: ignore
597
600
  else:
598
601
  key = self._path.name
599
602
  if self.version is not None:
@@ -619,27 +622,15 @@ class Context:
619
622
  # make a new transform record
620
623
  if transform is None:
621
624
  assert key is not None # noqa: S101
622
- raise_update_context = False
623
- try:
624
- transform = Transform( # type: ignore
625
- uid=self.uid,
626
- version=self.version,
627
- description=description,
628
- key=key,
629
- reference=transform_ref,
630
- reference_type=transform_ref_type,
631
- type=transform_type,
632
- ).save()
633
- except InconsistentKey:
634
- raise_update_context = True
635
- if raise_update_context:
636
- if revises is None:
637
- revises = (
638
- Transform.filter(uid__startswith=self.uid[:-4], is_latest=True)
639
- .order_by("-created_at")
640
- .first()
641
- )
642
- raise UpdateContext(get_key_clashing_message(revises, key))
625
+ transform = Transform( # type: ignore
626
+ uid=self.uid,
627
+ version=self.version,
628
+ description=description,
629
+ key=key,
630
+ reference=transform_ref,
631
+ reference_type=transform_ref_type,
632
+ type=transform_type,
633
+ ).save()
643
634
  self._logging_message_track += f"created Transform('{transform.uid}')"
644
635
  else:
645
636
  uid = transform.uid
@@ -647,7 +638,11 @@ class Context:
647
638
  transform_was_saved = transform.source_code is not None
648
639
  # check whether the transform.key is consistent
649
640
  if transform.key != key:
650
- raise UpdateContext(get_key_clashing_message(transform, key))
641
+ self._logging_message_track += (
642
+ f"renaming transform {transform.key} to {key}"
643
+ )
644
+ transform.key = key
645
+ transform.save()
651
646
  elif transform.description != description and description is not None:
652
647
  transform.description = description
653
648
  transform.save()
@@ -659,7 +654,7 @@ class Context:
659
654
  and not transform_was_saved
660
655
  ):
661
656
  raise UpdateContext(
662
- f'{transform.created_by.name} ({transform.created_by.handle}) already works on this draft {transform.type}.\n\nPlease create a revision via `ln.track("{uid[:-4]}{increment_base62(uid[-4:])}")` or a new transform with a *different* filedescription and `ln.track("{ids.base62_12()}0000")`.'
657
+ f'{transform.created_by.name} ({transform.created_by.handle}) already works on this draft {transform.type}.\n\nPlease create a revision via `ln.track("{uid[:-4]}{increment_base62(uid[-4:])}")` or a new transform with a *different* key and `ln.track("{ids.base62_12()}0000")`.'
663
658
  )
664
659
  # check whether transform source code was already saved
665
660
  if transform_was_saved:
@@ -752,4 +747,4 @@ class Context:
752
747
  self._description = None
753
748
 
754
749
 
755
- context = Context()
750
+ context: Context = Context()
@@ -44,7 +44,7 @@ def small_dataset1(
44
44
  # define the dataset-level metadata
45
45
  metadata = {
46
46
  "temperature": 21.6,
47
- "study": "Candidate marker study 1",
47
+ "experiment": "Experiment 1",
48
48
  "date_of_study": "2024-12-01",
49
49
  "study_note": "We had a great time performing this study and the results look compelling.",
50
50
  }
@@ -78,7 +78,7 @@ def small_dataset2(
78
78
  }
79
79
  metadata = {
80
80
  "temperature": 22.6,
81
- "study": "Candidate marker study 2",
81
+ "experiment": "Experiment 2",
82
82
  "date_of_study": "2025-02-13",
83
83
  }
84
84
  dataset_df = pd.DataFrame(