lamindb 0.76.15__py3-none-any.whl → 0.76.16__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
@@ -43,7 +43,7 @@ Modules and settings.
43
43
  """
44
44
 
45
45
  # denote a release candidate for 0.1.0 with 0.1rc1, 0.1a1, 0.1b1, etc.
46
- __version__ = "0.76.15"
46
+ __version__ = "0.76.16"
47
47
 
48
48
  import os as _os
49
49
 
lamindb/_artifact.py CHANGED
@@ -111,7 +111,12 @@ def process_pathlike(
111
111
  # for the storage root: the bucket
112
112
  if not isinstance(filepath, LocalPathClasses):
113
113
  # for a cloud path, new_root is always the bucket name
114
- new_root = list(filepath.parents)[-1]
114
+ if filepath.protocol == "hf":
115
+ hf_path = filepath.fs.resolve_path(filepath.as_posix())
116
+ hf_path.path_in_repo = ""
117
+ new_root = "hf://" + hf_path.unresolve()
118
+ else:
119
+ new_root = list(filepath.parents)[-1]
115
120
  # do not register remote storage locations on hub if the current instance
116
121
  # is not managed on the hub
117
122
  storage_settings, _ = init_storage(
@@ -213,9 +218,9 @@ def get_stat_or_artifact(
213
218
  if stat is not None:
214
219
  # convert UPathStatResult to fsspec info dict
215
220
  stat = stat.as_info()
216
- if "ETag" in stat: # is file
221
+ if (store_type := stat["type"]) == "file":
217
222
  size, hash, hash_type = get_stat_file_cloud(stat)
218
- elif stat["type"] == "directory":
223
+ elif store_type == "directory":
219
224
  size, hash, hash_type, n_objects = get_stat_dir_cloud(path)
220
225
  if hash is None:
221
226
  logger.warning(f"did not add hash for {path}")
@@ -240,7 +245,7 @@ def get_stat_or_artifact(
240
245
  .order_by("-created_at")
241
246
  .all()
242
247
  )
243
- artifact_with_same_hash_exists = len(result.filter(hash=hash).all()) > 0
248
+ artifact_with_same_hash_exists = result.filter(hash=hash).count() > 0
244
249
  if not artifact_with_same_hash_exists and len(result) > 0:
245
250
  logger.important(
246
251
  f"creating new artifact version for key='{key}' (storage: '{settings.storage.root_as_str}')"
lamindb/_can_validate.py CHANGED
@@ -108,14 +108,14 @@ def _check_organism_db(organism: Record, using_key: str | None):
108
108
 
109
109
  def _concat_lists(values: ListLike) -> list[str]:
110
110
  """Concatenate a list of lists of strings into a single list."""
111
- if len(values) > 0 and isinstance(values, (list, pd.Series)):
112
- try:
113
- if isinstance(values[0], list):
114
- if isinstance(values, pd.Series):
115
- values = values.tolist()
116
- values = sum([v for v in values if isinstance(v, list)], [])
117
- except KeyError:
118
- pass
111
+ if isinstance(values, (list, pd.Series)) and len(values) > 0:
112
+ first_item = values[0] if isinstance(values, list) else values.iloc[0]
113
+ if isinstance(first_item, list):
114
+ if isinstance(values, pd.Series):
115
+ values = values.tolist()
116
+ values = [
117
+ v for sublist in values if isinstance(sublist, list) for v in sublist
118
+ ]
119
119
  return values
120
120
 
121
121
 
@@ -250,7 +250,7 @@ def _validate(
250
250
  f"Your {cls.__name__} registry is empty, consider populating it first!"
251
251
  )
252
252
  if hasattr(cls, "source_id"):
253
- msg += "\n → use `.import_from_source()` to import records from a source, e.g. a public ontology"
253
+ msg += "\n → use `.import_source()` to import records from a source, e.g. a public ontology"
254
254
  logger.warning(msg)
255
255
  return np.array([False] * len(values))
256
256
 
@@ -388,7 +388,11 @@ def _standardize(
388
388
 
389
389
  try:
390
390
  registry._meta.get_field(synonyms_field)
391
- fields = {i for i in [field, return_field, synonyms_field] if i is not None}
391
+ fields = {
392
+ field_name
393
+ for field_name in [field, return_field, synonyms_field]
394
+ if field_name is not None
395
+ }
392
396
  df = _filter_query_based_on_organism(
393
397
  queryset=queryset,
394
398
  field=field,
@@ -445,14 +449,19 @@ def _standardize(
445
449
  if len(std_names_bt_mapper) > 0 and not mute:
446
450
  s = "" if len(std_names_bt_mapper) == 1 else "s"
447
451
  field_print = "synonym" if field == return_field else field
448
- warn_msg = (
449
- f"found {len(std_names_bt_mapper)} {field_print}{s} in Bionty:"
450
- f" {list(std_names_bt_mapper.keys())}"
452
+
453
+ reduced_mapped_keys_str = f"{list(std_names_bt_mapper.keys())[:10] + ['...'] if len(std_names_bt_mapper) > 10 else list(std_names_bt_mapper.keys())}"
454
+ truncated_note = (
455
+ " (output truncated)" if len(std_names_bt_mapper) > 10 else ""
451
456
  )
452
- warn_msg += (
453
- f"\n please add corresponding {registry._meta.model.__name__} records via"
454
- f" `.from_values({list(set(std_names_bt_mapper.values()))})`"
457
+
458
+ warn_msg = (
459
+ f"found {len(std_names_bt_mapper)} {field_print}{s} in Bionty{truncated_note}:"
460
+ f" {reduced_mapped_keys_str}\n"
461
+ f" please add corresponding {registry._meta.model.__name__} records via{truncated_note}:"
462
+ f" `.from_values({reduced_mapped_keys_str})`"
455
463
  )
464
+
456
465
  logger.warning(warn_msg)
457
466
 
458
467
  mapper.update(std_names_bt_mapper)
lamindb/_curate.py CHANGED
@@ -20,6 +20,7 @@ from .core.exceptions import ValidationError
20
20
 
21
21
  if TYPE_CHECKING:
22
22
  from collections.abc import Iterable
23
+ from typing import Any
23
24
 
24
25
  from lamindb_setup.core.types import UPathStr
25
26
  from lnschema_core.types import FieldAttr
@@ -226,7 +227,7 @@ class DataFrameCurator(BaseCurator):
226
227
  f"the following keys passed to {name} are not allowed: {nonval_keys}"
227
228
  )
228
229
 
229
- def _save_columns(self, validated_only: bool = True, **kwargs) -> None:
230
+ def _save_columns(self, validated_only: bool = True) -> None:
230
231
  """Save column name records."""
231
232
  # Always save features specified as the fields keys
232
233
  update_registry(
@@ -238,7 +239,7 @@ class DataFrameCurator(BaseCurator):
238
239
  validated_only=False,
239
240
  source=self._sources.get("columns"),
240
241
  exclude=self._exclude.get("columns"),
241
- **kwargs,
242
+ **self._kwargs, # type: ignore
242
243
  )
243
244
 
244
245
  # Save the rest of the columns based on validated_only
@@ -255,7 +256,7 @@ class DataFrameCurator(BaseCurator):
255
256
  source=self._sources.get("columns"),
256
257
  exclude=self._exclude.get("columns"),
257
258
  warning=False, # Do not warn about missing columns, just an info message
258
- **kwargs,
259
+ **self._kwargs, # type: ignore
259
260
  )
260
261
 
261
262
  def add_new_from(self, key: str, organism: str | None = None, **kwargs):
@@ -292,7 +293,7 @@ class DataFrameCurator(BaseCurator):
292
293
  f"Feature {categorical} is not part of the fields!"
293
294
  )
294
295
  update_registry(
295
- values=flatten_unique(self._df[categorical]),
296
+ values=_flatten_unique(self._df[categorical]),
296
297
  field=self.fields[categorical],
297
298
  key=categorical,
298
299
  using_key=self._using_key,
@@ -305,7 +306,6 @@ class DataFrameCurator(BaseCurator):
305
306
  def _update_registry_all(self, validated_only: bool = True, **kwargs):
306
307
  """Save labels for all features."""
307
308
  for name in self.fields.keys():
308
- logger.info(f"saving validated records of '{name}'")
309
309
  self._update_registry(name, validated_only=validated_only, **kwargs)
310
310
 
311
311
  def validate(self, organism: str | None = None) -> bool:
@@ -436,12 +436,15 @@ class AnnDataCurator(DataFrameCurator):
436
436
  ) -> None:
437
437
  from lamindb_setup.core import upath
438
438
 
439
+ if isinstance(var_index, str):
440
+ raise TypeError("var_index parameter has to be a bionty field")
441
+
439
442
  from ._artifact import data_is_anndata
440
443
 
441
444
  if sources is None:
442
445
  sources = {}
443
446
  if not data_is_anndata(data):
444
- raise ValueError(
447
+ raise TypeError(
445
448
  "data has to be an AnnData object or a path to AnnData-like"
446
449
  )
447
450
  if isinstance(data, ad.AnnData):
@@ -451,6 +454,11 @@ class AnnDataCurator(DataFrameCurator):
451
454
 
452
455
  self._adata = backed_access(upath.create_path(data))
453
456
 
457
+ if "symbol" in str(var_index):
458
+ logger.warning(
459
+ "Curating gene symbols is discouraged. See FAQ for more details."
460
+ )
461
+
454
462
  self._data = data
455
463
  self._var_field = var_index
456
464
  super().__init__(
@@ -512,10 +520,8 @@ class AnnDataCurator(DataFrameCurator):
512
520
 
513
521
  def _update_registry_all(self, validated_only: bool = True, **kwargs):
514
522
  """Save labels for all features."""
515
- logger.info("saving validated records of 'var_index'")
516
523
  self._save_from_var_index(validated_only=validated_only, **self._kwargs)
517
524
  for name in self._obs_fields.keys():
518
- logger.info(f"saving validated terms of '{name}'")
519
525
  self._update_registry(name, validated_only=validated_only, **self._kwargs)
520
526
 
521
527
  def add_new_from_var_index(self, organism: str | None = None, **kwargs):
@@ -1229,7 +1235,7 @@ def validate_categories(
1229
1235
  if n_non_validated == 0:
1230
1236
  if n_validated == 0:
1231
1237
  logger.indent = ""
1232
- logger.success(f"{key} is validated against {colors.italic(model_field)}")
1238
+ logger.success(f"'{key}' is validated against {colors.italic(model_field)}")
1233
1239
  return True, []
1234
1240
  else:
1235
1241
  # validated values still need to be saved to the current instance
@@ -1434,8 +1440,8 @@ def save_artifact(
1434
1440
  return artifact
1435
1441
 
1436
1442
 
1437
- def flatten_unique(series):
1438
- """Flatten a pandas series if it contains lists."""
1443
+ def _flatten_unique(series: pd.Series[list[Any] | Any]) -> list[Any]:
1444
+ """Flatten a Pandas series containing lists or single items into a unique list of elements."""
1439
1445
  result = set()
1440
1446
 
1441
1447
  for item in series:
@@ -1505,9 +1511,14 @@ def update_registry(
1505
1511
 
1506
1512
  public_records = [r for r in existing_and_public_records if r._state.adding]
1507
1513
  # here we check to only save the public records if they are from the specified source
1508
- # we check the uid because r.source and soruce can be from different instances
1514
+ # we check the uid because r.source and source can be from different instances
1509
1515
  if source:
1510
1516
  public_records = [r for r in public_records if r.source.uid == source.uid]
1517
+
1518
+ if public_records:
1519
+ settings.verbosity = "info"
1520
+ logger.info(f"saving validated records of '{key}'")
1521
+ settings.verbosity = "error"
1511
1522
  ln_save(public_records)
1512
1523
  labels_saved["from public"] = [
1513
1524
  getattr(r, field.field.name) for r in public_records
lamindb/_finish.py CHANGED
@@ -103,10 +103,10 @@ def save_context_core(
103
103
 
104
104
  # for scripts, things are easy
105
105
  is_consecutive = True
106
- is_notebook = transform.type == "notebook"
106
+ is_ipynb = filepath.suffix == ".ipynb"
107
107
  source_code_path = filepath
108
108
  # for notebooks, we need more work
109
- if is_notebook:
109
+ if is_ipynb:
110
110
  try:
111
111
  import jupytext
112
112
  from nbproject.dev import (
@@ -198,7 +198,7 @@ def save_context_core(
198
198
  run.finished_at = datetime.now(timezone.utc)
199
199
 
200
200
  # track report and set is_consecutive
201
- if not is_notebook:
201
+ if not is_ipynb:
202
202
  run.is_consecutive = True
203
203
  run.save()
204
204
  else:
@@ -234,8 +234,15 @@ def save_context_core(
234
234
  # finalize
235
235
  if not from_cli:
236
236
  run_time = run.finished_at - run.started_at
237
+ days = run_time.days
238
+ seconds = run_time.seconds
239
+ hours = seconds // 3600
240
+ minutes = (seconds % 3600) // 60
241
+ secs = seconds % 60
242
+ formatted_run_time = f"{days}d {hours}h {minutes}m {secs}s"
243
+
237
244
  logger.important(
238
- f"finished Run('{run.uid[:8]}') after {run_time} at {format_field_value(run.finished_at)}"
245
+ f"finished Run('{run.uid[:8]}') after {formatted_run_time} at {format_field_value(run.finished_at)}"
239
246
  )
240
247
  if ln_setup.settings.instance.is_on_hub:
241
248
  identifier = ln_setup.settings.instance.slug
@@ -244,9 +251,7 @@ def save_context_core(
244
251
  )
245
252
  if not from_cli:
246
253
  thing, name = (
247
- ("notebook", "notebook.ipynb")
248
- if is_notebook
249
- else ("script", "script.py")
254
+ ("notebook", "notebook.ipynb") if is_ipynb else ("script", "script.py")
250
255
  )
251
256
  logger.important(
252
257
  f"if you want to update your {thing} without re-running it, use `lamin save {name}`"
lamindb/_query_set.py CHANGED
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections import UserList
4
- from typing import TYPE_CHECKING, NamedTuple
4
+ from collections.abc import Iterable
5
+ from collections.abc import Iterable as IterableType
6
+ from typing import TYPE_CHECKING, Any, NamedTuple
5
7
 
6
8
  import pandas as pd
7
9
  from django.db import models
@@ -69,8 +71,33 @@ def one_helper(self):
69
71
  return self[0]
70
72
 
71
73
 
72
- def process_expressions(registry: Registry, expressions: dict) -> dict:
73
- if registry in {Artifact, Collection}:
74
+ def process_expressions(queryset: QuerySet, expressions: dict) -> dict:
75
+ def _map_databases(value: Any, key: str, target_db: str) -> tuple[str, Any]:
76
+ if isinstance(value, Record):
77
+ if value._state.db != target_db:
78
+ logger.warning(
79
+ f"passing record from database {value._state.db} to query {target_db}, matching on uid '{value.uid}'"
80
+ )
81
+ return f"{key}__uid", value.uid
82
+ return key, value
83
+
84
+ if (
85
+ key.endswith("__in")
86
+ and isinstance(value, IterableType)
87
+ and not isinstance(value, str)
88
+ ):
89
+ if any(isinstance(v, Record) and v._state.db != target_db for v in value):
90
+ logger.warning(
91
+ f"passing records from another database to query {target_db}, matching on uids"
92
+ )
93
+ return key.replace("__in", "__uid__in"), [
94
+ v.uid if isinstance(v, Record) else v for v in value
95
+ ]
96
+ return key, value
97
+
98
+ return key, value
99
+
100
+ if queryset.model in {Artifact, Collection}:
74
101
  # visibility is set to 0 unless expressions contains id or uid equality
75
102
  if not (
76
103
  "id" in expressions
@@ -87,7 +114,17 @@ def process_expressions(registry: Registry, expressions: dict) -> dict:
87
114
  # sense for a non-NULLABLE column
88
115
  elif visibility in expressions and expressions[visibility] is None:
89
116
  expressions.pop(visibility)
90
- return expressions
117
+ if queryset._db is not None:
118
+ # only check for database mismatch if there is a defined database on the
119
+ # queryset
120
+ return dict(
121
+ (
122
+ _map_databases(value, key, queryset._db)
123
+ for key, value in expressions.items()
124
+ )
125
+ )
126
+ else:
127
+ return expressions
91
128
 
92
129
 
93
130
  def get(
@@ -114,7 +151,7 @@ def get(
114
151
  return qs.one()
115
152
  else:
116
153
  assert idlike is None # noqa: S101
117
- expressions = process_expressions(registry, expressions)
154
+ expressions = process_expressions(qs, expressions)
118
155
  return registry.objects.using(qs.db).get(**expressions)
119
156
 
120
157
 
@@ -282,6 +319,14 @@ class QuerySet(models.QuerySet):
282
319
  """Query a single record. Raises error if there are more or none."""
283
320
  return get(self, idlike, **expressions)
284
321
 
322
+ def filter(self, *queries, **expressions) -> QuerySet:
323
+ """Query a set of records."""
324
+ expressions = process_expressions(self, expressions)
325
+ if len(expressions) > 0:
326
+ return super().filter(*queries, **expressions)
327
+ else:
328
+ return self
329
+
285
330
  def one(self) -> Record:
286
331
  """Exactly one result. Raises error if there are more or none."""
287
332
  return one_helper(self)
lamindb/_record.py CHANGED
@@ -1,12 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import builtins
4
+ from functools import reduce
4
5
  from typing import TYPE_CHECKING, NamedTuple
5
6
 
6
7
  import dj_database_url
7
8
  import lamindb_setup as ln_setup
9
+ from django.core.exceptions import FieldDoesNotExist
8
10
  from django.db import connections, transaction
9
- from django.db.models import IntegerField, Manager, Q, QuerySet, Value
11
+ from django.db.models import F, IntegerField, Manager, Q, QuerySet, TextField, Value
12
+ from django.db.models.functions import Cast, Coalesce
13
+ from django.db.models.lookups import Contains, Exact, IContains, IExact, IRegex, Regex
10
14
  from lamin_utils import colors, logger
11
15
  from lamin_utils._lookup import Lookup
12
16
  from lamindb_setup._connect_instance import (
@@ -17,11 +21,22 @@ from lamindb_setup._connect_instance import (
17
21
  from lamindb_setup.core._docs import doc_args
18
22
  from lamindb_setup.core._hub_core import connect_instance_hub
19
23
  from lamindb_setup.core._settings_store import instance_settings_file
20
- from lnschema_core.models import Artifact, Feature, IsVersioned, Record, Run, Transform
24
+ from lnschema_core.models import (
25
+ Artifact,
26
+ Collection,
27
+ Feature,
28
+ FeatureSet,
29
+ IsVersioned,
30
+ Param,
31
+ Record,
32
+ Run,
33
+ Transform,
34
+ ULabel,
35
+ )
21
36
 
22
37
  from ._utils import attach_func_to_class_method
23
38
  from .core._settings import settings
24
- from .core.exceptions import RecordNameChangeIntegrityError
39
+ from .core.exceptions import RecordNameChangeIntegrityError, ValidationError
25
40
 
26
41
  if TYPE_CHECKING:
27
42
  import pandas as pd
@@ -48,6 +63,7 @@ def update_attributes(record: Record, attributes: dict[str, str]):
48
63
 
49
64
 
50
65
  def validate_required_fields(record: Record, kwargs):
66
+ # a "required field" is a Django field that has `null=True, default=None`
51
67
  required_fields = {
52
68
  k.name for k in record._meta.fields if not k.null and k.default is None
53
69
  }
@@ -58,25 +74,47 @@ def validate_required_fields(record: Record, kwargs):
58
74
  ]
59
75
  if missing_fields:
60
76
  raise TypeError(f"{missing_fields} are required.")
77
+ # ensure the exact length of the internal uid for core entities
78
+ if "uid" in kwargs and record.__class__ in {
79
+ Artifact,
80
+ Collection,
81
+ Transform,
82
+ Run,
83
+ ULabel,
84
+ Feature,
85
+ FeatureSet,
86
+ Param,
87
+ }:
88
+ uid_max_length = record.__class__._meta.get_field(
89
+ "uid"
90
+ ).max_length # triggers FieldDoesNotExist
91
+ if len(kwargs["uid"]) != uid_max_length: # triggers KeyError
92
+ raise ValidationError(
93
+ f'`uid` must be exactly {uid_max_length} characters long, got {len(kwargs["uid"])}.'
94
+ )
61
95
 
62
96
 
63
- def suggest_records_with_similar_names(record: Record, kwargs) -> bool:
97
+ def suggest_records_with_similar_names(record: Record, name_field: str, kwargs) -> bool:
64
98
  """Returns True if found exact match, otherwise False.
