lamindb_setup 0.71.4__py2.py3-none-any.whl → 0.72.0__py2.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_setup/__init__.py CHANGED
@@ -34,7 +34,7 @@ Modules & settings:
34
34
 
35
35
  """
36
36
 
37
- __version__ = "0.71.4" # denote a release candidate for 0.1.0 with 0.1rc1
37
+ __version__ = "0.72.0" # denote a release candidate for 0.1.0 with 0.1rc1
38
38
 
39
39
  import sys
40
40
  from os import name as _os_name
lamindb_setup/_delete.py CHANGED
@@ -7,11 +7,12 @@ from uuid import UUID
7
7
  from lamin_utils import logger
8
8
 
9
9
  from ._connect_instance import _connect_instance, get_owner_name_from_identifier
10
+ from .core._aws_credentials import HOSTED_BUCKETS
10
11
  from .core._hub_core import delete_instance as delete_instance_on_hub
11
12
  from .core._hub_core import get_storage_records_for_instance
12
13
  from .core._settings import settings
13
14
  from .core._settings_storage import StorageSettings
14
- from .core.upath import HOSTED_BUCKETS, check_storage_is_empty
15
+ from .core.upath import check_storage_is_empty
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from pathlib import Path
@@ -109,11 +110,11 @@ def delete(slug: str, force: bool = False, require_empty: bool = True) -> int |
109
110
  for storage_record in storage_records:
110
111
  if storage_record["root"] == isettings.storage.root_as_str:
111
112
  continue
113
+ ssettings = StorageSettings(storage_record["root"]) # type: ignore
112
114
  check_storage_is_empty(
113
- storage_record["root"], # type: ignore
115
+ ssettings.root, # type: ignore
114
116
  raise_error=require_empty,
115
117
  )
116
- ssettings = StorageSettings(storage_record["root"]) # type: ignore
117
118
  if ssettings._mark_storage_root.exists():
118
119
  ssettings._mark_storage_root.unlink(
119
120
  missing_ok=True # this is totally weird, but needed on Py3.11
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import time
5
+
6
+ from upath.implementations.cloud import S3Path
7
+
8
+ HOSTED_REGIONS = [
9
+ "eu-central-1",
10
+ "eu-west-2",
11
+ "us-east-1",
12
+ "us-east-2",
13
+ "us-west-1",
14
+ "us-west-2",
15
+ ]
16
+ lamin_env = os.getenv("LAMIN_ENV")
17
+ if lamin_env is None or lamin_env == "prod":
18
+ hosted_buckets_list = [f"s3://lamin-{region}" for region in HOSTED_REGIONS]
19
+ hosted_buckets_list.append("s3://scverse-spatial-eu-central-1")
20
+ HOSTED_BUCKETS = tuple(hosted_buckets_list)
21
+ else:
22
+ HOSTED_BUCKETS = ("s3://lamin-hosted-test",) # type: ignore
23
+
24
+
25
+ AWS_CREDENTIALS_EXPIRATION = 11 * 60 * 60 # refresh credentials after 11 hours
26
+
27
+
28
+ class AWSCredentialsManager:
29
+ def __init__(self):
30
+ self._credentials_cache = {}
31
+
32
+ from s3fs import S3FileSystem
33
+
34
+ # this is cached so will be resued with the connection initialized
35
+ fs = S3FileSystem(cache_regions=True)
36
+ fs.connect()
37
+ self.anon = fs.session._credentials is None
38
+
39
+ def _find_root(self, path_str: str) -> str | None:
40
+ roots = self._credentials_cache.keys()
41
+ if path_str in roots:
42
+ return path_str
43
+ roots = sorted(roots, key=len, reverse=True)
44
+ for root in roots:
45
+ if path_str.startswith(root):
46
+ return root
47
+ return None
48
+
49
+ def _is_active(self, root: str) -> bool:
50
+ return (
51
+ time.time() - self._credentials_cache[root]["time"]
52
+ ) < AWS_CREDENTIALS_EXPIRATION
53
+
54
+ def _set_cached_credentials(self, root: str, credentials: dict):
55
+ if root not in self._credentials_cache:
56
+ self._credentials_cache[root] = {}
57
+ self._credentials_cache[root]["credentials"] = credentials
58
+ self._credentials_cache[root]["time"] = time.time()
59
+
60
+ def _get_cached_credentials(self, root: str) -> dict:
61
+ return self._credentials_cache[root]["credentials"]
62
+
63
+ def _path_inject_options(self, path: S3Path, credentials: dict) -> S3Path:
64
+ if credentials == {}:
65
+ # credentials were specified manually for the path
66
+ if "anon" in path._kwargs:
67
+ anon = path._kwargs["anon"]
68
+ elif path.fs.key is not None and path.fs.secret is not None:
69
+ anon = False
70
+ else:
71
+ anon = self.anon
72
+ connection_options = {"anon": anon}
73
+ else:
74
+ connection_options = credentials
75
+
76
+ if "cache_regions" in path._kwargs:
77
+ cache_regions = path._kwargs["cache_regions"]
78
+ else:
79
+ cache_regions = True
80
+
81
+ return S3Path(path, cache_regions=cache_regions, **connection_options)
82
+
83
+ def enrich_path(self, path: S3Path, access_token: str | None = None) -> S3Path:
84
+ path_str = path.as_posix().rstrip("/")
85
+ root = self._find_root(path_str)
86
+
87
+ if root is not None:
88
+ set_cache = False
89
+ credentials = self._get_cached_credentials(root)
90
+
91
+ if access_token is not None:
92
+ set_cache = True
93
+ elif credentials != {}:
94
+ # update credentials
95
+ if not self._is_active(root):
96
+ set_cache = True
97
+ else:
98
+ set_cache = True
99
+
100
+ if set_cache:
101
+ from ._hub_core import access_aws
102
+
103
+ storage_root_info = access_aws(path_str, access_token=access_token)
104
+ accessibility = storage_root_info["accessibility"]
105
+
106
+ is_managed = accessibility.get("is_managed", False)
107
+ if is_managed:
108
+ credentials = storage_root_info["credentials"]
109
+ else:
110
+ credentials = {}
111
+
112
+ if access_token is None:
113
+ if "storage_root" in accessibility:
114
+ root = accessibility["storage_root"]
115
+ # just to be safe
116
+ root = None if root == "" else root
117
+ if root is None:
118
+ # heuristic
119
+ # do not write the first level for the known hosted buckets
120
+ if path_str.startswith(HOSTED_BUCKETS):
121
+ root = "/".join(path.path.rstrip("/").split("/")[:2])
122
+ else:
123
+ # write the bucket for everything else
124
+ root = path._url.netloc
125
+ root = "s3://" + root
126
+ self._set_cached_credentials(root, credentials)
127
+
128
+ return self._path_inject_options(path, credentials)
129
+
130
+
131
+ _aws_credentials_manager: AWSCredentialsManager | None = None
132
+
133
+
134
+ def get_aws_credentials_manager() -> AWSCredentialsManager:
135
+ global _aws_credentials_manager
136
+
137
+ if _aws_credentials_manager is None:
138
+ _aws_credentials_manager = AWSCredentialsManager()
139
+
140
+ return _aws_credentials_manager
@@ -353,35 +353,43 @@ def _connect_instance(
353
353
  return instance, storage
354
354
 
355
355
 
356
- def access_aws(storage_root: str, access_token: str | None = None) -> dict[str, str]:
356
+ def access_aws(storage_root: str, access_token: str | None = None) -> dict[str, dict]:
357
357
  from ._settings import settings
358
358
 
359
359
  if settings.user.handle != "anonymous" or access_token is not None:
360
- credentials = call_with_fallback_auth(
360
+ storage_root_info = call_with_fallback_auth(
361
361
  _access_aws, storage_root=storage_root, access_token=access_token
362
362
  )
363
- return credentials
363
+ return storage_root_info
364
364
  else:
365
365
  raise RuntimeError("Can only get access to AWS if authenticated.")
366
366
 
367
367
 
368
- def _access_aws(*, storage_root: str, client: Client) -> dict[str, str]:
368
+ def _access_aws(*, storage_root: str, client: Client) -> dict[str, dict]:
369
369
  import lamindb_setup
370
370
 
371
+ storage_root_info: dict[str, dict] = {"credentials": {}, "accessibility": {}}
371
372
  response = client.functions.invoke(
372
373
  "access-aws",
373
374
  invoke_options={"body": {"storage_root": storage_root}},
374
375
  )
375
376
  if response is not None and response != b"{}":
376
- loaded_credentials = json.loads(response)["Credentials"]
377
- credentials = {}
377
+ data = json.loads(response)
378
+
379
+ loaded_credentials = data["Credentials"]
380
+ loaded_accessibility = data["StorageAccessibility"]
381
+
382
+ credentials = storage_root_info["credentials"]
378
383
  credentials["key"] = loaded_credentials["AccessKeyId"]
379
384
  credentials["secret"] = loaded_credentials["SecretAccessKey"]
380
385
  credentials["token"] = loaded_credentials["SessionToken"]
381
- return credentials
386
+
387
+ accessibility = storage_root_info["accessibility"]
388
+ accessibility["storage_root"] = loaded_accessibility["storageRoot"]
389
+ accessibility["is_managed"] = loaded_accessibility["isManaged"]
382
390
  elif lamindb_setup._TESTING:
383
391
  raise RuntimeError(f"access-aws errored: {response}")
384
- return {}
392
+ return storage_root_info
385
393
 
386
394
 
387
395
  def get_lamin_site_base_url():
@@ -367,17 +367,25 @@ class InstanceSettings:
367
367
 
368
368
  @property
369
369
  def is_on_hub(self) -> bool:
370
- """Is this instance on the hub.
370
+ """Is this instance on the hub?
371
371
 
372
- Only works if user has access to the instance.
372
+ Can only reliably establish if user has access to the instance. Will
373
+ return `False` in case the instance isn't found.
373
374
  """
