lamindb_setup 0.78.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. lamindb_setup/__init__.py +74 -0
  2. lamindb_setup/_cache.py +48 -0
  3. lamindb_setup/_check.py +7 -0
  4. lamindb_setup/_check_setup.py +92 -0
  5. lamindb_setup/_close.py +35 -0
  6. lamindb_setup/_connect_instance.py +429 -0
  7. lamindb_setup/_delete.py +141 -0
  8. lamindb_setup/_django.py +39 -0
  9. lamindb_setup/_entry_points.py +22 -0
  10. lamindb_setup/_exportdb.py +68 -0
  11. lamindb_setup/_importdb.py +50 -0
  12. lamindb_setup/_init_instance.py +411 -0
  13. lamindb_setup/_migrate.py +239 -0
  14. lamindb_setup/_register_instance.py +36 -0
  15. lamindb_setup/_schema.py +27 -0
  16. lamindb_setup/_schema_metadata.py +411 -0
  17. lamindb_setup/_set_managed_storage.py +55 -0
  18. lamindb_setup/_setup_user.py +137 -0
  19. lamindb_setup/_silence_loggers.py +44 -0
  20. lamindb_setup/core/__init__.py +21 -0
  21. lamindb_setup/core/_aws_credentials.py +151 -0
  22. lamindb_setup/core/_aws_storage.py +48 -0
  23. lamindb_setup/core/_deprecated.py +55 -0
  24. lamindb_setup/core/_docs.py +14 -0
  25. lamindb_setup/core/_hub_client.py +173 -0
  26. lamindb_setup/core/_hub_core.py +554 -0
  27. lamindb_setup/core/_hub_crud.py +211 -0
  28. lamindb_setup/core/_hub_utils.py +109 -0
  29. lamindb_setup/core/_private_django_api.py +88 -0
  30. lamindb_setup/core/_settings.py +184 -0
  31. lamindb_setup/core/_settings_instance.py +485 -0
  32. lamindb_setup/core/_settings_load.py +117 -0
  33. lamindb_setup/core/_settings_save.py +92 -0
  34. lamindb_setup/core/_settings_storage.py +350 -0
  35. lamindb_setup/core/_settings_store.py +75 -0
  36. lamindb_setup/core/_settings_user.py +55 -0
  37. lamindb_setup/core/_setup_bionty_sources.py +101 -0
  38. lamindb_setup/core/cloud_sqlite_locker.py +237 -0
  39. lamindb_setup/core/django.py +115 -0
  40. lamindb_setup/core/exceptions.py +10 -0
  41. lamindb_setup/core/hashing.py +116 -0
  42. lamindb_setup/core/types.py +17 -0
  43. lamindb_setup/core/upath.py +779 -0
  44. lamindb_setup-0.78.0.dist-info/LICENSE +201 -0
  45. lamindb_setup-0.78.0.dist-info/METADATA +47 -0
  46. lamindb_setup-0.78.0.dist-info/RECORD +47 -0
  47. lamindb_setup-0.78.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,350 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import secrets