65
99
 
66
100
  Logs similar matches if found.
67
101
  """
68
- if kwargs.get("name") is None:
102
+ if kwargs.get(name_field) is None or not isinstance(kwargs.get(name_field), str):
69
103
  return False
70
104
  queryset = _search(
71
- record.__class__, kwargs["name"], field="name", truncate_words=True, limit=3
105
+ record.__class__,
106
+ kwargs[name_field],
107
+ field=name_field,
108
+ truncate_string=True,
109
+ limit=3,
72
110
  )
73
111
  if not queryset.exists(): # empty queryset
74
112
  return False
75
113
  for alternative_record in queryset:
76
- if alternative_record.name == kwargs["name"]:
114
+ if getattr(alternative_record, name_field) == kwargs[name_field]:
77
115
  return True
78
116
  s, it, nots = ("", "it", "s") if len(queryset) == 1 else ("s", "one of them", "")
79
- msg = f"record{s} with similar name{s} exist{nots}! did you mean to load {it}?"
117
+ msg = f"record{s} with similar {name_field}{s} exist{nots}! did you mean to load {it}?"
80
118
  if IPYTHON:
81
119
  from IPython.display import display
82
120
 
@@ -98,13 +136,19 @@ def __init__(record: Record, *args, **kwargs):
98
136
  if "_has_consciously_provided_uid" in kwargs:
99
137
  has_consciously_provided_uid = kwargs.pop("_has_consciously_provided_uid")
100
138
  if settings.creation.search_names and not has_consciously_provided_uid:
101
- match = suggest_records_with_similar_names(record, kwargs)
139
+ name_field = (
140
+ "name" if not hasattr(record, "_name_field") else record._name_field
141
+ )
142
+ match = suggest_records_with_similar_names(record, name_field, kwargs)
102
143
  if match:
103
144
  if "version" in kwargs:
104
145
  if kwargs["version"] is not None:
105
146
  version_comment = " and version"
106
147
  existing_record = record.__class__.filter(
107
- name=kwargs["name"], version=kwargs["version"]
148
+ **{
149
+ name_field: kwargs[name_field],
150
+ "version": kwargs["version"],
151
+ }
108
152
  ).one_or_none()
109
153
  else:
110
154
  # for a versioned record, an exact name match is not a
@@ -115,12 +159,12 @@ def __init__(record: Record, *args, **kwargs):
115
159
  else:
116
160
  version_comment = ""
117
161
  existing_record = record.__class__.filter(
118
- name=kwargs["name"]
162
+ **{name_field: kwargs[name_field]}
119
163
  ).one_or_none()
120
164
  if existing_record is not None:
121
165
  logger.important(
122
166
  f"returning existing {record.__class__.__name__} record with same"
123
- f" name{version_comment}: '{kwargs['name']}'"
167
+ f" {name_field}{version_comment}: '{kwargs[name_field]}'"
124
168
  )
125
169
  init_self_from_db(record, existing_record)
126
170
  return None
@@ -137,9 +181,13 @@ def __init__(record: Record, *args, **kwargs):
137
181
  @doc_args(Record.filter.__doc__)
138
182
  def filter(cls, *queries, **expressions) -> QuerySet:
139
183
  """{}""" # noqa: D415
140
- from lamindb._filter import filter
184
+ from lamindb._query_set import QuerySet
141
185
 
142
- return filter(cls, *queries, **expressions)
186
+ _using_key = None
187
+ if "_using_key" in expressions:
188
+ _using_key = expressions.pop("_using_key")
189
+
190
+ return QuerySet(model=cls, using=_using_key).filter(*queries, **expressions)
143
191
 
144
192
 
145
193
  @classmethod # type:ignore
@@ -150,8 +198,6 @@ def get(
150
198
  **expressions,
151
199
  ) -> Record:
152
200
  """{}""" # noqa: D415
153
- # this is the only place in which we need the lamindb queryset
154
- # in this file; everywhere else it should be Django's
155
201
  from lamindb._query_set import QuerySet
156
202
 
157
203
  return QuerySet(model=cls).get(idlike, **expressions)
@@ -166,9 +212,7 @@ def df(
166
212
  limit: int = 100,
167
213
  ) -> pd.DataFrame:
168
214
  """{}""" # noqa: D415
169
- from lamindb._filter import filter
170
-
171
- query_set = filter(cls)
215
+ query_set = cls.filter()
172
216
  if hasattr(cls, "updated_at"):
173
217
  query_set = query_set.order_by("-updated_at")
174
218
  return query_set[:limit].df(include=include, join=join)
@@ -182,7 +226,7 @@ def _search(
182
226
  limit: int | None = 20,
183
227
  case_sensitive: bool = False,
184
228
  using_key: str | None = None,
185
- truncate_words: bool = False,
229
+ truncate_string: bool = False,
186
230
  ) -> QuerySet:
187
231
  input_queryset = _queryset(cls, using_key=using_key)
188
232
  registry = input_queryset.model
@@ -209,48 +253,67 @@ def _search(
209
253
  else:
210
254
  fields.append(field)
211
255
 
212
- # decompose search string
213
- def truncate_word(word) -> str:
214
- if len(word) > 5:
215
- n_80_pct = int(len(word) * 0.8)
216
- return word[:n_80_pct]
217
- elif len(word) > 3:
218
- return word[:3]
219
- else:
220
- return word
221
-
222
- decomposed_string = str(string).split()
223
- # add the entire string back
224
- decomposed_string += [string]
225
- for word in decomposed_string:
226
- # will not search against words with 3 or fewer characters
227
- if len(word) <= 3:
228
- decomposed_string.remove(word)
229
- if truncate_words:
230
- decomposed_string = [truncate_word(word) for word in decomposed_string]
231
- # construct the query
232
- expression = Q()
233
- case_sensitive_i = "" if case_sensitive else "i"
234
- for field in fields:
235
- for word in decomposed_string:
236
- query = {f"{field}__{case_sensitive_i}contains": word}
237
- expression |= Q(**query)
238
- output_queryset = input_queryset.filter(expression)
239
- # ensure exact matches are at the top
240
- narrow_expression = Q()
256
+ if truncate_string:
257
+ if (len_string := len(string)) > 5:
258
+ n_80_pct = int(len_string * 0.8)
259
+ string = string[:n_80_pct]
260
+
261
+ string = string.strip()
262
+
263
+ exact_lookup = Exact if case_sensitive else IExact
264
+ regex_lookup = Regex if case_sensitive else IRegex
265
+ contains_lookup = Contains if case_sensitive else IContains
266
+
267
+ ranks = []
268
+ contains_filters = []
241
269
  for field in fields:
242
- query = {f"{field}__{case_sensitive_i}contains": string}
243
- narrow_expression |= Q(**query)
244
- refined_output_queryset = output_queryset.filter(narrow_expression).annotate(
245
- ordering=Value(1, output_field=IntegerField())
246
- )
247
- remaining_output_queryset = output_queryset.exclude(narrow_expression).annotate(
248
- ordering=Value(2, output_field=IntegerField())
270
+ field_expr = Coalesce(
271
+ Cast(field, output_field=TextField()),
272
+ Value(""),
273
+ output_field=TextField(),
274
+ )
275
+ # exact rank
276
+ exact_expr = exact_lookup(field_expr, string)
277
+ exact_rank = Cast(exact_expr, output_field=IntegerField()) * 200
278
+ ranks.append(exact_rank)
279
+ # exact synonym
280
+ synonym_expr = regex_lookup(field_expr, rf"(?:^|.*\|){string}(?:\|.*|$)")
281
+ synonym_rank = Cast(synonym_expr, output_field=IntegerField()) * 200
282
+ ranks.append(synonym_rank)
283
+ # match as sub-phrase
284
+ sub_expr = regex_lookup(
285
+ field_expr, rf"(?:^|.*[ \|\.,;:]){string}(?:[ \|\.,;:].*|$)"
286
+ )
287
+ sub_rank = Cast(sub_expr, output_field=IntegerField()) * 10
288
+ ranks.append(sub_rank)
289
+ # startswith and avoid matching string with " " on the right
290
+ # mostly for truncated
291
+ startswith_expr = regex_lookup(field_expr, rf"(?:^|\|){string}[^ ]*(\||$)")
292
+ startswith_rank = Cast(startswith_expr, output_field=IntegerField()) * 8
293
+ ranks.append(startswith_rank)
294
+ # match as sub-phrase from the left, mostly for truncated
295
+ right_expr = regex_lookup(field_expr, rf"(?:^|.*[ \|]){string}.*")
296
+ right_rank = Cast(right_expr, output_field=IntegerField()) * 2
297
+ ranks.append(right_rank)
298
+ # match as sub-phrase from the right
299
+ left_expr = regex_lookup(field_expr, rf".*{string}(?:$|[ \|\.,;:].*)")
300
+ left_rank = Cast(left_expr, output_field=IntegerField()) * 2
301
+ ranks.append(left_rank)
302
+ # simple contains filter
303
+ contains_expr = contains_lookup(field_expr, string)
304
+ contains_filter = Q(contains_expr)
305
+ contains_filters.append(contains_filter)
306
+ # also rank by contains
307
+ contains_rank = Cast(contains_expr, output_field=IntegerField())
308
+ ranks.append(contains_rank)
309
+
310
+ ranked_queryset = (
311
+ input_queryset.filter(reduce(lambda a, b: a | b, contains_filters))
312
+ .alias(rank=sum(ranks))
313
+ .order_by("-rank")
249
314
  )
250
- combined_queryset = refined_output_queryset.union(
251
- remaining_output_queryset
252
- ).order_by("ordering")[:limit]
253
- return combined_queryset
315
+
316
+ return ranked_queryset[:limit]
254
317
 
255
318
 
256
319
  @classmethod # type: ignore
lamindb/core/_context.py CHANGED
@@ -82,11 +82,14 @@ def raise_missing_context(transform_type: str, key: str) -> bool:
82
82
  transform = Transform.filter(key=key).latest_version().first()
83
83
  if transform is None:
84
84
  new_uid = f"{base62_12()}0000"
85
- message = f'to track this {transform_type}, copy & paste `ln.track("{new_uid}")` and re-run'
85
+ message = f'to track this {transform_type}, run: ln.track("{new_uid}")'
86
86
  else:
87
87
  uid = transform.uid
88
88
  new_uid = f"{uid[:-4]}{increment_base62(uid[-4:])}"
89
- message = f"you already have a transform with key '{key}' ('{transform.uid}')\n - to make a revision, call `ln.track('{new_uid}')`\n - to create a new transform, rename your file and run: `ln.track()`"
89
+ message = (
90
+ f"you already have a transform with key '{key}': Transform('{transform.uid[:8]}')\n"
91
+ f' (1) to make a revision, run: ln.track("{new_uid}")\n (2) to create a new transform, rename your {transform_type} file and re-run: ln.track()'
92
+ )
90
93
  if transform_type == "notebook":
91
94
  print(f"→ {message}")
92
95
  response = input("→ Ready to re-run? (y/n)")
@@ -118,6 +121,8 @@ class Context:
118
121
  Enables convenient data lineage tracking by managing a transform & run
119
122
  upon :meth:`~lamindb.core.Context.track` & :meth:`~lamindb.core.Context.finish`.
120
123
 
124
+ Guide: :doc:`/track`
125
+
121
126
  Examples:
122
127
 
123
128
  Is typically used via the global :class:`~lamindb.context` object via `ln.track()` and `ln.finish()`:
@@ -137,7 +142,8 @@ class Context:
137
142
  self._run: Run | None = None
138
143
  self._path: Path | None = None
139
144
  """A local path to the script that's running."""
