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 +1 -1
- lamindb/_artifact.py +9 -4
- lamindb/_can_validate.py +25 -16
- lamindb/_curate.py +23 -12
- lamindb/_finish.py +12 -7
- lamindb/_query_set.py +50 -5
- lamindb/_record.py +123 -60
- lamindb/core/_context.py +48 -26
- lamindb/core/_label_manager.py +1 -1
- lamindb/core/storage/_anndata_accessor.py +7 -4
- {lamindb-0.76.15.dist-info → lamindb-0.76.16.dist-info}/METADATA +17 -5
- {lamindb-0.76.15.dist-info → lamindb-0.76.16.dist-info}/RECORD +14 -15
- lamindb/_filter.py +0 -21
- {lamindb-0.76.15.dist-info → lamindb-0.76.16.dist-info}/LICENSE +0 -0
- {lamindb-0.76.15.dist-info → lamindb-0.76.16.dist-info}/WHEEL +0 -0
lamindb/__init__.py
CHANGED
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
|
-
|
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
|
221
|
+
if (store_type := stat["type"]) == "file":
|
217
222
|
size, hash, hash_type = get_stat_file_cloud(stat)
|
218
|
-
elif
|
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 =
|
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
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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 `.
|
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 = {
|
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
|
-
|
449
|
-
|
450
|
-
|
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
|
-
|
453
|
-
|
454
|
-
f"
|
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
|
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
|
-
**
|
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
|
-
**
|
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=
|
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
|
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
|
1438
|
-
"""Flatten a
|
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
|
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
|
-
|
106
|
+
is_ipynb = filepath.suffix == ".ipynb"
|
107
107
|
source_code_path = filepath
|
108
108
|
# for notebooks, we need more work
|
109
|
-
if
|
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
|
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 {
|
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
|
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(
|
73
|
-
|
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
|
-
|
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(
|
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
|
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(
|
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__,
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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"
|
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.
|
184
|
+
from lamindb._query_set import QuerySet
|
141
185
|
|
142
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
251
|
-
|
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},
|
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 =
|
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.
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
320
|
+
self._logging_message_track += (
|
321
|
+
f"created Transform('{transform.uid[:8]}')"
|
322
|
+
)
|
306
323
|
transform_exists = transform
|
307
324
|
else:
|
308
|
-
self.
|
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.
|
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.
|
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.
|
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.
|
347
|
-
self.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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:
|
lamindb/core/_label_manager.py
CHANGED
@@ -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=
|
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
|
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.
|
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.
|
12
|
+
Requires-Dist: lnschema_core==0.76.2
|
13
13
|
Requires-Dist: lamin_utils==0.13.7
|
14
|
-
Requires-Dist: lamin_cli==0.
|
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.
|
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.
|
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
|
[](https://github.com/laminlabs/lamindb)
|
@@ -1,18 +1,17 @@
|
|
1
|
-
lamindb/__init__.py,sha256=
|
2
|
-
lamindb/_artifact.py,sha256=
|
3
|
-
lamindb/_can_validate.py,sha256=
|
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=
|
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/
|
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=
|
15
|
-
lamindb/_record.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
60
|
-
lamindb-0.76.
|
61
|
-
lamindb-0.76.
|
62
|
-
lamindb-0.76.
|
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
|
File without changes
|
File without changes
|