374
375
  if self._is_on_hub is None:
375
376
  from ._hub_client import call_with_fallback_auth
376
377
  from ._hub_crud import select_instance_by_id
378
+ from ._settings import settings
377
379
 
378
- response = call_with_fallback_auth(
379
- select_instance_by_id, instance_id=self._id.hex
380
- )
380
+ if settings.user.handle != "anonymous":
381
+ response = call_with_fallback_auth(
382
+ select_instance_by_id, instance_id=self._id.hex
383
+ )
384
+ else:
385
+ response = call_with_fallback(
386
+ select_instance_by_id, instance_id=self._id.hex
387
+ )
388
+ logger.warning("calling anonymously, will miss private instances")
381
389
  if response is None:
382
390
  self._is_on_hub = False
383
391
  else:
@@ -10,11 +10,11 @@ from typing import TYPE_CHECKING, Any, Literal, Optional, Union
10
10
  from appdirs import AppDirs
11
11
  from lamin_utils import logger
12
12
 
13
+ from ._aws_credentials import HOSTED_REGIONS, get_aws_credentials_manager
13
14
  from ._aws_storage import find_closest_aws_region
14
15
  from ._settings_save import save_system_storage_settings
15
16
  from ._settings_store import system_storage_settings_file
16
17
  from .upath import (
17
- HOSTED_REGIONS,
18
18
  LocalPathClasses,
19
19
  UPath,
20
20
  convert_pathlike,
@@ -151,6 +151,7 @@ class StorageSettings:
151
151
  uid: str | None = None,
152
152
  uuid: UUID | None = None,
153
153
  instance_id: UUID | None = None,
154
+ # note that passing access_token prevents credentials caching
154
155
  access_token: str | None = None,
155
156
  ):