140
- self._logging_message: str = ""
145
+ self._logging_message_track: str = ""
146
+ self._logging_message_imports: str = ""
141
147
 
142
148
  @property
143
149
  def transform(self) -> Transform | None:
@@ -178,12 +184,11 @@ class Context:
178
184
 
179
185
  def track(
180
186
  self,
181
- uid: str | None = None,
187
+ transform: str | Transform | None = None,
182
188
  *,
183
189
  params: dict | None = None,
184
190
  new_run: bool | None = None,
185
191
  path: str | None = None,
186
- transform: Transform | None = None,
187
192
  ) -> None:
188
193
  """Initiate a run with tracked data lineage.
189
194
 
@@ -196,24 +201,31 @@ class Context:
196
201
  script-like transform exists in a git repository and links it.
197
202
 
198
203
  Args:
199
- uid: A `uid` to create or load a transform.
204
+ transform: A transform `uid` or record. If `None`, creates a `uid`.
200
205
  params: A dictionary of parameters to track for the run.
201
206
  new_run: If `False`, loads latest run of transform
202
207
  (default notebook), if `True`, creates new run (default pipeline).
203
208
  path: Filepath of notebook or script. Only needed if it can't be
204
209
  automatically detected.
205
- transform: Useful to track an abstract pipeline.
206
210
 
207
211
  Examples:
208
212
 
209
- To track the run of a notebook or script, call:
213
+ To create a transform `uid` for tracking a script or notebook, call:
210
214
 
211
- >>> import lamindb as ln
212
215
  >>> ln.track()
213
216
 
217
+ To track the run of a notebook or script, call:
218
+
219
+ >>> ln.track("FPnfDtJz8qbE0000") # replace with your uid
220
+
214
221
  """