5
+ import shutil
6
+ import string
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, Literal
9
+
10
+ from lamin_utils import logger
11
+
12
+ from ._aws_credentials import HOSTED_REGIONS, get_aws_credentials_manager
13
+ from ._aws_storage import find_closest_aws_region
14
+ from .upath import (
15
+ LocalPathClasses,
16
+ UPath,
17
+ create_path,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from uuid import UUID
22
+
23
+ from .types import UPathStr
24
+
25
+ IS_INITIALIZED_KEY = ".lamindb/_is_initialized"
26
+
27
+
28
+ def base62(n_char: int) -> str:
29
+ """Like nanoid without hyphen and underscore."""
30
+ alphabet = string.digits + string.ascii_letters.swapcase()
31
+ id = "".join(secrets.choice(alphabet) for i in range(n_char))
32
+ return id
33
+
34
+
35
+ def get_storage_region(path: UPathStr) -> str | None:
36
+ path_str = str(path)
37
+ if path_str.startswith("s3://"):
38
+ import botocore.session
39
+ from botocore.config import Config
40
+ from botocore.exceptions import ClientError
41
+
42
+ # strip the prefix and any suffixes of the bucket name
43
+ bucket = path_str.replace("s3://", "").split("/")[0]
44
+ session = botocore.session.get_session()
45
+ credentials = session.get_credentials()
46
+ if credentials is None or credentials.access_key is None:
47
+ config = Config(signature_version=botocore.session.UNSIGNED)
48
+ else:
49
+ config = None
50
+ s3_client = session.create_client("s3", config=config)
51
+ try:
52
+ response = s3_client.head_bucket(Bucket=bucket)
53
+ except ClientError as exc:
54
+ response = getattr(exc, "response", {})
55
+ if response.get("Error", {}).get("Code") == "404":
56
+ raise exc
57
+ region = (
58
+ response.get("ResponseMetadata", {})
59
+ .get("HTTPHeaders", {})
60
+ .get("x-amz-bucket-region")
61
+ )
62
+ else:
63
+ region = None
64
+ return region
65
+
66
+
67
+ def mark_storage_root(root: UPathStr, uid: str):
68
+ # we need to touch a 0-byte object in folder-like storage location on S3 to avoid
69
+ # permission errors from leveraging s3fs on an empty hosted storage location
70
+ # for consistency, we write this file everywhere
71
+ root_upath = UPath(root)
72
+ mark_upath = root_upath / IS_INITIALIZED_KEY
73
+ mark_upath.write_text(uid)
74
+
75
+
76
+ def init_storage(
77
+ root: UPathStr,
78
+ instance_id: UUID | None = None,
79
+ register_hub: bool | None = None,
80
+ prevent_register_hub: bool = False,
81
+ init_instance: bool = False,
82
+ created_by: UUID | None = None,
83
+ access_token: str | None = None,
84
+ ) -> tuple[
85
+ StorageSettings,
86
+ Literal["hub-record-not-created", "hub-record-retireved", "hub-record-created"],
87
+ ]:
88
+ if root is None:
89
+ raise ValueError("`storage` argument can't be `None`")
90
+ root_str = str(root) # ensure we have a string
91
+ if ".lamindb" in root_str:
92
+ raise ValueError(
93
+ 'Please pass a folder name that does not end or contain ".lamindb"'
94
+ )
95
+ uid = os.getenv("LAMINDB_STORAGE_LNID_INIT")
96
+ if uid is None:
97
+ uid = base62(12)
98
+ else:
99
+ # this means we constructed a hosted location of shape s3://bucket-name/uid
100
+ # within LaminHub
101
+ assert root_str.endswith(uid)
102
+ region = None
103
+ lamin_env = os.getenv("LAMIN_ENV")
104
+ if root_str.startswith("create-s3"):
105
+ if root_str != "create-s3":
106
+ assert "--" in root_str, "example: `create-s3--eu-central-1`"
107
+ region = root_str.replace("create-s3--", "")
108
+ if region is None:
109
+ region = find_closest_aws_region()
110
+ else:
111
+ if region not in HOSTED_REGIONS:
112
+ raise ValueError(f"region has to be one of {HOSTED_REGIONS}")
113
+ if lamin_env is None or lamin_env == "prod":
114
+ root_str = f"s3://lamin-{region}/{uid}"
115
+ else:
116
+ root_str = f"s3://lamin-hosted-test/{uid}"
117
+ elif root_str.startswith(("gs://", "s3://")):
118
+ pass
119
+ else: # local path
120
+ try:
121
+ _ = Path(root_str)
122
+ except Exception as e:
123
+ logger.error("`storage` is not a valid local, GCP storage or AWS S3 path")
124
+ raise e
125
+ ssettings = StorageSettings(
126
+ uid=uid,
127
+ root=root_str,
128
+ region=region,
129
+ instance_id=instance_id,
130
+ access_token=access_token,
131
+ )
132
+ # this stores the result of init_storage_hub
133
+ hub_record_status: Literal[
134
+ "hub-record-not-created", "hub-record-retireved", "hub-record-created"
135
+ ] = "hub-record-not-created"
136
+ # the below might update the uid with one that's already taken on the hub
137
+ if not prevent_register_hub:
138
+ if ssettings.type_is_cloud or register_hub:
139
+ from ._hub_core import delete_storage_record
140
+ from ._hub_core import init_storage as init_storage_hub
141
+
142
+ hub_record_status = init_storage_hub(
143
+ ssettings,
144
+ auto_populate_instance=not init_instance,
145
+ created_by=created_by,
146
+ access_token=access_token,
147
+ )
148
+ # below comes last only if everything else was successful
149
+ try:
150
+ # (federated) credentials for AWS access are provisioned under-the-hood
151
+ # discussion: https://laminlabs.slack.com/archives/C04FPE8V01W/p1719260587167489
152
+ # if access_token was passed in ssettings, it is used here
153
+ mark_storage_root(ssettings.root, ssettings.uid) # type: ignore
154
+ except Exception:
155
+ logger.important(
156
+ f"due to lack of write access, LaminDB won't manage storage location: {ssettings.root}"
157
+ )
158
+ # we have to check hub_record_status here because
159
+ # _select_storage inside init_storage_hub also populates ssettings._uuid
160
+ # and we don't want to delete an existing storage record here
161
+ # only newly created
162
+ if hub_record_status == "hub-record-created" and ssettings._uuid is not None:
163
+ delete_storage_record(ssettings._uuid, access_token=access_token) # type: ignore
164
+ ssettings._instance_id = None
165
+ return ssettings, hub_record_status
166
+
167
+
168
+ class StorageSettings:
169
+ """Settings for a given storage location (local or cloud)."""
170
+
171
+ def __init__(
172
+ self,
173
+ root: UPathStr,
174
+ region: str | None = None,
175
+ uid: str | None = None,
176
+ uuid: UUID | None = None,
177
+ instance_id: UUID | None = None,
178
+ # note that passing access_token prevents credentials caching
179
+ access_token: str | None = None,
180
+ ):
181
+ self._uid = uid
182
+ self._uuid_ = uuid
183
+ self._root_init = UPath(root)
184
+ if isinstance(self._root_init, LocalPathClasses): # local paths
185
+ try:
186
+ (self._root_init / ".lamindb").mkdir(parents=True, exist_ok=True)
187
+ self._root_init = self._root_init.resolve()
188
+ except Exception:
189
+ logger.warning(f"unable to create .lamindb folder in {self._root_init}")
190
+ pass
191
+ self._root = None
192
+ self._instance_id = instance_id
193
+ # we don't yet infer region here to make init fast
194
+ self._region = region
195
+ # would prefer to type below as Registry, but need to think through import order
196
+ self._record: Any | None = None
197
+ # save access_token here for use in self.root
198
+ self.access_token = access_token
199
+
200
+ # local storage
201
+ self._has_local = False
202
+ self._local = None
203
+
204
+ @property
205
+ def id(self) -> int:
206
+ """Storage id in current instance."""
207
+ return self.record.id
208
+
209
+ @property
210
+ def _uuid(self) -> UUID | None:
211
+ """Lamin's internal storage uuid."""
212
+ return self._uuid_
213
+
214
+ @property
215
+ def uid(self) -> str | None:
216
+ """Storage id."""
217
+ if self._uid is None:
218
+ self._uid = self.record.uid
219
+ return self._uid
220
+
221
+ @property
222
+ def _mark_storage_root(self) -> UPath:
223
+ return self.root / IS_INITIALIZED_KEY
224
+
225
+ @property
226
+ def record(self) -> Any:
227
+ """Storage record in current instance."""
228
+ if self._record is None:
229
+ # dynamic import because of import order
230
+ from lnschema_core.models import Storage
231
+
232
+ from ._settings import settings
233
+
234
+ self._record = Storage.objects.using(settings._using_key).get(
235
+ root=self.root_as_str
236
+ )
237
+ return self._record
238
+
239
+ def __repr__(self):
240
+ """String rep."""
241
+ s = f"root='{self.root_as_str}', uid='{self.uid}'"
242
+ if self._uuid is not None:
243
+ s += f", uuid='{self._uuid.hex}'"
244
+ return f"StorageSettings({s})"
245
+
246
+ @property
247
+ def root(self) -> UPath:
248
+ """Root storage location."""
249
+ if self._root is None:
250
+ # below makes network requests to get credentials
251
+ self._root = create_path(self._root_init, access_token=self.access_token)
252
+ elif getattr(self._root, "protocol", "") == "s3":
253
+ # this is needed to be sure that the root always has nonexpired credentials
254
+ # this just checks for time of the cached credentials in most cases
255
+ return get_aws_credentials_manager().enrich_path(
256
+ self._root, access_token=self.access_token
257
+ )
258
+ return self._root
259
+
260
+ def _set_fs_kwargs(self, **kwargs):
261
+ """Set additional fsspec arguments for cloud root.
262
+
263
+ Example:
264
+
265
+ >>> ln.setup.settings.storage._set_fs_kwargs( # any fsspec args
266
+ >>> profile="some_profile", cache_regions=True
267
+ >>> )
268
+ """
269
+ if not isinstance(self._root, LocalPathClasses) and kwargs != {}:
270
+ self._root = UPath(self.root, **kwargs)
271
+
272
+ @property
273
+ def root_as_str(self) -> str:
274
+ """Formatted root string."""
275
+ return self._root_init.as_posix().rstrip("/")
276
+
277
+ @property
278
+ def cache_dir(
279
+ self,
280
+ ) -> UPath:
281
+ """Cache root, a local directory to cache cloud files."""
282
+ from lamindb_setup import settings
283
+
284
+ return settings.cache_dir
285
+
286
+ @property
287
+ def type_is_cloud(self) -> bool:
288
+ """`True` if `storage_root` is in cloud, `False` otherwise."""
289
+ return self.type != "local"
290
+
291
+ @property
292
+ def region(self) -> str | None:
293
+ """Storage region."""
294
+ if self._region is None:
295
+ self._region = get_storage_region(self.root_as_str)
296
+ return self._region
297
+
298
+ @property
299
+ def type(self) -> Literal["local", "s3", "gs"]:
300
+ """AWS S3 vs. Google Cloud vs. local.
301
+
302
+ Returns the protocol as a string: "local", "s3", "gs".
303
+ """
304
+ import fsspec
305
+
306
+ convert = {"file": "local"}
307
+ protocol = fsspec.utils.get_protocol(self.root_as_str)
308
+ return convert.get(protocol, protocol) # type: ignore
309
+
310
+ @property
311
+ def is_on_hub(self) -> bool:
312
+ """Is this instance on the hub.
313
+
314
+ Only works if user has access to the instance.
315
+ """
316
+ if self._uuid is None:
317
+ return False
318
+ else:
319
+ return True
320
+
321
+ def cloud_to_local(
322
+ self, filepath: UPathStr, cache_key: str | None = None, **kwargs
323
+ ) -> UPath:
324
+ """Local (or local cache) filepath from filepath."""
325
+ # cache_key is ignored in cloud_to_local_no_update if filepath is local
326
+ local_filepath = self.cloud_to_local_no_update(filepath, cache_key)
327
+ if isinstance(filepath, UPath) and not isinstance(filepath, LocalPathClasses):
328
+ local_filepath.parent.mkdir(parents=True, exist_ok=True)
329
+ filepath.synchronize(local_filepath, **kwargs)
330
+ return local_filepath
331
+
332
+ def cloud_to_local_no_update(
333
+ self, filepath: UPathStr, cache_key: str | None = None
334
+ ) -> UPath:
335
+ # cache_key is ignored if filepath is local
336
+ if isinstance(filepath, UPath) and not isinstance(filepath, LocalPathClasses):
337
+ local_filepath = self.cache_dir / (
338
+ filepath.path if cache_key is None else cache_key
339
+ )
340
+ else:
341
+ local_filepath = filepath
342
+ return UPath(local_filepath)
343
+
344
+ def key_to_filepath(self, filekey: UPathStr) -> UPath:
345
+ """Cloud or local filepath from filekey."""
346
+ return self.root / filekey
347
+
348
+ def local_filepath(self, filekey: UPathStr) -> UPath:
349
+ """Local (cache) filepath from filekey: `local(filepath(...))`."""
350
+ return self.cloud_to_local(self.key_to_filepath(filekey))
@@ -0,0 +1,75 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from pydantic_settings import BaseSettings, SettingsConfigDict
6
+
7
+ if "LAMIN_SETTINGS_DIR" in os.environ:
8
+ # Needed when running with AWS Lambda, as only tmp/ directory has a write access
9
+ settings_dir = Path(f"{os.environ['LAMIN_SETTINGS_DIR']}/.lamin")
10
+ else:
11
+ # user_config_dir in appdirs is weird on MacOS!
12
+ # hence, let's take home/.lamin
13
+ settings_dir = Path.home() / ".lamin"
14
+
15
+ settings_dir.mkdir(parents=True, exist_ok=True)
16
+
17
+
18
+ def get_settings_file_name_prefix():
19
+ if "LAMIN_ENV" in os.environ:
20
+ if os.environ["LAMIN_ENV"] != "prod":
21
+ return f"{os.environ['LAMIN_ENV']}--"
22
+ return ""
23
+
24
+
25
+ def current_instance_settings_file():
26
+ return settings_dir / f"{get_settings_file_name_prefix()}current_instance.env"
27
+
28
+
29
+ def current_user_settings_file():
30
+ return settings_dir / f"{get_settings_file_name_prefix()}current_user.env"
31
+
32
+
33
+ def instance_settings_file(name: str, owner: str):
34
+ return (
35
+ settings_dir / f"{get_settings_file_name_prefix()}instance--{owner}--{name}.env"
36
+ )
37
+
38
+
39
+ def user_settings_file_email(email: str):
40
+ return settings_dir / f"{get_settings_file_name_prefix()}user--{email}.env"
41
+
42
+
43
+ def user_settings_file_handle(handle: str):
44
+ return settings_dir / f"{get_settings_file_name_prefix()}user--{handle}.env"
45
+
46
+
47
+ def system_storage_settings_file():
48
+ return settings_dir / "storage.env"
49
+
50
+
51
+ class InstanceSettingsStore(BaseSettings):
52
+ api_url: Optional[str] = None
53
+ owner: str
54
+ name: str
55
+ storage_root: str
56
+ storage_region: Optional[str] # take old type annotations here because pydantic
57
+ db: Optional[str] # doesn't like new types on 3.9 even with future annotations
58
+ schema_str: Optional[str]
59
+ schema_id: Optional[str] = None
60
+ id: str
61
+ git_repo: Optional[str]
62
+ keep_artifacts_local: Optional[bool]
63
+ model_config = SettingsConfigDict(env_prefix="lamindb_instance_", env_file=".env")
64
+
65
+
66
+ class UserSettingsStore(BaseSettings):
67
+ email: str
68
+ password: str
69
+ access_token: str
70
+ api_key: Optional[str] = None
71
+ uid: str
72
+ uuid: str
73
+ handle: str
74
+ name: str
75
+ model_config = SettingsConfigDict(env_prefix="lamin_user_", env_file=".env")
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from uuid import UUID
8
+
9
+
10
+ class user_description:
11
+ email = """User email."""
12
+ password = """API key or legacy password."""
13
+ uid = """Universal user ID."""
14
+ handle = "Unique handle."
15
+ name = "Full name."
16
+
17
+
18
+ @dataclass
19
+ class UserSettings:
20
+ """User data. All synched from cloud."""
21
+
22
+ handle: str = "anonymous"
23
+ """Unique handle."""
24
+ email: str | None = None
25
+ """User email."""
26
+ api_key: str | None = None
27
+ """Beta API key."""
28
+ password: str | None = None
29
+ """API key or legacy password."""
30
+ access_token: str | None = None
31
+ """User access token."""
32
+ uid: str = "null"
33
+ """Universal user ID."""
34
+ _uuid: UUID | None = None
35
+ """Lamin's internal user ID."""
36
+ name: str | None = None
37
+ """Full name."""
38
+
39
+ def __repr__(self) -> str:
40
+ """Rich string representation."""
41
+ representation = f"Current user: {self.handle}"
42
+ attrs = ["handle", "email", "uid"]
43
+ for attr in attrs:
44
+ value = getattr(self, attr)
45
+ representation += f"\n- {attr}: {value}"
46
+ return representation
47
+
48
+ @property
49
+ def id(self):
50
+ """Integer id valid in current intance."""
51
+ from lnschema_core.users import current_user_id
52
+
53
+ # there is no cache needed here because current_user_id()
54
+ # has its own cache
55
+ return current_user_id()
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django.db.utils import OperationalError, ProgrammingError
6
+
7
+ if TYPE_CHECKING:
8
+ from ._settings_instance import InstanceSettings
9
+
10
+ # bionty.Source -> bionty.base
11
+ RENAME = {"name": "source", "description": "source_name"}
12
+
13
+
14
+ def write_bionty_sources(isettings: InstanceSettings) -> None:
15
+ """Write bionty sources to Source table."""
16
+ if "bionty" not in isettings.schema:
17
+ return None
18
+ import shutil
19
+
20
+ import bionty
21
+ import bionty.base as bionty_base
22
+ from bionty._bionty import list_biorecord_models
23
+ from bionty.base.dev._handle_sources import parse_sources_yaml
24
+ from bionty.models import Source
25
+
26
+ bionty_models = list_biorecord_models(bionty)
27
+
28
+ shutil.copy(
29
+ bionty_base.settings.current_sources, bionty_base.settings.lamindb_sources
30
+ )
31
+
32
+ all_sources = parse_sources_yaml(bionty_base.settings.local_sources)
33
+ all_sources_dict = all_sources.to_dict(orient="records")
34
+
35
+ def _get_currently_used(key: str):
36
+ return (
37
+ bionty_base.display_currently_used_sources()
38
+ .reset_index()
39
+ .set_index(["entity", key])
40
+ )
41
+
42
+ currently_used = _get_currently_used("organism")
43
+
44
+ all_records = []
45
+ for kwargs in all_sources_dict:
46
+ act = currently_used.loc[(kwargs["entity"], kwargs["organism"])].to_dict()
47
+ if (act["source"] == kwargs["source"]) and (
48
+ act["version"] == kwargs["version"]
49
+ ):
50
+ kwargs["currently_used"] = True
51
+
52
+ # when the database is not yet migrated but setup is updated
53
+ # won't need this once lamindb is released with the new pin
54
+ kwargs["run"] = None # can't yet access tracking information
55
+ kwargs["in_db"] = False
56
+ for db_field, base_col in RENAME.items():
57
+ kwargs[db_field] = kwargs.pop(base_col)
58
+ if kwargs["entity"] in bionty_models:
59
+ kwargs["entity"] = f"bionty.{kwargs['entity']}"
60
+ record = Source(**kwargs)
61
+ all_records.append(record)
62
+
63
+ Source.objects.bulk_create(all_records, ignore_conflicts=True)
64
+
65
+
66
+ def load_bionty_sources(isettings: InstanceSettings):
67
+ """Write currently_used bionty sources to LAMINDB_VERSIONS_PATH in bionty."""
68
+ if "bionty" not in isettings.schema:
69
+ return None
70
+
71
+ import bionty.base as bionty_base
72
+ from bionty.base.dev._handle_sources import parse_currently_used_sources
73
+ from bionty.base.dev._io import write_yaml
74
+ from bionty.models import Source
75
+
76
+ try:
77
+ # need try except because of integer primary key migration
78
+ active_records = (
79
+ Source.objects.filter(currently_used=True).order_by("id").all().values()
80
+ )
81
+ for kwargs in active_records:
82
+ for db_field, base_col in RENAME.items():
83
+ kwargs[base_col] = kwargs.pop(db_field)
84
+ # TODO: non-bionty schema?
85
+ kwargs["entity"] = kwargs["entity"].replace("bionty.", "")
86
+ write_yaml(
87
+ parse_currently_used_sources(active_records),
88
+ bionty_base.settings.lamindb_sources,
89
+ )
90
+ except (OperationalError, ProgrammingError):
91
+ pass
92
+
93
+
94
+ def delete_bionty_sources_yaml():
95
+ """Delete LAMINDB_SOURCES_PATH in bionty."""
96
+ try:
97
+ import bionty.base as bionty_base
98
+
99
+ bionty_base.settings.lamindb_sources.unlink(missing_ok=True)
100
+ except ModuleNotFoundError:
101
+ pass