156
157
  self._uid = uid
@@ -235,6 +236,12 @@ class StorageSettings:
235
236
  if self._root is None:
236
237
  # below makes network requests to get credentials
237
238
  self._root = create_path(self._root_init, access_token=self.access_token)
239
+ elif getattr(self._root, "protocol", "") == "s3":
240
+ # this is needed to be sure that the root always has nonexpired credentials
241
+ # this just checks for time of the cached credentials in most cases
242
+ return get_aws_credentials_manager().enrich_path(
243
+ self._root, access_token=self.access_token
244
+ )
238
245
  return self._root
239
246
 
240
247
  def _set_fs_kwargs(self, **kwargs):
@@ -11,13 +11,13 @@ from itertools import islice
11
11
  from pathlib import Path, PurePosixPath
12
12
  from typing import TYPE_CHECKING, Any, Literal
13
13
 
14
- import botocore.session
15
14
  import fsspec
16
15
  from lamin_utils import logger
17
16
  from upath import UPath
18
17
  from upath.implementations.cloud import CloudPath, S3Path # keep CloudPath!
19
18
  from upath.implementations.local import LocalPath, PosixUPath, WindowsUPath
20
19
 
20
+ from ._aws_credentials import HOSTED_BUCKETS, get_aws_credentials_manager
21
21
  from .hashing import b16_to_b64, hash_md5s_from_dir