215
- if uid is not None:
222
+ self._logging_message_track = ""
223
+ self._logging_message_imports = ""
224
+ uid = None
225
+ if transform is not None and isinstance(transform, str):
226
+ uid = transform
216
227
  self.uid = uid
228
+ transform = None
217
229
  self._path = None
218
230
  if transform is None:
219
231
  is_tracked = False
@@ -223,17 +235,20 @@ class Context:
223
235
  )
224
236
  transform = None
225
237
  stem_uid = None
238
+ # you can set ln.context.uid and then call ln.track() without passing anythin
239
+ # that has been the preferred syntax for a while; we'll likely
240
+ # deprecate it at some point
226
241
  if uid is not None or self.uid is not None:
227
242
  transform = Transform.filter(uid=self.uid).one_or_none()
228
243
  if self.version is not None:
229
244
  # test inconsistent version passed
230
245
  if (
231
246
  transform is not None
232
- and transform.version is not None
233
- and self.version != transform.version
247
+ and transform.version is not None # type: ignore
248
+ and self.version != transform.version # type: ignore
234
249
  ):
235
250
  raise SystemExit(
236
- f"Please pass consistent version: ln.context.version = '{transform.version}'"
251
+ f"Please pass consistent version: ln.context.version = '{transform.version}'" # type: ignore
237
252
  )
