What does the key parameter do under the hood?ΒΆ
LaminDB is designed around associating biological metadata to artifacts and collections. This enables querying for them in storage by metadata and removes the requirement for semantic artifact and collection names.
Here, we will discuss trade-offs for using the key
parameter, which allows for semantic keys, in various scenarios.
SetupΒΆ
Weβre simulating an artifact system with several nested folders and artifacts. Such structures are resembled in, for example, the RxRx: cell imaging guide.
import random
import string
from pathlib import Path
def create_complex_biological_hierarchy(root_folder):
root_path = Path(root_folder)
if root_path.exists():
print("Folder structure already exists. Skipping...")
else:
root_path.mkdir()
raw_folder = root_path / "raw"
preprocessed_folder = root_path / "preprocessed"
raw_folder.mkdir()
preprocessed_folder.mkdir()
for i in range(1, 5):
artifact_name = f"raw_data_{i}.txt"
with (raw_folder / artifact_name).open("w") as f:
random_text = "".join(
random.choice(string.ascii_letters) for _ in range(10)
)
f.write(random_text)
for i in range(1, 3):
collection_folder = raw_folder / f"Collection_{i}"
collection_folder.mkdir()
for j in range(1, 5):
artifact_name = f"raw_data_{j}.txt"
with (collection_folder / artifact_name).open("w") as f:
random_text = "".join(
random.choice(string.ascii_letters) for _ in range(10)
)
f.write(random_text)
for i in range(1, 5):
artifact_name = f"result_{i}.txt"
with (preprocessed_folder / artifact_name).open("w") as f:
random_text = "".join(
random.choice(string.ascii_letters) for _ in range(10)
)
f.write(random_text)
root_folder = "complex_biological_project"
create_complex_biological_hierarchy(root_folder)
!lamin init --storage ./key-eval
π‘ connected lamindb: testuser1/key-eval
import lamindb as ln
ln.settings.verbosity = "hint"
π‘ connected lamindb: testuser1/key-eval
ln.UPath("complex_biological_project").view_tree()
4 sub-directories & 8 files with suffixes '.txt'
/home/runner/work/lamindb/lamindb/docs/faq/complex_biological_project
βββ raw/
β βββ Collection_2/
β βββ Collection_1/
β βββ raw_data_1.txt
β βββ raw_data_2.txt
β βββ raw_data_4.txt
β βββ raw_data_3.txt
βββ preprocessed/
βββ result_2.txt
βββ result_4.txt
βββ result_3.txt
βββ result_1.txt
ln.settings.transform.stem_uid = "WIwaNDvlEkwS"
ln.settings.transform.version = "1"
ln.track()
π‘ notebook imports: lamindb==0.72.1
π‘ saved: Transform(uid='WIwaNDvlEkwS5zKv', version='1', name='What does the key parameter do under the hood?', key='key', type='notebook', created_by_id=1, updated_at='2024-05-25 15:25:52 UTC')
π‘ saved: Run(uid='5DzBU11JaUgbDMYieEYg', transform_id=1, created_by_id=1)
π‘ tracked pip freeze > /home/runner/.cache/lamindb/run_env_pip_5DzBU11JaUgbDMYieEYg.txt
Run(uid='5DzBU11JaUgbDMYieEYg', started_at='2024-05-25 15:25:52 UTC', is_consecutive=True, transform_id=1, created_by_id=1)
Storing artifacts using Storage
, File
, and Collection
ΒΆ
Lamin has three storage classes that manage different types of in-memory and on-disk objects:
Storage
: Manages the default storage root that can be either local or in the cloud. For more details we refer to Storage FAQ.Artifact
: Manages datasets with an optionalkey
that acts as a relative path within the current default storage root (seeStorage
). An example is a single h5 artifact.Collection
: Manages a collection of datasets with an optionalkey
that acts as a relative path within the current default storage root (seeStorage
). An example is a collection of h5 artifacts.
For more details we refer to Tutorial: Artifacts.
The current storage root is:
ln.settings.storage
PosixUPath('/home/runner/work/lamindb/lamindb/docs/faq/key-eval')
By default, Lamin uses virtual keys
that are only reflected in the database but not in storage.
It is possible to turn this behavior off by setting ln.settings.artifact_use_virtual_keys = False
.
Generally, we discourage disabling this setting manually. For more details we refer to Storage FAQ.
ln.settings.artifact_use_virtual_keys
True
We will now create File
objects with and without semantic keys using key
and also save them as Collections
.
artifact_no_key_1 = ln.Artifact("complex_biological_project/raw/raw_data_1.txt")
artifact_no_key_2 = ln.Artifact("complex_biological_project/raw/raw_data_2.txt")
π‘ path content will be copied to default storage upon `save()` with key `None` ('.lamindb/PJ63kLJmSO4EyHsakR2r.txt')
π‘ path content will be copied to default storage upon `save()` with key `None` ('.lamindb/IZPsJMM8aF7yS0Xb4b7C.txt')
The logging suggests that the artifacts will be saved to our current default storage with auto generated storage keys.
artifact_no_key_1.save()
artifact_no_key_2.save()
β
storing artifact 'PJ63kLJmSO4EyHsakR2r' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/PJ63kLJmSO4EyHsakR2r.txt'
β
storing artifact 'IZPsJMM8aF7yS0Xb4b7C' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/IZPsJMM8aF7yS0Xb4b7C.txt'
Artifact(uid='IZPsJMM8aF7yS0Xb4b7C', suffix='.txt', size=10, hash='0IFKbTb2OTktxXOF5XHaXw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:53 UTC')
artifact_key_3 = ln.Artifact(
"complex_biological_project/raw/raw_data_3.txt", key="raw/raw_data_3.txt"
)
artifact_key_4 = ln.Artifact(
"complex_biological_project/raw/raw_data_4.txt", key="raw/raw_data_4.txt"
)
artifact_key_3.save()
artifact_key_4.save()
π‘ path content will be copied to default storage upon `save()` with key 'raw/raw_data_3.txt'
π‘ path content will be copied to default storage upon `save()` with key 'raw/raw_data_4.txt'
β
storing artifact 'xnIKYQnfoP4Tyr85Cfeq' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/xnIKYQnfoP4Tyr85Cfeq.txt'
β
storing artifact 'D4Twh2WXgzhNO2NH9vLS' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/D4Twh2WXgzhNO2NH9vLS.txt'
Artifact(uid='D4Twh2WXgzhNO2NH9vLS', key='raw/raw_data_4.txt', suffix='.txt', size=10, hash='WKdt5lkOhZpa6gk_2kTyIQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:53 UTC')
Files
with keys are not stored in different locations because of the usage of virtual keys
.
However, they are still semantically queryable by key
.
ln.Artifact.filter(key__contains="raw").df().head()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | ||||||||||||||||||
3 | xnIKYQnfoP4Tyr85Cfeq | None | None | raw/raw_data_3.txt | .txt | None | 10 | FuxxJEmnTh5k4UHbNjq39w | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:53.596193+00:00 |
4 | D4Twh2WXgzhNO2NH9vLS | None | None | raw/raw_data_4.txt | .txt | None | 10 | WKdt5lkOhZpa6gk_2kTyIQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:53.600575+00:00 |
Collection
does not have a key
parameter because it does not store any additional data in Storage
.
In contrast, it has a name
parameter that serves as a semantic identifier of the collection.
ds_1 = ln.Collection([artifact_no_key_1, artifact_no_key_2], name="no key collection")
ds_2 = ln.Collection([artifact_key_3, artifact_key_4], name="sample collection")
ds_1
Collection(uid='UanpjQVQgqL6oPe3vEiz', name='no key collection', hash='uFaiVJ00VVC8vfTewwVw', visibility=1, created_by_id=1, transform_id=1, run_id=1)
Advantages and disadvantages of semantic keysΒΆ
Semantic keys have several advantages and disadvantages that we will discuss and demonstrate in the remaining notebook:
Advantages:ΒΆ
Simple: It can be easier to refer to specific collections in conversations
Familiarity: Most people are familiar with the concept of semantic names
DisadvantagesΒΆ
Length: Semantic names can be long with limited aesthetic appeal
Inconsistency: Lack of naming conventions can lead to confusion
Limited metadata: Semantic keys can contain some, but usually not all metadata
Inefficiency: Writing lengthy semantic names is a repetitive process and can be time-consuming
Ambiguity: Overly descriptive artifact names may introduce ambiguity and redundancy
Clashes: Several people may attempt to use the same semantic key. They are not unique
Renaming artifactsΒΆ
Renaming Files
that have associated keys can be done on several levels.
In storageΒΆ
A artifact can be locally moved or renamed:
artifact_key_3.path
PosixUPath('/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/xnIKYQnfoP4Tyr85Cfeq.txt')
loaded_artifact = artifact_key_3.load()
!mkdir complex_biological_project/moved_artifacts
!mv complex_biological_project/raw/raw_data_3.txt complex_biological_project/moved_artifacts
artifact_key_3.path
PosixUPath('/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/xnIKYQnfoP4Tyr85Cfeq.txt')
After moving the artifact locally, the storage location (the path) has not changed and the artifact can still be loaded.
artifact_3 = artifact_key_3.load()
The same applies to the key
which has not changed.
artifact_key_3.key
'raw/raw_data_3.txt'
By keyΒΆ
Besides moving the artifact in storage, the key
can also be renamed.
artifact_key_4.key
'raw/raw_data_4.txt'
artifact_key_4.key = "bad_samples/sample_data_4.txt"
artifact_key_4.key
'bad_samples/sample_data_4.txt'
Due to the usage of virtual keys
, modifying the key does not change the storage location and the artifact stays accessible.
artifact_key_4.path
PosixUPath('/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/D4Twh2WXgzhNO2NH9vLS.txt')
artifact_4 = artifact_key_4.load()
Modifying the path
attributeΒΆ
However, modifying the path
directly is not allowed:
try:
artifact_key_4.path = f"{ln.settings.storage}/here_now/sample_data_4.txt"
except AttributeError as e:
print(e)
property of 'Artifact' object has no setter
Clashing semantic keysΒΆ
Semantic keys should not clash. Letβs attempt to use the same semantic key twice
print(artifact_key_3.key)
print(artifact_key_4.key)
raw/raw_data_3.txt
bad_samples/sample_data_4.txt
artifact_key_4.key = "raw/raw_data_3.txt"
print(artifact_key_3.key)
print(artifact_key_4.key)
raw/raw_data_3.txt
raw/raw_data_3.txt
When filtering for this semantic key it is now unclear to which artifact we were referring to:
ln.Artifact.filter(key__icontains="sample_data_3").df()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id |
When querying by key
LaminDB cannot resolve which artifact we actually wanted.
In fact, we only get a single hit which does not paint a complete picture.
print(artifact_key_3.uid)
print(artifact_key_4.uid)
xnIKYQnfoP4Tyr85Cfeq
D4Twh2WXgzhNO2NH9vLS
Both artifacts still exist though with unique uids
that can be used to get access to them.
Most importantly though, saving these artifacts to the database will result in an IntegrityError
to prevent this issue.
try:
artifact_key_3.save()
artifact_key_4.save()
except Exception as e:
print(
"It is not possible to save artifacts to the same key. This results in an"
" Integrity Error!"
)
We refer to What happens if I save the same artifacts & records twice? for more detailed explanations of behavior when attempting to save artifacts multiple times.
HierarchiesΒΆ
Another common use-case of keys
are artifact hierarchies.
It can be useful to resemble the artifact structure in βcomplex_biological_projectβ from above also in LaminDB to allow for queries for artifacts that were stored in specific folders.
Common examples of this are folders specifying different processing stages such as raw
, preprocessed
, or annotated
.
Note that this use-case may also be overlapping with Collection
which also allows for grouping Files
.
However, Collection
cannot model hierarchical groupings.
KeyΒΆ
import os
for root, _, artifacts in os.walk("complex_biological_project/raw"):
for artifactname in artifacts:
file_path = os.path.join(root, artifactname)
key_path = file_path.removeprefix("complex_biological_project")
ln_artifact = ln.Artifact(file_path, key=key_path)
ln_artifact.save()
π‘ returning existing artifact with same hash: Artifact(uid='PJ63kLJmSO4EyHsakR2r', suffix='.txt', size=10, hash='eF1IddZV6gXvRUytNrVXEw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:53 UTC')
β key None on existing artifact differs from passed key /raw/raw_data_1.txt
π‘ returning existing artifact with same hash: Artifact(uid='IZPsJMM8aF7yS0Xb4b7C', suffix='.txt', size=10, hash='0IFKbTb2OTktxXOF5XHaXw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:53 UTC')
β key None on existing artifact differs from passed key /raw/raw_data_2.txt
π‘ returning existing artifact with same hash: Artifact(uid='D4Twh2WXgzhNO2NH9vLS', key='raw/raw_data_3.txt', suffix='.txt', size=10, hash='WKdt5lkOhZpa6gk_2kTyIQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
β key raw/raw_data_3.txt on existing artifact differs from passed key /raw/raw_data_4.txt
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_2/raw_data_1.txt'
β
storing artifact 'ela9w38NssilOWfN0GhK' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/ela9w38NssilOWfN0GhK.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_2/raw_data_2.txt'
β
storing artifact 'QYXFg92FoQMV0N34CRQF' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/QYXFg92FoQMV0N34CRQF.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_2/raw_data_4.txt'
β
storing artifact 'fzHNbDYUpA0orlcc2Oaq' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/fzHNbDYUpA0orlcc2Oaq.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_2/raw_data_3.txt'
β
storing artifact 'hwhSpEFLSpZZxroOuVTe' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/hwhSpEFLSpZZxroOuVTe.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_1/raw_data_1.txt'
β
storing artifact 'qS3w4CQjGy1kY9e8S3S1' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/qS3w4CQjGy1kY9e8S3S1.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_1/raw_data_2.txt'
β
storing artifact 'czBkM250k2aDS2z3oI1Y' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/czBkM250k2aDS2z3oI1Y.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_1/raw_data_4.txt'
β
storing artifact 'VkmnndYQiCHKPawzavMy' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/VkmnndYQiCHKPawzavMy.txt'
π‘ path content will be copied to default storage upon `save()` with key '/raw/Collection_1/raw_data_3.txt'
β
storing artifact 'SEg1eHoDLuYxgy3j3Vxe' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/SEg1eHoDLuYxgy3j3Vxe.txt'
ln.Artifact.filter(key__startswith="raw").df()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | ||||||||||||||||||
3 | xnIKYQnfoP4Tyr85Cfeq | None | None | raw/raw_data_3.txt | .txt | None | 10 | FuxxJEmnTh5k4UHbNjq39w | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.020148+00:00 |
4 | D4Twh2WXgzhNO2NH9vLS | None | None | raw/raw_data_3.txt | .txt | None | 10 | WKdt5lkOhZpa6gk_2kTyIQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.060091+00:00 |
CollectionΒΆ
Alternatively, it would have been possible to create a Collection
with a corresponding name:
all_data_paths = []
for root, _, artifacts in os.walk("complex_biological_project/raw"):
for artifactname in artifacts:
file_path = os.path.join(root, artifactname)
all_data_paths.append(file_path)
all_data_artifacts = []
for path in all_data_paths:
all_data_artifacts.append(ln.Artifact(path))
data_ds = ln.Collection(all_data_artifacts, name="data")
data_ds.save()
π‘ returning existing artifact with same hash: Artifact(uid='PJ63kLJmSO4EyHsakR2r', suffix='.txt', size=10, hash='eF1IddZV6gXvRUytNrVXEw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='IZPsJMM8aF7yS0Xb4b7C', suffix='.txt', size=10, hash='0IFKbTb2OTktxXOF5XHaXw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='D4Twh2WXgzhNO2NH9vLS', key='raw/raw_data_3.txt', suffix='.txt', size=10, hash='WKdt5lkOhZpa6gk_2kTyIQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='ela9w38NssilOWfN0GhK', key='/raw/Collection_2/raw_data_1.txt', suffix='.txt', size=10, hash='ow36Nmfu3XXsRL1zyoNHKQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='QYXFg92FoQMV0N34CRQF', key='/raw/Collection_2/raw_data_2.txt', suffix='.txt', size=10, hash='Jpj9Er6ekqKyepH8woIAsA', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='fzHNbDYUpA0orlcc2Oaq', key='/raw/Collection_2/raw_data_4.txt', suffix='.txt', size=10, hash='YtpwKEj4r1zi1dR07U46zg', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='hwhSpEFLSpZZxroOuVTe', key='/raw/Collection_2/raw_data_3.txt', suffix='.txt', size=10, hash='HkuAxo8iDBWPRoL5ngHSuA', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='qS3w4CQjGy1kY9e8S3S1', key='/raw/Collection_1/raw_data_1.txt', suffix='.txt', size=10, hash='OLYfVuDRc1Mf2N4kj8pRfQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='czBkM250k2aDS2z3oI1Y', key='/raw/Collection_1/raw_data_2.txt', suffix='.txt', size=10, hash='4bY_Bpednz26cONuRaM7Mw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='VkmnndYQiCHKPawzavMy', key='/raw/Collection_1/raw_data_4.txt', suffix='.txt', size=10, hash='amEWtDAb5yJm3J2SVNsh5Q', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing artifact with same hash: Artifact(uid='SEg1eHoDLuYxgy3j3Vxe', key='/raw/Collection_1/raw_data_3.txt', suffix='.txt', size=10, hash='cTaT8FFw6ubML_VoYcnOJQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
ln.Collection.filter(name__icontains="data").df()
uid | version | name | description | hash | reference | reference_type | visibility | transform_id | artifact_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | Q7LgGum1KHEGv2rK0Nkz | None | data | None | hSK_ho2u89cDl2hJseDB | None | None | 1 | 1 | None | 1 | 1 | 2024-05-25 15:25:54.218153+00:00 |
This approach will likely lead to clashes. Alternatively, Ulabels
can be added to Files
to resemble hierarchies.
UlabelsΒΆ
for root, _, artifacts in os.walk("complex_biological_project/raw"):
for artifactname in artifacts:
file_path = os.path.join(root, artifactname)
key_path = file_path.removeprefix("complex_biological_project")
ln_artifact = ln.Artifact(file_path, key=key_path)
ln_artifact.save()
data_label = ln.ULabel(name="data")
data_label.save()
ln_artifact.ulabels.add(data_label)
π‘ returning existing artifact with same hash: Artifact(uid='PJ63kLJmSO4EyHsakR2r', suffix='.txt', size=10, hash='eF1IddZV6gXvRUytNrVXEw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
β key None on existing artifact differs from passed key /raw/raw_data_1.txt
π‘ returning existing artifact with same hash: Artifact(uid='IZPsJMM8aF7yS0Xb4b7C', suffix='.txt', size=10, hash='0IFKbTb2OTktxXOF5XHaXw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
β key None on existing artifact differs from passed key /raw/raw_data_2.txt
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='D4Twh2WXgzhNO2NH9vLS', key='raw/raw_data_3.txt', suffix='.txt', size=10, hash='WKdt5lkOhZpa6gk_2kTyIQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
β key raw/raw_data_3.txt on existing artifact differs from passed key /raw/raw_data_4.txt
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='ela9w38NssilOWfN0GhK', key='/raw/Collection_2/raw_data_1.txt', suffix='.txt', size=10, hash='ow36Nmfu3XXsRL1zyoNHKQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='QYXFg92FoQMV0N34CRQF', key='/raw/Collection_2/raw_data_2.txt', suffix='.txt', size=10, hash='Jpj9Er6ekqKyepH8woIAsA', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='fzHNbDYUpA0orlcc2Oaq', key='/raw/Collection_2/raw_data_4.txt', suffix='.txt', size=10, hash='YtpwKEj4r1zi1dR07U46zg', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='hwhSpEFLSpZZxroOuVTe', key='/raw/Collection_2/raw_data_3.txt', suffix='.txt', size=10, hash='HkuAxo8iDBWPRoL5ngHSuA', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='qS3w4CQjGy1kY9e8S3S1', key='/raw/Collection_1/raw_data_1.txt', suffix='.txt', size=10, hash='OLYfVuDRc1Mf2N4kj8pRfQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='czBkM250k2aDS2z3oI1Y', key='/raw/Collection_1/raw_data_2.txt', suffix='.txt', size=10, hash='4bY_Bpednz26cONuRaM7Mw', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='VkmnndYQiCHKPawzavMy', key='/raw/Collection_1/raw_data_4.txt', suffix='.txt', size=10, hash='amEWtDAb5yJm3J2SVNsh5Q', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
π‘ returning existing artifact with same hash: Artifact(uid='SEg1eHoDLuYxgy3j3Vxe', key='/raw/Collection_1/raw_data_3.txt', suffix='.txt', size=10, hash='cTaT8FFw6ubML_VoYcnOJQ', hash_type='md5', visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
π‘ returning existing ULabel record with same name: 'data'
labels = ln.ULabel.lookup()
ln.Artifact.filter(ulabels__in=[labels.data]).df()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | ||||||||||||||||||
1 | PJ63kLJmSO4EyHsakR2r | None | None | None | .txt | None | 10 | eF1IddZV6gXvRUytNrVXEw | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.258244+00:00 |
2 | IZPsJMM8aF7yS0Xb4b7C | None | None | None | .txt | None | 10 | 0IFKbTb2OTktxXOF5XHaXw | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.275072+00:00 |
4 | D4Twh2WXgzhNO2NH9vLS | None | None | raw/raw_data_3.txt | .txt | None | 10 | WKdt5lkOhZpa6gk_2kTyIQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.295283+00:00 |
5 | ela9w38NssilOWfN0GhK | None | None | /raw/Collection_2/raw_data_1.txt | .txt | None | 10 | ow36Nmfu3XXsRL1zyoNHKQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.314244+00:00 |
6 | QYXFg92FoQMV0N34CRQF | None | None | /raw/Collection_2/raw_data_2.txt | .txt | None | 10 | Jpj9Er6ekqKyepH8woIAsA | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.333502+00:00 |
7 | fzHNbDYUpA0orlcc2Oaq | None | None | /raw/Collection_2/raw_data_4.txt | .txt | None | 10 | YtpwKEj4r1zi1dR07U46zg | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.355245+00:00 |
8 | hwhSpEFLSpZZxroOuVTe | None | None | /raw/Collection_2/raw_data_3.txt | .txt | None | 10 | HkuAxo8iDBWPRoL5ngHSuA | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.375040+00:00 |
9 | qS3w4CQjGy1kY9e8S3S1 | None | None | /raw/Collection_1/raw_data_1.txt | .txt | None | 10 | OLYfVuDRc1Mf2N4kj8pRfQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.393818+00:00 |
10 | czBkM250k2aDS2z3oI1Y | None | None | /raw/Collection_1/raw_data_2.txt | .txt | None | 10 | 4bY_Bpednz26cONuRaM7Mw | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.414859+00:00 |
11 | VkmnndYQiCHKPawzavMy | None | None | /raw/Collection_1/raw_data_4.txt | .txt | None | 10 | amEWtDAb5yJm3J2SVNsh5Q | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.434056+00:00 |
12 | SEg1eHoDLuYxgy3j3Vxe | None | None | /raw/Collection_1/raw_data_3.txt | .txt | None | 10 | cTaT8FFw6ubML_VoYcnOJQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.455888+00:00 |
However, Ulabels
are too versatile for such an approach and clashes are also to be expected here.
MetadataΒΆ
Due to the chance of clashes for the aforementioned approaches being rather high, we generally recommend not to store hierarchical data with solely semantic keys.
Biological metadata makes Files
and Collections
unambiguous and easily queryable.
Legacy data and multiple storage rootsΒΆ
Distributed CollectionsΒΆ
LaminDB can ingest legacy data that already had a structure in their storage.
In such cases, it disables artifact_use_virtual_keys
and the artifacts are ingested with their actual storage location.
It might be therefore be possible that Files
stored in different storage roots may be associated with a single Collection
.
To simulate this, we are disabling artifact_use_virtual_keys
and ingest artifacts stored in a different path (the βlegacy dataβ).
ln.settings.artifact_use_virtual_keys = False
for root, _, artifacts in os.walk("complex_biological_project/preprocessed"):
for artifactname in artifacts:
file_path = os.path.join(root, artifactname)
key_path = file_path.removeprefix("complex_biological_project")
print(file_path)
print()
ln_artifact = ln.Artifact(file_path, key=f"./{key_path}")
ln_artifact.save()
complex_biological_project/preprocessed/result_2.txt
π‘ path content will be copied to default storage upon `save()` with key './/preprocessed/result_2.txt'
β
storing artifact 'I7NCpdhdQxooATjuB0ay' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/preprocessed/result_2.txt'
complex_biological_project/preprocessed/result_4.txt
π‘ path content will be copied to default storage upon `save()` with key './/preprocessed/result_4.txt'
β
storing artifact 'zyeCrtV0blEqFyroItCz' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/preprocessed/result_4.txt'
complex_biological_project/preprocessed/result_3.txt
π‘ path content will be copied to default storage upon `save()` with key './/preprocessed/result_3.txt'
β
storing artifact 'rhqytrB4O1nzcqzT4lPi' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/preprocessed/result_3.txt'
complex_biological_project/preprocessed/result_1.txt
π‘ path content will be copied to default storage upon `save()` with key './/preprocessed/result_1.txt'
β
storing artifact 'sEV25BkwGGEUbL6hSiHZ' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/preprocessed/result_1.txt'
ln.Artifact.df()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | ||||||||||||||||||
16 | sEV25BkwGGEUbL6hSiHZ | None | None | .//preprocessed/result_1.txt | .txt | None | 10 | AS62b3yyvt12rpLoZgQR3Q | md5 | None | None | 1 | False | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.539581+00:00 |
15 | rhqytrB4O1nzcqzT4lPi | None | None | .//preprocessed/result_3.txt | .txt | None | 10 | 6YPwPFeVlJDHdjQIdP5FwA | md5 | None | None | 1 | False | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.532739+00:00 |
14 | zyeCrtV0blEqFyroItCz | None | None | .//preprocessed/result_4.txt | .txt | None | 10 | rSsbNSHTYwZvaUD3IF3x4w | md5 | None | None | 1 | False | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.525731+00:00 |
13 | I7NCpdhdQxooATjuB0ay | None | None | .//preprocessed/result_2.txt | .txt | None | 10 | boKxFmmJe9IZR9BPu2ndVQ | md5 | None | None | 1 | False | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.518700+00:00 |
12 | SEg1eHoDLuYxgy3j3Vxe | None | None | /raw/Collection_1/raw_data_3.txt | .txt | None | 10 | cTaT8FFw6ubML_VoYcnOJQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.455888+00:00 |
11 | VkmnndYQiCHKPawzavMy | None | None | /raw/Collection_1/raw_data_4.txt | .txt | None | 10 | amEWtDAb5yJm3J2SVNsh5Q | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.434056+00:00 |
10 | czBkM250k2aDS2z3oI1Y | None | None | /raw/Collection_1/raw_data_2.txt | .txt | None | 10 | 4bY_Bpednz26cONuRaM7Mw | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.414859+00:00 |
9 | qS3w4CQjGy1kY9e8S3S1 | None | None | /raw/Collection_1/raw_data_1.txt | .txt | None | 10 | OLYfVuDRc1Mf2N4kj8pRfQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.393818+00:00 |
8 | hwhSpEFLSpZZxroOuVTe | None | None | /raw/Collection_2/raw_data_3.txt | .txt | None | 10 | HkuAxo8iDBWPRoL5ngHSuA | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.375040+00:00 |
7 | fzHNbDYUpA0orlcc2Oaq | None | None | /raw/Collection_2/raw_data_4.txt | .txt | None | 10 | YtpwKEj4r1zi1dR07U46zg | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.355245+00:00 |
6 | QYXFg92FoQMV0N34CRQF | None | None | /raw/Collection_2/raw_data_2.txt | .txt | None | 10 | Jpj9Er6ekqKyepH8woIAsA | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.333502+00:00 |
5 | ela9w38NssilOWfN0GhK | None | None | /raw/Collection_2/raw_data_1.txt | .txt | None | 10 | ow36Nmfu3XXsRL1zyoNHKQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.314244+00:00 |
4 | D4Twh2WXgzhNO2NH9vLS | None | None | raw/raw_data_3.txt | .txt | None | 10 | WKdt5lkOhZpa6gk_2kTyIQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.295283+00:00 |
2 | IZPsJMM8aF7yS0Xb4b7C | None | None | None | .txt | None | 10 | 0IFKbTb2OTktxXOF5XHaXw | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.275072+00:00 |
1 | PJ63kLJmSO4EyHsakR2r | None | None | None | .txt | None | 10 | eF1IddZV6gXvRUytNrVXEw | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.258244+00:00 |
3 | xnIKYQnfoP4Tyr85Cfeq | None | None | raw/raw_data_3.txt | .txt | None | 10 | FuxxJEmnTh5k4UHbNjq39w | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.020148+00:00 |
artifact_from_raw = ln.Artifact.filter(key__icontains="Collection_2/raw_data_1").first()
artifact_from_preprocessed = ln.Artifact.filter(
key__icontains="preprocessed/result_1"
).first()
print(artifact_from_raw.path)
print(artifact_from_preprocessed.path)
/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/ela9w38NssilOWfN0GhK.txt
/home/runner/work/lamindb/lamindb/docs/faq/key-eval/preprocessed/result_1.txt
Letβs create our Collection
:
ds = ln.Collection(
[artifact_from_raw, artifact_from_preprocessed], name="raw_and_processed_collection_2"
)
ds.save()
ds.artifacts.df()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | ||||||||||||||||||
5 | ela9w38NssilOWfN0GhK | None | None | /raw/Collection_2/raw_data_1.txt | .txt | None | 10 | ow36Nmfu3XXsRL1zyoNHKQ | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.314244+00:00 |
16 | sEV25BkwGGEUbL6hSiHZ | None | None | .//preprocessed/result_1.txt | .txt | None | 10 | AS62b3yyvt12rpLoZgQR3Q | md5 | None | None | 1 | False | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.539581+00:00 |
Modeling directoriesΒΆ
ln.settings.artifact_use_virtual_keys = True
dir_path = ln.core.datasets.dir_scrnaseq_cellranger("sample_001")
ln.UPath(dir_path).view_tree()
π‘ file has more than one suffix (path.suffixes), using only last suffix: '.bai' - if you want your composite suffix to be recognized add it to lamindb.core.storage.VALID_SUFFIXES.add()
3 sub-directories & 15 files with suffixes '.bai', '.h5', '.bam', '.csv', '.tsv.gz', '.html', '.cloupe', '.mtx.gz'
/home/runner/work/lamindb/lamindb/docs/faq/sample_001
βββ possorted_genome_bam.bam
βββ raw_feature_bc_matrix.h5
βββ molecule_info.h5
βββ filtered_feature_bc_matrix.h5
βββ raw_feature_bc_matrix/
β βββ matrix.mtx.gz
β βββ barcodes.tsv.gz
β βββ features.tsv.gz
βββ metrics_summary.csv
βββ cloupe.cloupe
βββ web_summary.html
βββ analysis/
β βββ analysis.csv
βββ possorted_genome_bam.bam.bai
βββ filtered_feature_bc_matrix/
βββ matrix.mtx.gz
βββ barcodes.tsv.gz
βββ features.tsv.gz
There are two ways to create Artifact
objects from directories: from_dir()
and Artifact
.
cellranger_raw_artifact = ln.Artifact.from_dir("sample_001/raw_feature_bc_matrix/")
β this creates one artifact per file in the directory - you might simply call ln.Artifact(dir) to get one artifact for the entire directory
β folder is outside existing storage location, will copy files from sample_001/raw_feature_bc_matrix/ to /home/runner/work/lamindb/lamindb/docs/faq/key-eval/raw_feature_bc_matrix
β
created 3 artifacts from directory using storage /home/runner/work/lamindb/lamindb/docs/faq/key-eval and key = raw_feature_bc_matrix/
for artifact in cellranger_raw_artifact:
artifact.save()
β
storing artifact 'qA4v2SHrjCYeZBWs4U7g' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/qA4v2SHrjCYeZBWs4U7g.mtx.gz'
β
storing artifact 'Wx2ug7xZZtNom7HmNA44' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/Wx2ug7xZZtNom7HmNA44.tsv.gz'
β
storing artifact 'qqDfA2TbeORpkLdeCtVu' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/qqDfA2TbeORpkLdeCtVu.tsv.gz'
cellranger_raw_folder = ln.Artifact(
"sample_001/raw_feature_bc_matrix/", description="cellranger raw"
)
cellranger_raw_folder.save()
π‘ path content will be copied to default storage upon `save()` with key `None` ('.lamindb/8r8EVPgfdfvkK4jU')
β
storing artifact '8r8EVPgfdfvkK4jUNOCh' at '/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/8r8EVPgfdfvkK4jU'
Artifact(uid='8r8EVPgfdfvkK4jUNOCh', description='cellranger raw', suffix='', size=18, hash='7Hw1zf4rhce2XmxrnF3GVA', hash_type='md5-d', n_objects=3, visibility=1, key_is_virtual=True, created_by_id=1, storage_id=1, transform_id=1, run_id=1, updated_at='2024-05-25 15:25:54 UTC')
ln.Artifact.filter(key__icontains="raw_feature_bc_matrix").df()
uid | version | description | key | suffix | accessor | size | hash | hash_type | n_objects | n_observations | visibility | key_is_virtual | storage_id | transform_id | run_id | created_by_id | updated_at | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | ||||||||||||||||||
17 | qA4v2SHrjCYeZBWs4U7g | None | None | raw_feature_bc_matrix/matrix.mtx.gz | .mtx.gz | None | 6 | 71GG9oXLuaEMJQilPwgf5g | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.659445+00:00 |
18 | Wx2ug7xZZtNom7HmNA44 | None | None | raw_feature_bc_matrix/barcodes.tsv.gz | .tsv.gz | None | 6 | 9nBLSsGU2aD6dpcuOqxtYg | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.663529+00:00 |
19 | qqDfA2TbeORpkLdeCtVu | None | None | raw_feature_bc_matrix/features.tsv.gz | .tsv.gz | None | 6 | LMBpeQFslPPrjcXw4q0YuA | md5 | None | None | 1 | True | 1 | 1 | 1 | 1 | 2024-05-25 15:25:54.667495+00:00 |
ln.Artifact.filter(key__icontains="raw_feature_bc_matrix/matrix.mtx.gz").one().path
PosixUPath('/home/runner/work/lamindb/lamindb/docs/faq/key-eval/.lamindb/qA4v2SHrjCYeZBWs4U7g.mtx.gz')
artifact = ln.Artifact.filter(description="cellranger raw").one()
artifact.path.glob("*")
<generator object Path.glob at 0x7f8e376fe020>