22
22
 
23
23
  if TYPE_CHECKING:
@@ -158,7 +158,8 @@ def print_hook(size: int, value: int, objectname: str, action: str):
158
158
  progress_in_percent = (value / size) * 100
159
159
  out = f"... {action} {objectname}:" f" {min(progress_in_percent, 100):4.1f}%"
160
160
  if "NBPRJ_TEST_NBPATH" not in os.environ:
161
- print(out, end="\r")
161
+ end = "\n" if progress_in_percent >= 100 else "\r"
162
+ print(out, end=end)
162
163
 
163
164
 
164
165
  class ProgressCallback(fsspec.callbacks.Callback):
@@ -670,77 +671,12 @@ def convert_pathlike(pathlike: UPathStr) -> UPath:
670
671
  return path
671
672
 
672
673
 
673
- HOSTED_REGIONS = [
674
- "eu-central-1",
675
- "eu-west-2",
676
- "us-east-1",
677
- "us-east-2",
678
- "us-west-1",
679
- "us-west-2",
680
- ]
681
- lamin_env = os.getenv("LAMIN_ENV")
682
- if lamin_env is None or lamin_env == "prod":
683
- hosted_buckets_list = [f"s3://lamin-{region}" for region in HOSTED_REGIONS]
684
- hosted_buckets_list.append("s3://scverse-spatial-eu-central-1")
685
- HOSTED_BUCKETS = tuple(hosted_buckets_list)
686
- else:
687
- HOSTED_BUCKETS = ("s3://lamin-hosted-test",) # type: ignore
688
- credentials_cache: dict[str, dict[str, str]] = {}
689
- AWS_CREDENTIALS_PRESENT = None
690
-
691
-
692
674
  def create_path(path: UPath, access_token: str | None = None) -> UPath:
693
675
  path = convert_pathlike(path)
694
676
  # test whether we have an AWS S3 path
695
677
  if not isinstance(path, S3Path):
696
678
  return path
697
- # test whether AWS credentials are present
698
- global AWS_CREDENTIALS_PRESENT
699
-
700
- if AWS_CREDENTIALS_PRESENT is not None:
701
- anon = AWS_CREDENTIALS_PRESENT
702
- else:
703
- if path.fs.key is not None and path.fs.secret is not None:
704
- anon = False
705
- else:
706
- # we could do path.fs.connect()
707
- # and check path.fs.session._credentials, but it'd be slower
708
- session = botocore.session.get_session()
709
- credentials = session.get_credentials()
710
- if credentials is None or credentials.access_key is None:
711
- anon = True
712
- else:
713
- anon = False
714
- # cache credentials and reuse further
715
- AWS_CREDENTIALS_PRESENT = anon
716
-
717
- # test whether we are on hosted storage or not
718
- path_str = path.as_posix()
719
- is_hosted_storage = path_str.startswith(HOSTED_BUCKETS)
720
-
721
- if not is_hosted_storage:
722
- # make anon request if no credentials present
723
- return UPath(path, cache_regions=True, anon=anon)
724
-
725
- root_folder = "/".join(path_str.replace("s3://", "").split("/")[:2])
726
-
727
- if access_token is None and root_folder in credentials_cache:
728
- credentials = credentials_cache[root_folder]
729
- else:
730
- from ._hub_core import access_aws
731
-
732
- credentials = access_aws(
733
- storage_root=f"s3://{root_folder}", access_token=access_token
734
- )
735
- if access_token is None:
736
- credentials_cache[root_folder] = credentials
737
-
738
- return UPath(
739
- path,
740
- key=credentials["key"],
741
- secret=credentials["secret"],
742
- token=credentials["token"],
743
- )
679
+ return get_aws_credentials_manager().enrich_path(path, access_token)
744
680
 