238
253
  # test whether version was already used for another member of the family
239
254
  suid, vuid = (
@@ -302,10 +317,14 @@ class Context:
302
317
  transform_exists = Transform.filter(id=transform.id).first()
303
318
  if transform_exists is None:
304
319
  transform.save()
305
- self._logging_message += f"created Transform('{transform.uid[:8]}')"
320
+ self._logging_message_track += (
321
+ f"created Transform('{transform.uid[:8]}')"
322
+ )
306
323
  transform_exists = transform
307
324
  else:
308
- self._logging_message += f"loaded Transform('{transform.uid[:8]}')"
325
+ self._logging_message_track += (
326
+ f"loaded Transform('{transform.uid[:8]}')"
327
+ )
309
328
  self._transform = transform_exists
310
329
 
311
330
  if new_run is None: # for notebooks, default to loading latest runs
@@ -322,7 +341,7 @@ class Context:
322
341
  )
323
342
  if run is not None: # loaded latest run
324
343
  run.started_at = datetime.now(timezone.utc) # update run time
325
- self._logging_message += f", started Run('{run.uid[:8]}') at {format_field_value(run.started_at)}"
344
+ self._logging_message_track += f", started Run('{run.uid[:8]}') at {format_field_value(run.started_at)}"
326
345
 