745
681
 
746
682
  def get_stat_file_cloud(stat: dict) -> tuple[int, str, str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamindb_setup
3
- Version: 0.71.4
3
+ Version: 0.72.0
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <laminlabs@gmail.com>
6
6
  Description-Content-Type: text/markdown
@@ -1,10 +1,10 @@
1
- lamindb_setup/__init__.py,sha256=w7Zj50IVdvhD0ctVPpTi0Uh4X2FLlm-YplosftlWzEs,1542
1
+ lamindb_setup/__init__.py,sha256=4RLRB4zcYMk9ztnkPkCMOiGJ1hmqyhLXUsSRwtN932I,1542
2
2
  lamindb_setup/_cache.py,sha256=wA7mbysANwe8hPNbjDo9bOmXJ0xIyaS5iyxIpxSWji4,846
3
3
  lamindb_setup/_check.py,sha256=28PcG8Kp6OpjSLSi1r2boL2Ryeh6xkaCL87HFbjs6GA,129
4
4
  lamindb_setup/_check_setup.py,sha256=cNEL9Q4yPpmEkGKHH8JgullWl1VUZwALJ4RHn9wZypY,2613
5
5
  lamindb_setup/_close.py,sha256=1QS9p2SCacgovYn6xqWU4zFvwHN1RgIccvzwJgFvKgU,1186
6
6
  lamindb_setup/_connect_instance.py,sha256=C4WURprypLpvS1oJmfyXHvXCMPc7yOOvKmOabyFr7dE,12728
7
- lamindb_setup/_delete.py,sha256=gRqtlGWUaYDYL97kI7EyLvgI23DIaAoVyOx6hF6LPNw,5498
7
+ lamindb_setup/_delete.py,sha256=Y8KSFYgY0UHAvjd7cCL6hZ_XiLeJwx50BguVATcj_Xo,5524
8
8
  lamindb_setup/_django.py,sha256=EoyWvFzH0i9wxjy4JZhcoXCTckztP_Mrl6FbYQnMmLE,1534
9
9
  lamindb_setup/_exportdb.py,sha256=uTIZjKKTB7arzEr1j0O6lONiT2pRBKeOFdLvOV8ZwzE,2120
10
10
  lamindb_setup/_importdb.py,sha256=yYYShzUajTsR-cTW4CZ-UNDWZY2uE5PAgNbp-wn8Ogc,1874
@@ -16,18 +16,19 @@ lamindb_setup/_set_managed_storage.py,sha256=mNZrANn-9rwZ0oGWxxg0wS0T0VOQCWyo4nS
16
16
  lamindb_setup/_setup_user.py,sha256=6Oc7Rke-yRQSZbuntdUAz8QbJ6UuPzYHI9FnYlf_q-A,3670
17
17
  lamindb_setup/_silence_loggers.py,sha256=AKF_YcHvX32eGXdsYK8MJlxEaZ-Uo2f6QDRzjKFCtws,1568
18
18
  lamindb_setup/core/__init__.py,sha256=dV9S-rQpNK9JcBn4hiEmiLnmNqfpPFJD9pqagMCaIew,416
19
+ lamindb_setup/core/_aws_credentials.py,sha256=0vDs7pWFguBE5UHXf4ysvsjieWygye-Akig6E2Ewl-Q,4749
19
20
  lamindb_setup/core/_aws_storage.py,sha256=nEjeUv4xUVpoV0Lx-zjjmyb9w804bDyaeiM-OqbfwM0,1799
20
21
  lamindb_setup/core/_deprecated.py,sha256=3qxUI1dnDlSeR0BYrv7ucjqRBEojbqotPgpShXs4KF8,2520
21
22
  lamindb_setup/core/_docs.py,sha256=3k-YY-oVaJd_9UIY-LfBg_u8raKOCNfkZQPA73KsUhs,276
22
23
  lamindb_setup/core/_hub_client.py,sha256=V0qKDsCdRn-tQy2YIk4VgXcpJFmuum6N3gcavAC7gBQ,5504
23
- lamindb_setup/core/_hub_core.py,sha256=Jxjf6qyae25NlnrHLqArNaCq1ZjWYuE3pEStbx7jCI0,16043
24
+ lamindb_setup/core/_hub_core.py,sha256=GSPPwBQlqg1l3TfzoaDulcG8u_HAjeqqBgGUJQVY4HQ,16447
24
25
  lamindb_setup/core/_hub_crud.py,sha256=b1XF7AJpM9Q-ttm9nPG-r3OTRWHQaGzAGIyvmb83NTo,4859
25
26
  lamindb_setup/core/_hub_utils.py,sha256=b_M1LkdCjiMWm1EOlSb9GuPdLijwVgQDtATTpeZuXI0,1875
26
27
  lamindb_setup/core/_settings.py,sha256=jjZ_AxRXB3Y3UP6m04BAw_dhFbJbdg2-nZWmEv2LNZ8,3141
27
- lamindb_setup/core/_settings_instance.py,sha256=PtMg-0ADnvH5bsTUPt7mD87qbOqj9pZE4RujWTR3nw8,16191
28
+ lamindb_setup/core/_settings_instance.py,sha256=w5SBrp6nIJMegzNJSnfQl8HvqEtwgmR7OrayffVedLc,16612
28
29
  lamindb_setup/core/_settings_load.py,sha256=NGgCDpN85j1EqoKlrYFIlZBMlBJm33gx2-wc96CP_ZQ,3922
29
30
  lamindb_setup/core/_settings_save.py,sha256=d1A-Ex-7H08mb8l7I0Oe0j0GilrfaDuprh_NMxhQAsQ,2704
30
- lamindb_setup/core/_settings_storage.py,sha256=KnSvMABdGNQYzx6TPVTAprahY23LY1pAfxzYulqtWaw,12751
31
+ lamindb_setup/core/_settings_storage.py,sha256=7f0jt1zcSltpOYDPQ5CVvbBon_d7aneKTte935-2REY,13236
31
32
  lamindb_setup/core/_settings_store.py,sha256=dagS5c7wAMRnuZTRfCU4sKaIOyF_HwAP5Fnnn8vphno,2084
32
33
  lamindb_setup/core/_settings_user.py,sha256=P2lC4WDRAFfT-Xq3MlXJ-wMKIHCoGNhMTQfRGIAyUNQ,1344
33
34
  lamindb_setup/core/_setup_bionty_sources.py,sha256=OgPpZxN2_Wffy-ogEBz_97c_k8d2bD-DDVt89-u9GLY,3002
@@ -36,8 +37,8 @@ lamindb_setup/core/django.py,sha256=QUQm3zt5QIiD8uv6o9vbSm_bshqiSWzKSkgD3z2eJCg,
36
37
  lamindb_setup/core/exceptions.py,sha256=eoI7AXgATgDVzgArtN7CUvpaMUC067vsBg5LHCsWzDM,305
37
38
  lamindb_setup/core/hashing.py,sha256=7r96h5JBzuwfOR_gNNqTyWNPKMuiOUfBYwn6sCbZkf8,2269
38
39
  lamindb_setup/core/types.py,sha256=bcYnZ0uM_2NXKJCl94Mmc-uYrQlRUUVKG3sK2N-F-N4,532
39
- lamindb_setup/core/upath.py,sha256=mou3NdF-tRTLW3E4eNDRFdCZrFZUyt-NPOVyC-DLe24,28342
40
- lamindb_setup-0.71.4.dist-info/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
41
- lamindb_setup-0.71.4.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
42
- lamindb_setup-0.71.4.dist-info/METADATA,sha256=ge5_4_y1g-3nbwgsX_3xP11x27UrZGdr9O0wghjrD3s,1620
43
- lamindb_setup-0.71.4.dist-info/RECORD,,
40
+ lamindb_setup/core/upath.py,sha256=QnAiaOZgT1TLUaX0PEs9dSJ0E4ZDD431hCfKrJIbmqQ,26339
41
+ lamindb_setup-0.72.0.dist-info/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
42
+ lamindb_setup-0.72.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
43
+ lamindb_setup-0.72.0.dist-info/METADATA,sha256=8Bp06EbsyZPsRjXGPGb0Le95RfKbQXlMsf-4sFUgESM,1620
44
+ lamindb_setup-0.72.0.dist-info/RECORD,,