327
346
  if run is None: # create new run
328
347
  run = Run(
@@ -330,7 +349,7 @@ class Context:
330
349
  params=params,
331
350
  )
332
351
  run.started_at = datetime.now(timezone.utc)
333
- self._logging_message += f", started new Run('{run.uid[:8]}') at {format_field_value(run.started_at)}"
352
+ self._logging_message_track += f", started new Run('{run.uid[:8]}') at {format_field_value(run.started_at)}"
334
353
  # can only determine at ln.finish() if run was consecutive in
335
354
  # interactive session, otherwise, is consecutive
336
355
  run.is_consecutive = True if is_run_from_ipython else None
@@ -338,13 +357,14 @@ class Context:
338
357
  run.save()
339
358
  if params is not None:
340
359
  run.params.add_values(params)
341
- self._logging_message += "\n→ params: " + " ".join(
360
+ self._logging_message_track += "\n→ params: " + " ".join(
342
361
  f"{key}='{value}'" for key, value in params.items()
343
362
  )
344
363
  self._run = run
345
364
  track_environment(run)
346
- logger.important(self._logging_message)
347
- self._logging_message = ""
365
+ logger.important(self._logging_message_track)
366
+ if self._logging_message_imports:
367
+ logger.important(self._logging_message_imports)
348
368
 
349
369
  def _track_script(
350
370
  self,
@@ -406,9 +426,9 @@ class Context:
406
426
  from nbproject.dev._pypackage import infer_pypackages
407
427
 
408
428
  nb = nbproject.dev.read_notebook(path_str)
409
- logger.important(
429
+ self._logging_message_imports += (
410
430
  "notebook imports:"
411
- f" {pretty_pypackages(infer_pypackages(nb, pin_versions=True))}"
431
+ f" {pretty_pypackages(infer_pypackages(nb, pin_versions=True))}\n"
412
432
  )
413
433
  except Exception:
414
434
  logger.debug("inferring imported packages failed")
@@ -471,7 +491,7 @@ class Context:
471
491
  raise_update_context = True
472
492
  if raise_update_context:
473
493
  raise UpdateContext(get_key_clashing_message(revises, key))
474
- self._logging_message += f"created Transform('{transform.uid[:8]}')"
494
+ self._logging_message_track += f"created Transform('{transform.uid[:8]}')"
475
495
  else:
476
496
  uid = transform.uid
477
497
  # transform was already saved via `finish()`
@@ -485,7 +505,7 @@ class Context:
485
505
  elif transform.name != name:
486
506
  transform.name = name
487
507
  transform.save()
488
- self._logging_message += (
508
+ self._logging_message_track += (
489
509
  "updated transform name, " # white space on purpose
490
510
  )
491
511
  elif (
@@ -509,7 +529,7 @@ class Context:
509
529
  if condition:
510
530
  bump_revision = True
511
531
  else:
512
- self._logging_message += (
532
+ self._logging_message_track += (
513
533
  f"loaded Transform('{transform.uid[:8]}')"
514
534
  )
515
535
  if bump_revision:
@@ -523,7 +543,9 @@ class Context:
523
543
  f'ln.track("{uid[:-4]}{increment_base62(uid[-4:])}")'
524
544
  )
525
545
  else:
526
- self._logging_message += f"loaded Transform('{transform.uid[:8]}')"
546
+ self._logging_message_track += (
547
+ f"loaded Transform('{transform.uid[:8]}')"
548
+ )
527
549
  self._transform = transform
528
550
 
529
551
  def finish(self, ignore_non_consecutive: None | bool = None) -> None:
@@ -202,7 +202,7 @@ class LabelManager:
202
202
  transfer_logs = {"mapped": [], "transferred": [], "run": None}
203
203
  using_key = settings._using_key
204
204
  for related_name, (_, labels) in get_labels_as_dict(
205
- data, instance=self._host._state.db
205
+ data, instance=data._state.db
206
206
  ).items():
207
207
  labels = labels.all()
208
208
  if not labels.exists():
@@ -54,13 +54,16 @@ if anndata_version_parse < version.parse("0.10.0"):
54
54
  return SparseDataset(group)
55
55
 
56
56
  else:
57
+ if anndata_version_parse >= version.parse("0.11.0"):
58
+ from anndata._core.sparse_dataset import ( # type: ignore
59
+ _CSRDataset as CSRDataset,
60
+ )
61
+ else:
62
+ from anndata._core.sparse_dataset import CSRDataset # type: ignore
57
63
  from anndata._core.sparse_dataset import (
58
64
  BaseCompressedSparseDataset as SparseDataset,
59
65
  )
60
- from anndata._core.sparse_dataset import ( # type: ignore
61
- CSRDataset,
62
- sparse_dataset,
63
- )
66
+ from anndata._core.sparse_dataset import sparse_dataset # type: ignore
64
67
 
65
68
  def _check_group_format(*args):
66
69
  pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamindb
3
- Version: 0.76.15
3
+ Version: 0.76.16
4
4
  Summary: A data framework for biology.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.9,<3.13
@@ -9,20 +9,22 @@ Classifier: Programming Language :: Python :: 3.9
9
9
  Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
- Requires-Dist: lnschema_core==0.76.1
12
+ Requires-Dist: lnschema_core==0.76.2
13
13
  Requires-Dist: lamin_utils==0.13.7
14
- Requires-Dist: lamin_cli==0.20.1
14
+ Requires-Dist: lamin_cli==0.21.2
15
15
  Requires-Dist: lamindb_setup
16
16
  Requires-Dist: rapidfuzz
17
17
  Requires-Dist: pyarrow
18
18
  Requires-Dist: typing_extensions!=4.6.0
19
19
  Requires-Dist: python-dateutil
20
- Requires-Dist: anndata>=0.8.0,<=0.10.9
20
+ Requires-Dist: anndata>=0.8.0,<=0.11.1
21
21
  Requires-Dist: fsspec
22
22
  Requires-Dist: graphviz
23
23
  Requires-Dist: psycopg2-binary
24
24
  Requires-Dist: lamindb_setup[aws] ; extra == "aws"
25
- Requires-Dist: bionty==0.52.0 ; extra == "bionty"
25
+ Requires-Dist: bionty==0.52.1 ; extra == "bionty"
26
+ Requires-Dist: cellregistry ; extra == "cellregistry"
27
+ Requires-Dist: clinicore ; extra == "clinicore"
26
28
  Requires-Dist: line_profiler ; extra == "dev"
27
29
  Requires-Dist: pre-commit ; extra == "dev"
28
30
  Requires-Dist: nox ; extra == "dev"
@@ -35,19 +37,29 @@ Requires-Dist: nbproject_test>=0.5.1 ; extra == "dev"
35
37
  Requires-Dist: faker-biology ; extra == "dev"
36
38
  Requires-Dist: django-schema-graph ; extra == "erdiagram"
37
39
  Requires-Dist: readfcs>=1.1.9 ; extra == "fcs"
40
+ Requires-Dist: findrefs ; extra == "findrefs"
38
41
  Requires-Dist: lamindb_setup[gcp] ; extra == "gcp"
39
42
  Requires-Dist: nbproject==0.10.5 ; extra == "jupyter"
40
43
  Requires-Dist: jupytext ; extra == "jupyter"
41
44
  Requires-Dist: nbconvert ; extra == "jupyter"
45
+ Requires-Dist: omop ; extra == "omop"
46
+ Requires-Dist: ourprojects ; extra == "ourprojects"
47
+ Requires-Dist: wetlab ; extra == "wetlab"
42
48
  Requires-Dist: zarr>=2.16.0 ; extra == "zarr"
43
49
  Project-URL: Home, https://github.com/laminlabs/lamindb
44
50
  Provides-Extra: aws
45
51
  Provides-Extra: bionty
52
+ Provides-Extra: cellregistry
53
+ Provides-Extra: clinicore
46
54
  Provides-Extra: dev
47
55
  Provides-Extra: erdiagram
48
56
  Provides-Extra: fcs
57
+ Provides-Extra: findrefs
49
58
  Provides-Extra: gcp
50
59
  Provides-Extra: jupyter
60
+ Provides-Extra: omop
61
+ Provides-Extra: ourprojects
62
+ Provides-Extra: wetlab
51
63
  Provides-Extra: zarr
52
64
 
53
65
  [![Stars](https://img.shields.io/github/stars/laminlabs/lamindb?logo=GitHub&color=yellow)](https://github.com/laminlabs/lamindb)
@@ -1,18 +1,17 @@
1
- lamindb/__init__.py,sha256=ZoRboX4PQc0sbVajeZ1fd4GoLN3YDj5U74zVUpcSd9I,2278
2
- lamindb/_artifact.py,sha256=WNdKAJFu3sFgQ_Qe1JflDHiTP4EGhAIkzaHFwTthAjY,44903
3
- lamindb/_can_validate.py,sha256=TKfkHgkPl1bwuJrQCp6pHgQbi7m2pc_zjFhZfUAUU20,19573
1
+ lamindb/__init__.py,sha256=kJTUjuddkfht0Wb1zhHebfdwK5q1CGpluwZwj8cvYfs,2278
2
+ lamindb/_artifact.py,sha256=4sHLnJQdWAjplB-dk0JXzhrHaaDn2ri5SeyvA7yzajQ,45166
3
+ lamindb/_can_validate.py,sha256=zRmSOJqPwwHcmRHDH18Dbn6eklY62vgiLpyKgvXpbLU,19956
4
4
  lamindb/_collection.py,sha256=MLOEoOgTu7rTlRD7zkm1k0YIk_gVhQDO17JbmZCptOs,14573
5
- lamindb/_curate.py,sha256=pLsdtnnTn0qQbsUB4hrU3yuoVnVNh-BQe10EpMNZ4Ns,64083
5
+ lamindb/_curate.py,sha256=aBpw0RdzDeFd4NdMzr5d4ZbpwOinONwcELnEy_odSNw,64493
6
6
  lamindb/_feature.py,sha256=9cgrcHoyOa1jpON-9KiUfFSHcxiGECiefUAqAx4cVvU,5325
7
7
  lamindb/_feature_set.py,sha256=WdXw_YGlMXCs8l0WVHOrqvvrH2hsQLqCiho8LFDYwhI,8161
8
- lamindb/_filter.py,sha256=Pf9NHV4gm7NOC0Frtvx4W7nvwt2EowOP74DwppyXAZs,635
9
- lamindb/_finish.py,sha256=VMAmxCUFmTKIMSCx7LEh4QAnWDeue6MeUAAzkMVEYMU,9546
8
+ lamindb/_finish.py,sha256=r8q6rhml2vHiDlojCnfSpS8pbfnq7u6MwIcBIwuVq2o,9745
10
9
  lamindb/_from_values.py,sha256=uRtZLaMWKoANMMXm1hrADHfckRCTiK8_d02Yp07nLkw,14119
11
10
  lamindb/_is_versioned.py,sha256=GWZk-usV6aB33Cl9AlrnEGE5nxUkZic7QJzOW_DrwQA,1298
12
11
  lamindb/_parents.py,sha256=INhbqh6IaUjuYVUOp-6rnOGN-8kGZirNqqW9XQ1qz_M,17119
13
12
  lamindb/_query_manager.py,sha256=noc05Ad-aADxckOVBVDAiErFB7gL8XTgckELvI4rGmM,3702
14
- lamindb/_query_set.py,sha256=6vHOvB_uXzKVVIw8AAVw7EYOIAGuw3TYcUzkpNlFLdE,12973
15
- lamindb/_record.py,sha256=isR9GMQFwUUwVSmPNABvEzcJS38TbjhD7Cc6kygPsTA,26819
13
+ lamindb/_query_set.py,sha256=xcGQZvWIGM9gQPtES3jQjZL_wCaUAp-ZNeVb1Kv-Ado,14682
14
+ lamindb/_record.py,sha256=nVkVnOcVFxVyE5a1boXtKp4i4idlIJHJevXfHiwoxSk,29149
16
15
  lamindb/_run.py,sha256=K_5drpLn3D7y3XtZ3vtAw35rG5RCSvB4bXQZx4ESSI0,1964
17
16
  lamindb/_save.py,sha256=OD052Qr_hiMyAonHTktKETe_Bhnp1RY810y0rwZqpBQ,11352
18
17
  lamindb/_storage.py,sha256=GBVChv-DHVMNEBJL5l_JT6B4RDhZ6NnwgzmUICphYKk,413
@@ -21,11 +20,11 @@ lamindb/_ulabel.py,sha256=DQQzAPkrOg8W9I77BJ5umajR8MQcFSvXYUy53YNN2HA,1604
21
20
  lamindb/_utils.py,sha256=LGdiW4k3GClLz65vKAVRkL6Tw-Gkx9DWAdez1jyA5bE,428
22
21
  lamindb/_view.py,sha256=4Ln2ItTb3857PAI-70O8eJYqoTJ_NNFc7E_wds6OGns,2412
23
22
  lamindb/core/__init__.py,sha256=y87MCP1BEC2qHNVDIOwqVteIP_2hPCdIoa9JXr0EG8U,1524
24
- lamindb/core/_context.py,sha256=dI3z7fCMRPC3IMb7-EIaQYhacSZBA4HfLVFyoJtVL7I,22900
23
+ lamindb/core/_context.py,sha256=6TJzCA88F4LUxLXBVpJ0UAeKhHgL8WieKgsbXmVtlnU,23803
25
24
  lamindb/core/_data.py,sha256=BVZkxK8aloSecH25LivbwnjcG1fz7Gs2UDceO5pWd3I,20049
26
25
  lamindb/core/_django.py,sha256=yeMPp1n9WrFmEjVRdavfpVqAolPLd24RseTQlvsK67w,7157
27
26
  lamindb/core/_feature_manager.py,sha256=q4WmzJvFLL_fAs-vNRgV2klanAoU6Wu8_g0O2dyIjVg,40027
28
- lamindb/core/_label_manager.py,sha256=yh-r4KbtOArMUKPJL75yOxJc8HUKqsik8pExBVKyDlA,10949
27
+ lamindb/core/_label_manager.py,sha256=Y0NdvXLGB3RTYPnNJiGfnhnvYWSS7XsOEj9UattJw3c,10943
29
28
  lamindb/core/_mapped_collection.py,sha256=EDS0xzOdCc_iGE_Iqv5COTVHNm4jWue7Jtcd8DdXkJU,24591
30
29
  lamindb/core/_settings.py,sha256=6jNadlQdimxCsKR2ZyUD0YJYzOdubTnKktki-MqEWqQ,6137
31
30
  lamindb/core/_sync_git.py,sha256=lIgl6YfpH4rCFT1WILAp7zlemZfxog1d0zp3cX0KIZw,4531
@@ -40,7 +39,7 @@ lamindb/core/datasets/__init__.py,sha256=zRP98oqUAaXhqWyKMiH0s_ImVIuNeziQQ2kQ_t0
40
39
  lamindb/core/datasets/_core.py,sha256=JGP_q-OQibDCEaI54jZ2F6fSbSW9Yg6oYOqgOCXM0v4,20414
41
40
  lamindb/core/datasets/_fake.py,sha256=BZF9R_1iF0HDnvtZNqL2FtsjSMuqDIfuFxnw_LJYIh4,953
42
41
  lamindb/core/storage/__init__.py,sha256=JOIMu_7unbyhndtH1j0Q-9AvY8knSuc1IJO9sQnyBAQ,498
43
- lamindb/core/storage/_anndata_accessor.py,sha256=2p1HjoatmZjQ1u94tjgmXgiv8MKowrQH5xInDmiLCw4,24191
42
+ lamindb/core/storage/_anndata_accessor.py,sha256=C321qng00vMmugukxv5dX8z3oJeRxq869DgAGaEd5rg,24413
44
43
  lamindb/core/storage/_anndata_sizes.py,sha256=aXO3OB--tF5MChenSsigW6Q-RuE8YJJOUTVukkLrv9A,1029
45
44
  lamindb/core/storage/_backed_access.py,sha256=t9iS9mlZQBy1FfIS-Twt-96npYiShbPwEo2y9_3b6jY,3517
46
45
  lamindb/core/storage/_pyarrow_dataset.py,sha256=wuLsEvdblqMdUdDfMtis8AWrE3igzvFWTSTbxuD1Oc8,926
@@ -56,7 +55,7 @@ lamindb/integrations/__init__.py,sha256=RWGMYYIzr8zvmNPyVB4m-p4gMDhxdRbjES2Ed23O
56
55
  lamindb/integrations/_vitessce.py,sha256=uPl45_w4Uu9_BhpBDDVonC1nDOuAnB7DAnzi5w5bZAE,4032
57
56
  lamindb/setup/__init__.py,sha256=OwZpZzPDv5lPPGXZP7-zK6UdO4FHvvuBh439yZvIp3A,410
58
57
  lamindb/setup/core/__init__.py,sha256=SevlVrc2AZWL3uALbE5sopxBnIZPWZ1IB0NBDudiAL8,167
59
- lamindb-0.76.15.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
60
- lamindb-0.76.15.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
61
- lamindb-0.76.15.dist-info/METADATA,sha256=31K0SoWfAxBpWR3x_7PLFfwpu-mnsQ8IHFqsvjdg_cw,2365
62
- lamindb-0.76.15.dist-info/RECORD,,
58
+ lamindb-0.76.16.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
59
+ lamindb-0.76.16.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
60
+ lamindb-0.76.16.dist-info/METADATA,sha256=kAhqI1VyLBx0iXLsQIt31re6xPcJEG2YiXS0N1BaO_M,2797
61
+ lamindb-0.76.16.dist-info/RECORD,,
lamindb/_filter.py DELETED
@@ -1,21 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- from ._query_set import QuerySet, process_expressions
6
-
7
- if TYPE_CHECKING:
8
- from lnschema_core import Record
9
-
10
-
11
- def filter(registry: type[Record], *queries, **expressions) -> QuerySet:
12
- """See :meth:`~lamindb.core.Record.filter`."""
13
- _using_key = None
14
- if "_using_key" in expressions:
15
- _using_key = expressions.pop("_using_key")
16
- expressions = process_expressions(registry, expressions)
17
- qs = QuerySet(model=registry, using=_using_key)
18
- if len(expressions) > 0:
19
- return qs.filter(*queries, **expressions)
20
- else:
21
- return qs