lamindb_setup 1.3.2__py3-none-any.whl → 1.4.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.
- lamindb_setup/__init__.py +1 -1
- lamindb_setup/_close.py +0 -3
- lamindb_setup/_connect_instance.py +1 -0
- lamindb_setup/_delete.py +1 -1
- lamindb_setup/_init_instance.py +26 -10
- lamindb_setup/_set_managed_storage.py +1 -1
- lamindb_setup/core/_hub_client.py +3 -1
- lamindb_setup/core/_hub_core.py +6 -5
- lamindb_setup/core/_settings_instance.py +35 -37
- lamindb_setup/core/_settings_load.py +1 -0
- lamindb_setup/core/_settings_save.py +1 -0
- lamindb_setup/core/_settings_storage.py +55 -35
- lamindb_setup/core/_settings_store.py +1 -0
- lamindb_setup/core/_setup_bionty_sources.py +9 -81
- lamindb_setup/core/django.py +1 -1
- lamindb_setup/core/upath.py +23 -18
- {lamindb_setup-1.3.2.dist-info → lamindb_setup-1.4.0.dist-info}/METADATA +2 -2
- {lamindb_setup-1.3.2.dist-info → lamindb_setup-1.4.0.dist-info}/RECORD +20 -20
- {lamindb_setup-1.3.2.dist-info → lamindb_setup-1.4.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-1.3.2.dist-info → lamindb_setup-1.4.0.dist-info}/WHEEL +0 -0
lamindb_setup/__init__.py
CHANGED
lamindb_setup/_close.py
CHANGED
|
@@ -4,7 +4,6 @@ from lamin_utils import logger
|
|
|
4
4
|
|
|
5
5
|
from .core._settings import settings
|
|
6
6
|
from .core._settings_store import current_instance_settings_file
|
|
7
|
-
from .core._setup_bionty_sources import delete_bionty_sources_yaml
|
|
8
7
|
from .core.cloud_sqlite_locker import clear_locker
|
|
9
8
|
|
|
10
9
|
|
|
@@ -24,8 +23,6 @@ def close(mute: bool = False) -> None:
|
|
|
24
23
|
logger.warning("did not upload cache file - not enough permissions")
|
|
25
24
|
else:
|
|
26
25
|
raise e
|
|
27
|
-
if "bionty" in settings.instance.modules:
|
|
28
|
-
delete_bionty_sources_yaml()
|
|
29
26
|
current_instance_settings_file().unlink()
|
|
30
27
|
clear_locker()
|
|
31
28
|
if not mute:
|
|
@@ -169,6 +169,7 @@ def _connect_instance(
|
|
|
169
169
|
if (schema_id := instance_result["schema_id"]) is None
|
|
170
170
|
else UUID(schema_id),
|
|
171
171
|
fine_grained_access=instance_result.get("fine_grained_access", False),
|
|
172
|
+
db_permissions=instance_result.get("db_permissions", None),
|
|
172
173
|
)
|
|
173
174
|
else:
|
|
174
175
|
if hub_result != "anonymous-user":
|
lamindb_setup/_delete.py
CHANGED
|
@@ -131,7 +131,7 @@ def delete(slug: str, force: bool = False, require_empty: bool = True) -> int |
|
|
|
131
131
|
# this will error if the user doesn't have permission
|
|
132
132
|
delete_instance_on_hub(isettings._id, require_empty=False)
|
|
133
133
|
delete_by_isettings(isettings)
|
|
134
|
-
# if .
|
|
134
|
+
# if lamin.db file was delete, then we might count -1
|
|
135
135
|
if n_files <= 0 and isettings.storage.type == "local":
|
|
136
136
|
# dir is only empty after sqlite file was delete via delete_by_isettings
|
|
137
137
|
if (isettings.storage.root / ".lamindb").exists():
|
lamindb_setup/_init_instance.py
CHANGED
|
@@ -14,6 +14,7 @@ from lamin_utils import logger
|
|
|
14
14
|
from ._close import close as close_instance
|
|
15
15
|
from ._silence_loggers import silence_loggers
|
|
16
16
|
from .core import InstanceSettings
|
|
17
|
+
from .core._docs import doc_args
|
|
17
18
|
from .core._settings import settings
|
|
18
19
|
from .core._settings_instance import is_local_db_url
|
|
19
20
|
from .core._settings_storage import StorageSettings, init_storage
|
|
@@ -95,11 +96,16 @@ def register_user(usettings):
|
|
|
95
96
|
pass
|
|
96
97
|
|
|
97
98
|
|
|
98
|
-
def
|
|
99
|
-
"""Register user & storage in DB."""
|
|
99
|
+
def register_initial_records(isettings: InstanceSettings, usettings):
|
|
100
|
+
"""Register space, user & storage in DB."""
|
|
100
101
|
from django.db.utils import OperationalError
|
|
102
|
+
from lamindb.models import Space
|
|
101
103
|
|
|
102
104
|
try:
|
|
105
|
+
Space.objects.get_or_create(
|
|
106
|
+
name="All",
|
|
107
|
+
description="Every team & user with access to the instance has access.",
|
|
108
|
+
)
|
|
103
109
|
register_user(usettings)
|
|
104
110
|
register_storage_in_instance(isettings.storage)
|
|
105
111
|
except OperationalError as error:
|
|
@@ -208,22 +214,32 @@ ln.Artifact.using("laminlabs/cellxgene").filter()
|
|
|
208
214
|
Or do you want to switch off auto-connect via `lamin settings set auto-connect false`?
|
|
209
215
|
"""
|
|
210
216
|
|
|
217
|
+
DOC_STORAGE_ARG = "A local or remote folder (`'s3://...'` or `'gs://...'`). Defaults to current working directory."
|
|
218
|
+
DOC_INSTANCE_NAME = (
|
|
219
|
+
"Instance name. If not passed, it will equal the folder name passed to `storage`."
|
|
220
|
+
)
|
|
221
|
+
DOC_DB = "Database connection URL. Defaults to `None`, which implies an SQLite file in the storage location."
|
|
222
|
+
DOC_MODULES = "Comma-separated string of schema modules."
|
|
223
|
+
DOC_LOW_LEVEL_KWARGS = "Keyword arguments for low-level control."
|
|
224
|
+
|
|
211
225
|
|
|
226
|
+
@doc_args(DOC_STORAGE_ARG, DOC_INSTANCE_NAME, DOC_DB, DOC_MODULES, DOC_LOW_LEVEL_KWARGS)
|
|
212
227
|
def init(
|
|
213
228
|
*,
|
|
214
|
-
storage: UPathStr,
|
|
229
|
+
storage: UPathStr = ".",
|
|
215
230
|
name: str | None = None,
|
|
216
231
|
db: PostgresDsn | None = None,
|
|
217
232
|
modules: str | None = None,
|
|
218
233
|
**kwargs,
|
|
219
234
|
) -> None:
|
|
220
|
-
"""
|
|
235
|
+
"""Init a LaminDB instance.
|
|
221
236
|
|
|
222
237
|
Args:
|
|
223
|
-
storage:
|
|
224
|
-
name:
|
|
225
|
-
db:
|
|
226
|
-
modules:
|
|
238
|
+
storage: {}
|
|
239
|
+
name: {}
|
|
240
|
+
db: {}
|
|
241
|
+
modules: {}
|
|
242
|
+
**kwargs: {}
|
|
227
243
|
"""
|
|
228
244
|
isettings = None
|
|
229
245
|
ssettings = None
|
|
@@ -359,8 +375,8 @@ def load_from_isettings(
|
|
|
359
375
|
user = settings.user if user is None else user
|
|
360
376
|
|
|
361
377
|
if init:
|
|
362
|
-
# during init
|
|
363
|
-
|
|
378
|
+
# during init space, user and storage need to be registered
|
|
379
|
+
register_initial_records(isettings, user)
|
|
364
380
|
write_bionty_sources(isettings)
|
|
365
381
|
isettings._update_cloud_sqlite_file(unlock_cloud_sqlite=False)
|
|
366
382
|
else:
|
|
@@ -31,7 +31,7 @@ def set_managed_storage(root: UPathStr, **fs_kwargs):
|
|
|
31
31
|
)
|
|
32
32
|
# here the storage is registered in the hub
|
|
33
33
|
# hub_record_status="hub-record-created" if a new record is created
|
|
34
|
-
# "hub-record-
|
|
34
|
+
# "hub-record-retrieved" if the storage is in the hub already
|
|
35
35
|
ssettings, hub_record_status = init_storage(
|
|
36
36
|
root=root, instance_id=settings.instance._id, register_hub=True
|
|
37
37
|
)
|
|
@@ -69,7 +69,9 @@ def connect_hub(
|
|
|
69
69
|
# function_client_timeout=5 by default
|
|
70
70
|
# increase to avoid rare timeouts for edge functions
|
|
71
71
|
client_options = ClientOptions(
|
|
72
|
-
auto_refresh_token=False,
|
|
72
|
+
auto_refresh_token=False,
|
|
73
|
+
function_client_timeout=20,
|
|
74
|
+
postgrest_client_timeout=20,
|
|
73
75
|
)
|
|
74
76
|
return create_client(env.supabase_api_url, env.supabase_anon_key, client_options)
|
|
75
77
|
|
lamindb_setup/core/_hub_core.py
CHANGED
|
@@ -7,6 +7,7 @@ from importlib import metadata
|
|
|
7
7
|
from typing import TYPE_CHECKING, Literal
|
|
8
8
|
from uuid import UUID
|
|
9
9
|
|
|
10
|
+
import jwt
|
|
10
11
|
from lamin_utils import logger
|
|
11
12
|
from postgrest.exceptions import APIError
|
|
12
13
|
|
|
@@ -121,7 +122,7 @@ def init_storage_hub(
|
|
|
121
122
|
auto_populate_instance: bool = True,
|
|
122
123
|
created_by: UUID | None = None,
|
|
123
124
|
access_token: str | None = None,
|
|
124
|
-
) -> Literal["hub-record-
|
|
125
|
+
) -> Literal["hub-record-retrieved", "hub-record-created"]:
|
|
125
126
|
if settings.user.handle != "anonymous" or access_token is not None:
|
|
126
127
|
return call_with_fallback_auth(
|
|
127
128
|
_init_storage_hub,
|
|
@@ -135,7 +136,7 @@ def init_storage_hub(
|
|
|
135
136
|
_select_storage, ssettings=ssettings, update_uid=True
|
|
136
137
|
)
|
|
137
138
|
if storage_exists:
|
|
138
|
-
return "hub-record-
|
|
139
|
+
return "hub-record-retrieved"
|
|
139
140
|
else:
|
|
140
141
|
raise ValueError("Log in to create a storage location on the hub.")
|
|
141
142
|
|
|
@@ -145,7 +146,7 @@ def _init_storage_hub(
|
|
|
145
146
|
ssettings: StorageSettings,
|
|
146
147
|
auto_populate_instance: bool,
|
|
147
148
|
created_by: UUID | None = None,
|
|
148
|
-
) -> Literal["hub-record-
|
|
149
|
+
) -> Literal["hub-record-retrieved", "hub-record-created"]:
|
|
149
150
|
from lamindb_setup import settings
|
|
150
151
|
|
|
151
152
|
created_by = settings.user._uuid if created_by is None else created_by
|
|
@@ -153,7 +154,7 @@ def _init_storage_hub(
|
|
|
153
154
|
# database
|
|
154
155
|
root = ssettings.root_as_str
|
|
155
156
|
if _select_storage(ssettings, update_uid=True, client=client):
|
|
156
|
-
return "hub-record-
|
|
157
|
+
return "hub-record-retrieved"
|
|
157
158
|
if ssettings.type_is_cloud:
|
|
158
159
|
id = uuid.uuid5(uuid.NAMESPACE_URL, root)
|
|
159
160
|
else:
|
|
@@ -548,7 +549,7 @@ def _sign_in_hub_api_key(api_key: str, client: Client):
|
|
|
548
549
|
access_token = json.loads(response)["accessToken"]
|
|
549
550
|
# probably need more info here to avoid additional queries
|
|
550
551
|
# like handle, uid etc
|
|
551
|
-
account_id =
|
|
552
|
+
account_id = jwt.decode(access_token, options={"verify_signature": False})["sub"]
|
|
552
553
|
client.postgrest.auth(access_token)
|
|
553
554
|
# normally public.account.id is equal to auth.user.id
|
|
554
555
|
data = client.table("account").select("*").eq("id", account_id).execute().data
|
|
@@ -13,7 +13,12 @@ from ._hub_client import call_with_fallback
|
|
|
13
13
|
from ._hub_crud import select_account_handle_name_by_lnid
|
|
14
14
|
from ._hub_utils import LaminDsn, LaminDsnModel
|
|
15
15
|
from ._settings_save import save_instance_settings
|
|
16
|
-
from ._settings_storage import
|
|
16
|
+
from ._settings_storage import (
|
|
17
|
+
LEGACY_STORAGE_UID_FILE_KEY,
|
|
18
|
+
STORAGE_UID_FILE_KEY,
|
|
19
|
+
StorageSettings,
|
|
20
|
+
init_storage,
|
|
21
|
+
)
|
|
17
22
|
from ._settings_store import current_instance_settings_file, instance_settings_file
|
|
18
23
|
from .cloud_sqlite_locker import (
|
|
19
24
|
EXPIRATION_TIME,
|
|
@@ -60,6 +65,7 @@ class InstanceSettings:
|
|
|
60
65
|
api_url: str | None = None,
|
|
61
66
|
schema_id: UUID | None = None,
|
|
62
67
|
fine_grained_access: bool = False,
|
|
68
|
+
db_permissions: str | None = None,
|
|
63
69
|
_locker_user: UserSettings | None = None, # user to lock for if cloud sqlite
|
|
64
70
|
):
|
|
65
71
|
from ._hub_utils import validate_db_arg
|
|
@@ -83,6 +89,8 @@ class InstanceSettings:
|
|
|
83
89
|
# private, whether fine grained access is used
|
|
84
90
|
# needed to be set to request jwt etc
|
|
85
91
|
self._fine_grained_access = fine_grained_access
|
|
92
|
+
# permissions for db such as jwt, read, write etc.
|
|
93
|
+
self._db_permissions = db_permissions
|
|
86
94
|
# if None then settings.user is used
|
|
87
95
|
self._locker_user = _locker_user
|
|
88
96
|
|
|
@@ -144,32 +152,27 @@ class InstanceSettings:
|
|
|
144
152
|
for record in all_local_records:
|
|
145
153
|
root_path = Path(record.root)
|
|
146
154
|
if root_path.exists():
|
|
147
|
-
marker_path = root_path /
|
|
148
|
-
if marker_path.exists():
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
except PermissionError:
|
|
155
|
+
marker_path = root_path / STORAGE_UID_FILE_KEY
|
|
156
|
+
if not marker_path.exists():
|
|
157
|
+
legacy_filepath = root_path / LEGACY_STORAGE_UID_FILE_KEY
|
|
158
|
+
if legacy_filepath.exists():
|
|
152
159
|
logger.warning(
|
|
153
|
-
f"
|
|
160
|
+
f"found legacy marker file, renaming it from {legacy_filepath} to {marker_path}"
|
|
154
161
|
)
|
|
155
|
-
|
|
156
|
-
if uid == record.uid:
|
|
157
|
-
found = True
|
|
158
|
-
break
|
|
159
|
-
elif uid == "":
|
|
160
|
-
try:
|
|
161
|
-
# legacy instance that was not yet marked properly
|
|
162
|
-
mark_storage_root(record.root, record.uid)
|
|
163
|
-
except PermissionError:
|
|
164
|
-
logger.warning(
|
|
165
|
-
f"ignoring the following location because no permission to write to it: {marker_path}"
|
|
166
|
-
)
|
|
167
|
-
continue
|
|
162
|
+
legacy_filepath.rename(marker_path)
|
|
168
163
|
else:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
raise ValueError(
|
|
165
|
+
f"local storage location '{root_path}' is corrupted, cannot find marker file with storage uid"
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
uid = marker_path.read_text()
|
|
169
|
+
except PermissionError:
|
|
170
|
+
logger.warning(
|
|
171
|
+
f"ignoring the following location because no permission to read it: {marker_path}"
|
|
172
|
+
)
|
|
173
|
+
continue
|
|
174
|
+
if uid == record.uid:
|
|
175
|
+
found = True
|
|
173
176
|
break
|
|
174
177
|
if found:
|
|
175
178
|
return StorageSettings(record.root)
|
|
@@ -295,7 +298,8 @@ class InstanceSettings:
|
|
|
295
298
|
@property
|
|
296
299
|
def _sqlite_file(self) -> UPath:
|
|
297
300
|
"""SQLite file."""
|
|
298
|
-
|
|
301
|
+
filepath = self.storage.root / ".lamindb/lamin.db"
|
|
302
|
+
return filepath
|
|
299
303
|
|
|
300
304
|
@property
|
|
301
305
|
def _sqlite_file_local(self) -> Path:
|
|
@@ -477,24 +481,18 @@ class InstanceSettings:
|
|
|
477
481
|
|
|
478
482
|
disable_auto_connect(setup_django)(self, init=True)
|
|
479
483
|
|
|
480
|
-
from lamindb.models import Space
|
|
481
|
-
|
|
482
|
-
Space.objects.get_or_create(
|
|
483
|
-
name="All",
|
|
484
|
-
description="Every team & user with access to the instance has access.",
|
|
485
|
-
)
|
|
486
|
-
|
|
487
484
|
def _load_db(self) -> tuple[bool, str]:
|
|
488
485
|
# Is the database available and initialized as LaminDB?
|
|
489
486
|
# returns a tuple of status code and message
|
|
490
487
|
if self.dialect == "sqlite" and not self._sqlite_file.exists():
|
|
491
|
-
legacy_file = self.storage.key_to_filepath(f"{self.
|
|
488
|
+
legacy_file = self.storage.key_to_filepath(f"{self._id.hex}.lndb")
|
|
492
489
|
if legacy_file.exists():
|
|
493
|
-
|
|
494
|
-
"The SQLite file
|
|
495
|
-
f" {legacy_file} to {self._sqlite_file}"
|
|
490
|
+
logger.warning(
|
|
491
|
+
f"The SQLite file is being renamed from {legacy_file} to {self._sqlite_file}"
|
|
496
492
|
)
|
|
497
|
-
|
|
493
|
+
legacy_file.rename(self._sqlite_file)
|
|
494
|
+
else:
|
|
495
|
+
return False, f"SQLite file {self._sqlite_file} does not exist"
|
|
498
496
|
# we need the local sqlite to setup django
|
|
499
497
|
self._update_local_sqlite_file()
|
|
500
498
|
# setting up django also performs a check for migrations & prints them
|
|
@@ -104,6 +104,7 @@ def setup_instance_from_store(store: InstanceSettingsStore) -> InstanceSettings:
|
|
|
104
104
|
api_url=_null_to_value(store.api_url),
|
|
105
105
|
schema_id=None if store.schema_id == "null" else UUID(store.schema_id),
|
|
106
106
|
fine_grained_access=store.fine_grained_access,
|
|
107
|
+
db_permissions=_null_to_value(store.db_permissions),
|
|
107
108
|
)
|
|
108
109
|
|
|
109
110
|
|
|
@@ -23,7 +23,8 @@ if TYPE_CHECKING:
|
|
|
23
23
|
|
|
24
24
|
from .types import UPathStr
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
STORAGE_UID_FILE_KEY = ".lamindb/storage_uid.txt"
|
|
27
|
+
LEGACY_STORAGE_UID_FILE_KEY = ".lamindb/_is_initialized"
|
|
27
28
|
|
|
28
29
|
# a list of supported fsspec protocols
|
|
29
30
|
# rename file to local before showing to a user
|
|
@@ -82,11 +83,15 @@ def get_storage_region(path: UPathStr) -> str | None:
|
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
def mark_storage_root(root: UPathStr, uid: str):
|
|
85
|
-
# we need
|
|
86
|
+
# we need a file in folder-like storage locations on S3 to avoid
|
|
86
87
|
# permission errors from leveraging s3fs on an empty hosted storage location
|
|
87
|
-
#
|
|
88
|
+
# (path.fs.find raises a PermissionError)
|
|
89
|
+
# we also need it in case a storage location is ambiguous because a server / local environment
|
|
90
|
+
# doesn't have a globally unique identifier, then we screen for this file to map the
|
|
91
|
+
# path on a storage location in the registry
|
|
92
|
+
|
|
88
93
|
root_upath = UPath(root)
|
|
89
|
-
mark_upath = root_upath /
|
|
94
|
+
mark_upath = root_upath / STORAGE_UID_FILE_KEY
|
|
90
95
|
mark_upath.write_text(uid)
|
|
91
96
|
|
|
92
97
|
|
|
@@ -100,7 +105,7 @@ def init_storage(
|
|
|
100
105
|
access_token: str | None = None,
|
|
101
106
|
) -> tuple[
|
|
102
107
|
StorageSettings,
|
|
103
|
-
Literal["hub-record-not-created", "hub-record-
|
|
108
|
+
Literal["hub-record-not-created", "hub-record-retrieved", "hub-record-created"],
|
|
104
109
|
]:
|
|
105
110
|
assert root is not None, "`root` argument can't be `None`"
|
|
106
111
|
|
|
@@ -145,38 +150,46 @@ def init_storage(
|
|
|
145
150
|
)
|
|
146
151
|
# this stores the result of init_storage_hub
|
|
147
152
|
hub_record_status: Literal[
|
|
148
|
-
"hub-record-not-created", "hub-record-
|
|
153
|
+
"hub-record-not-created", "hub-record-retrieved", "hub-record-created"
|
|
149
154
|
] = "hub-record-not-created"
|
|
150
155
|
# the below might update the uid with one that's already taken on the hub
|
|
151
|
-
if not prevent_register_hub:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# below comes last only if everything else was successful
|
|
162
|
-
try:
|
|
163
|
-
# (federated) credentials for AWS access are provisioned under-the-hood
|
|
164
|
-
# discussion: https://laminlabs.slack.com/archives/C04FPE8V01W/p1719260587167489
|
|
165
|
-
# if access_token was passed in ssettings, it is used here
|
|
166
|
-
mark_storage_root(ssettings.root, ssettings.uid) # type: ignore
|
|
167
|
-
except Exception:
|
|
168
|
-
logger.important(
|
|
169
|
-
f"due to lack of write access, LaminDB won't manage storage location: {ssettings.root_as_str}"
|
|
156
|
+
if not prevent_register_hub and (ssettings.type_is_cloud or register_hub):
|
|
157
|
+
from ._hub_core import delete_storage_record, init_storage_hub
|
|
158
|
+
|
|
159
|
+
# this retrieves the storage record if it exists already in the hub
|
|
160
|
+
# and updates uid and instance_id in ssettings
|
|
161
|
+
hub_record_status = init_storage_hub(
|
|
162
|
+
ssettings,
|
|
163
|
+
auto_populate_instance=not init_instance,
|
|
164
|
+
created_by=created_by,
|
|
165
|
+
access_token=access_token,
|
|
170
166
|
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
167
|
+
# below comes last only if everything else was successful
|
|
168
|
+
# we check the write access here only if the storage record has been just created
|
|
169
|
+
# or if the storage is local
|
|
170
|
+
# also we have to check hub_record_status here because
|
|
171
|
+
# _select_storage inside init_storage_hub also populates ssettings._uuid
|
|
172
|
+
# and we don't want to delete an existing storage record here if no write access
|
|
173
|
+
# only newly created
|
|
174
|
+
# local storages not registered in the hub should be also marked
|
|
175
|
+
is_local_not_retrieved = not (
|
|
176
|
+
ssettings.type_is_cloud or hub_record_status == "hub-record-retrieved"
|
|
177
|
+
)
|
|
178
|
+
if hub_record_status == "hub-record-created" or is_local_not_retrieved:
|
|
179
|
+
try:
|
|
180
|
+
# (federated) credentials for AWS access are provisioned under-the-hood
|
|
181
|
+
# discussion: https://laminlabs.slack.com/archives/C04FPE8V01W/p1719260587167489
|
|
182
|
+
# if access_token was passed in ssettings, it is used here
|
|
183
|
+
mark_storage_root(ssettings.root, ssettings.uid) # type: ignore
|
|
184
|
+
except Exception:
|
|
185
|
+
logger.important(
|
|
186
|
+
f"due to lack of write access, LaminDB won't manage storage location: {ssettings.root_as_str}"
|
|
187
|
+
)
|
|
188
|
+
if ssettings._uuid is not None:
|
|
189
|
+
delete_storage_record(ssettings._uuid, access_token=access_token) # type: ignore
|
|
190
|
+
ssettings._uuid_ = None
|
|
191
|
+
hub_record_status = "hub-record-not-created"
|
|
192
|
+
ssettings._instance_id = None
|
|
180
193
|
return ssettings, hub_record_status
|
|
181
194
|
|
|
182
195
|
|
|
@@ -234,7 +247,14 @@ class StorageSettings:
|
|
|
234
247
|
|
|
235
248
|
@property
|
|
236
249
|
def _mark_storage_root(self) -> UPath:
|
|
237
|
-
|
|
250
|
+
marker_path = self.root / STORAGE_UID_FILE_KEY
|
|
251
|
+
legacy_filepath = self.root / LEGACY_STORAGE_UID_FILE_KEY
|
|
252
|
+
if legacy_filepath.exists():
|
|
253
|
+
logger.warning(
|
|
254
|
+
f"found legacy marker file, renaming it from {legacy_filepath} to {marker_path}"
|
|
255
|
+
)
|
|
256
|
+
legacy_filepath.rename(marker_path)
|
|
257
|
+
return marker_path
|
|
238
258
|
|
|
239
259
|
@property
|
|
240
260
|
def record(self) -> Any:
|
|
@@ -1,116 +1,44 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
|
-
from django.db.utils import OperationalError, ProgrammingError
|
|
7
|
-
from lamin_utils import logger
|
|
8
|
-
|
|
9
|
-
from ._settings import settings as setup_settings
|
|
10
|
-
|
|
11
5
|
if TYPE_CHECKING:
|
|
12
6
|
from ._settings_instance import InstanceSettings
|
|
13
7
|
|
|
14
|
-
# bionty.Source -> bionty.base
|
|
15
|
-
RENAME = {"name": "source", "description": "source_name"}
|
|
16
|
-
|
|
17
8
|
|
|
18
9
|
def write_bionty_sources(isettings: InstanceSettings) -> None:
|
|
19
|
-
"""Write bionty sources to Source table."""
|
|
10
|
+
"""Write public bionty sources to bt.Source table."""
|
|
20
11
|
if "bionty" not in isettings.modules:
|
|
21
12
|
return None
|
|
22
|
-
import shutil
|
|
23
13
|
|
|
24
14
|
import bionty
|
|
25
15
|
import bionty.base as bionty_base
|
|
26
|
-
from bionty.
|
|
16
|
+
from bionty._biorecord import list_biorecord_models
|
|
27
17
|
from bionty.base.dev._handle_sources import parse_sources_yaml
|
|
28
18
|
from bionty.models import Source
|
|
29
19
|
|
|
30
20
|
bionty_models = list_biorecord_models(bionty)
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
bionty_base.settings.current_sources, bionty_base.settings.lamindb_sources
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
all_sources = parse_sources_yaml(bionty_base.settings.local_sources)
|
|
22
|
+
all_sources = parse_sources_yaml(bionty_base.settings.public_sources)
|
|
37
23
|
all_sources_dict = all_sources.to_dict(orient="records")
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
currently_used = _get_currently_used("organism")
|
|
25
|
+
currently_used = (
|
|
26
|
+
bionty_base.display_currently_used_sources(mute=True)
|
|
27
|
+
.reset_index()
|
|
28
|
+
.set_index(["entity", "organism"])
|
|
29
|
+
)
|
|
47
30
|
|
|
48
31
|
all_records = []
|
|
49
32
|
for kwargs in all_sources_dict:
|
|
50
33
|
act = currently_used.loc[(kwargs["entity"], kwargs["organism"])].to_dict()
|
|
51
|
-
if (act["
|
|
52
|
-
act["version"] == kwargs["version"]
|
|
53
|
-
):
|
|
34
|
+
if (act["name"] == kwargs["name"]) and (act["version"] == kwargs["version"]):
|
|
54
35
|
kwargs["currently_used"] = True
|
|
55
36
|
|
|
56
|
-
# when the database is not yet migrated but setup is updated
|
|
57
|
-
# won't need this once lamindb is released with the new pin
|
|
58
37
|
kwargs["run"] = None # can't yet access tracking information
|
|
59
38
|
kwargs["in_db"] = False
|
|
60
|
-
for db_field, base_col in RENAME.items():
|
|
61
|
-
kwargs[db_field] = kwargs.pop(base_col)
|
|
62
39
|
if kwargs["entity"] in bionty_models:
|
|
63
40
|
kwargs["entity"] = f"bionty.{kwargs['entity']}"
|
|
64
41
|
record = Source(**kwargs)
|
|
65
42
|
all_records.append(record)
|
|
66
43
|
|
|
67
44
|
Source.objects.bulk_create(all_records, ignore_conflicts=True)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def load_bionty_sources(isettings: InstanceSettings | None = None):
|
|
71
|
-
"""Write currently_used bionty sources to LAMINDB_VERSIONS_PATH in bionty."""
|
|
72
|
-
if isettings is None:
|
|
73
|
-
if setup_settings._instance_settings is not None:
|
|
74
|
-
isettings = setup_settings.instance
|
|
75
|
-
else:
|
|
76
|
-
logger.warning(
|
|
77
|
-
f"Ignoring bionty setup because running in LAMINDB_MULTI_INSTANCE mode = {os.environ.get('LAMINDB_MULTI_INSTANCE')}"
|
|
78
|
-
)
|
|
79
|
-
# not setting up bionty sources
|
|
80
|
-
return None
|
|
81
|
-
if isettings is not None:
|
|
82
|
-
if "bionty" not in isettings.modules:
|
|
83
|
-
# no need to setup anything
|
|
84
|
-
return None
|
|
85
|
-
|
|
86
|
-
import bionty.base as bionty_base
|
|
87
|
-
from bionty.base.dev._handle_sources import parse_currently_used_sources
|
|
88
|
-
from bionty.base.dev._io import write_yaml
|
|
89
|
-
from bionty.models import Source
|
|
90
|
-
|
|
91
|
-
try:
|
|
92
|
-
# need try except because of integer primary key migration
|
|
93
|
-
active_records = (
|
|
94
|
-
Source.objects.filter(currently_used=True).order_by("id").all().values()
|
|
95
|
-
)
|
|
96
|
-
for kwargs in active_records:
|
|
97
|
-
for db_field, base_col in RENAME.items():
|
|
98
|
-
kwargs[base_col] = kwargs.pop(db_field)
|
|
99
|
-
# TODO: non-bionty modules?
|
|
100
|
-
kwargs["entity"] = kwargs["entity"].replace("bionty.", "")
|
|
101
|
-
write_yaml(
|
|
102
|
-
parse_currently_used_sources(active_records),
|
|
103
|
-
bionty_base.settings.lamindb_sources,
|
|
104
|
-
)
|
|
105
|
-
except (OperationalError, ProgrammingError):
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def delete_bionty_sources_yaml():
|
|
110
|
-
"""Delete LAMINDB_SOURCES_PATH in bionty."""
|
|
111
|
-
try:
|
|
112
|
-
import bionty.base as bionty_base
|
|
113
|
-
|
|
114
|
-
bionty_base.settings.lamindb_sources.unlink(missing_ok=True)
|
|
115
|
-
except ModuleNotFoundError:
|
|
116
|
-
pass
|
lamindb_setup/core/django.py
CHANGED
|
@@ -125,7 +125,7 @@ def setup_django(
|
|
|
125
125
|
|
|
126
126
|
BaseDatabaseWrapper.close_if_health_check_failed = close_if_health_check_failed
|
|
127
127
|
|
|
128
|
-
if isettings._fine_grained_access:
|
|
128
|
+
if isettings._fine_grained_access and isettings._db_permissions == "jwt":
|
|
129
129
|
from ._hub_core import access_db
|
|
130
130
|
|
|
131
131
|
set_db_token(access_db(isettings))
|
lamindb_setup/core/upath.py
CHANGED
|
@@ -316,16 +316,26 @@ def upload_from(
|
|
|
316
316
|
callback = ProgressCallback(local_path.name, "uploading")
|
|
317
317
|
kwargs["callback"] = callback
|
|
318
318
|
|
|
319
|
-
source: str | list[str]
|
|
320
|
-
destination: str | list[str]
|
|
321
|
-
if local_path_is_dir
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
319
|
+
source: str | list[str] = local_path.as_posix()
|
|
320
|
+
destination: str | list[str] = self.as_posix()
|
|
321
|
+
if local_path_is_dir:
|
|
322
|
+
size: int = 0
|
|
323
|
+
files: list[str] = []
|
|
324
|
+
for file in (path for path in local_path.rglob("*") if path.is_file()):
|
|
325
|
+
size += file.stat().st_size
|
|
326
|
+
files.append(file.as_posix())
|
|
327
|
+
# see https://github.com/fsspec/s3fs/issues/897
|
|
328
|
+
# here we reduce batch_size for folders bigger than 8 GiB
|
|
329
|
+
# to avoid the problem in the issue
|
|
330
|
+
# the default batch size for this case is 128
|
|
331
|
+
if "batch_size" not in kwargs and size >= 8 * 2**30:
|
|
332
|
+
kwargs["batch_size"] = 64
|
|
333
|
+
|
|
334
|
+
if not create_folder:
|
|
335
|
+
source = files
|
|
336
|
+
destination = fsspec.utils.other_paths(
|
|
337
|
+
files, self.as_posix(), exists=False, flatten=False
|
|
338
|
+
)
|
|
329
339
|
|
|
330
340
|
# the below lines are to avoid s3fs triggering create_bucket in upload if
|
|
331
341
|
# dirs are present, it allows to avoid the permission error
|
|
@@ -899,17 +909,12 @@ def check_storage_is_empty(
|
|
|
899
909
|
) -> int:
|
|
900
910
|
root_upath = UPath(root)
|
|
901
911
|
root_string = root_upath.as_posix() # type: ignore
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
# subdirectories (see lamindb_setup/core/_settings_storage/init_storage).
|
|
906
|
-
n_offset_objects = 1 # because of touched dummy file, see mark_storage_root()
|
|
912
|
+
n_offset_objects = 1 # because of storage_uid.txt file, see mark_storage_root()
|
|
913
|
+
if account_for_sqlite_file:
|
|
914
|
+
n_offset_objects += 1 # the SQLite file is in the ".lamindb" directory
|
|
907
915
|
if root_string.startswith(HOSTED_BUCKETS):
|
|
908
916
|
# in hosted buckets, count across entire root
|
|
909
917
|
directory_string = root_string
|
|
910
|
-
# the SQLite file is not in the ".lamindb" directory
|
|
911
|
-
if account_for_sqlite_file:
|
|
912
|
-
n_offset_objects += 1 # because of SQLite file
|
|
913
918
|
else:
|
|
914
919
|
# in any other storage location, only count in .lamindb
|
|
915
920
|
if not root_string.endswith("/"):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: lamindb_setup
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Setup & configure LaminDB.
|
|
5
5
|
Author-email: Lamin Labs <open-source@lamin.ai>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -15,13 +15,13 @@ Requires-Dist: universal_pathlib==0.2.6
|
|
|
15
15
|
Requires-Dist: botocore<2.0.0
|
|
16
16
|
Requires-Dist: supabase>=2.8.1,<=2.11.0
|
|
17
17
|
Requires-Dist: storage3!=0.11.2; python_version < '3.11'
|
|
18
|
+
Requires-Dist: pyjwt<3.0.0
|
|
18
19
|
Requires-Dist: psutil
|
|
19
20
|
Requires-Dist: packaging
|
|
20
21
|
Requires-Dist: urllib3<2 ; extra == "aws"
|
|
21
22
|
Requires-Dist: aiobotocore[boto3]>=2.5.4,<3.0.0 ; extra == "aws"
|
|
22
23
|
Requires-Dist: s3fs>=2023.12.2,<=2025.2.0,!=2024.10.0 ; extra == "aws"
|
|
23
24
|
Requires-Dist: line_profiler ; extra == "dev"
|
|
24
|
-
Requires-Dist: pyjwt<3.0.0 ; extra == "dev"
|
|
25
25
|
Requires-Dist: psycopg2-binary ; extra == "dev"
|
|
26
26
|
Requires-Dist: python-dotenv ; extra == "dev"
|
|
27
27
|
Requires-Dist: nox ; extra == "dev"
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
lamindb_setup/__init__.py,sha256=
|
|
1
|
+
lamindb_setup/__init__.py,sha256=2h5njKI5Z4qiixV_epzJ1DfWghCpY8ao29gDHqa3guw,2692
|
|
2
2
|
lamindb_setup/_cache.py,sha256=aszT-zk3S5dTLKp5g1W-S_FPh2E5YVCALwWSGPJLWBM,1493
|
|
3
3
|
lamindb_setup/_check.py,sha256=28PcG8Kp6OpjSLSi1r2boL2Ryeh6xkaCL87HFbjs6GA,129
|
|
4
4
|
lamindb_setup/_check_setup.py,sha256=d1GS1Csy2G9AysfjoyVcNVY0lhHiWSwSLpfgdIQf35s,5477
|
|
5
|
-
lamindb_setup/_close.py,sha256=
|
|
6
|
-
lamindb_setup/_connect_instance.py,sha256=
|
|
7
|
-
lamindb_setup/_delete.py,sha256=
|
|
5
|
+
lamindb_setup/_close.py,sha256=SFfOO0OjYR3krvya66kK8RRQa7lPH8MnzUtE33yZ6sQ,1082
|
|
6
|
+
lamindb_setup/_connect_instance.py,sha256=lo0EV_PA8YNlerxfPLG0u-YdmyKLAqbIIhTfzmwNDlI,18352
|
|
7
|
+
lamindb_setup/_delete.py,sha256=2KnZOqd5Kgr45XzjiDE9der35LODDUajZD6_hcurGtQ,5676
|
|
8
8
|
lamindb_setup/_django.py,sha256=uIQflpkp8l3axyPaKURlk3kacgpElVP5KOKmFxYSMGk,1454
|
|
9
9
|
lamindb_setup/_entry_points.py,sha256=sKwXPX9xjOotoAjvgkU5LBwjjHLWVkh0ZGdiSsrch9k,522
|
|
10
10
|
lamindb_setup/_exportdb.py,sha256=QLjoH4dEwqa01A12naKaDPglCCzl2_VLKWFfJRE_uSg,2113
|
|
11
11
|
lamindb_setup/_importdb.py,sha256=fKv9ev5OOj_-bmzC8XZ1GxOcjIjI486yrHSHDWQrJeI,1874
|
|
12
|
-
lamindb_setup/_init_instance.py,sha256=
|
|
12
|
+
lamindb_setup/_init_instance.py,sha256=LRtuXf2jiz5zTs47F7TyhCL0wfHA9YqwWHCBMadtnQs,14837
|
|
13
13
|
lamindb_setup/_migrate.py,sha256=ya-15sc91i4JmEWI4j00T2892x8hdy2fSW-qz4IdxLs,9739
|
|
14
14
|
lamindb_setup/_register_instance.py,sha256=X7ZGlCVOZKq4zTpi3bxML4jzo6hgN9UYmdTxxf6JLmc,1205
|
|
15
15
|
lamindb_setup/_schema.py,sha256=b3uzhhWpV5mQtDwhMINc2MabGCnGLESy51ito3yl6Wc,679
|
|
16
16
|
lamindb_setup/_schema_metadata.py,sha256=cDkNHyFTFZq879UGY6i-rJEXfeBN59AN3Wz86caKnYI,14358
|
|
17
|
-
lamindb_setup/_set_managed_storage.py,sha256=
|
|
17
|
+
lamindb_setup/_set_managed_storage.py,sha256=qC3ACD_PWG-MrzcS3fQpjDEOuxAaJBgPqCU_bDvqtXo,2043
|
|
18
18
|
lamindb_setup/_setup_user.py,sha256=-g7Xj6510BDyM8kuqAsVBZFwehlhBa_uWBSV1rPeuM8,4586
|
|
19
19
|
lamindb_setup/_silence_loggers.py,sha256=AKF_YcHvX32eGXdsYK8MJlxEaZ-Uo2f6QDRzjKFCtws,1568
|
|
20
20
|
lamindb_setup/core/__init__.py,sha256=BxIVMX5HQq8oZ1OuY_saUEJz5Tdd7gaCPngxVu5iou4,417
|
|
@@ -22,26 +22,26 @@ lamindb_setup/core/_aws_options.py,sha256=Umxu_1AYd-0LJUElZxowvE9vNA7Z8kh_63g3pp
|
|
|
22
22
|
lamindb_setup/core/_aws_storage.py,sha256=nEjeUv4xUVpoV0Lx-zjjmyb9w804bDyaeiM-OqbfwM0,1799
|
|
23
23
|
lamindb_setup/core/_deprecated.py,sha256=HN7iUBdEgahw5e4NHCd1VJooUfieNb6GRzS5x8jU-q8,2549
|
|
24
24
|
lamindb_setup/core/_docs.py,sha256=3k-YY-oVaJd_9UIY-LfBg_u8raKOCNfkZQPA73KsUhs,276
|
|
25
|
-
lamindb_setup/core/_hub_client.py,sha256=
|
|
26
|
-
lamindb_setup/core/_hub_core.py,sha256=
|
|
25
|
+
lamindb_setup/core/_hub_client.py,sha256=XJ9V0th11zcx87tDTDVtYf5YjnEC06_jiSRHcnbFseg,7510
|
|
26
|
+
lamindb_setup/core/_hub_core.py,sha256=FCFvnQsPWUYLJ_GFsuQ8rINQ7Rqth71pDfNJHRjPY_0,21344
|
|
27
27
|
lamindb_setup/core/_hub_crud.py,sha256=IAuPZes1am8OFwtcf5jSRQPGG1eKwVTEsp9Li-uq0cQ,5377
|
|
28
28
|
lamindb_setup/core/_hub_utils.py,sha256=6dyDGyzYFgVfR_lE3VN3CP1jGp98gxPtr-T91PAP05U,2687
|
|
29
29
|
lamindb_setup/core/_private_django_api.py,sha256=By63l3vIEtK1pq246FhHq3tslxsaTJGKm5VakYluWp4,2656
|
|
30
30
|
lamindb_setup/core/_settings.py,sha256=eslFO84vb5uRRfJ3r_uu4O8677l8lU5BbpZJMSAYw6A,8244
|
|
31
|
-
lamindb_setup/core/_settings_instance.py,sha256=
|
|
32
|
-
lamindb_setup/core/_settings_load.py,sha256=
|
|
33
|
-
lamindb_setup/core/_settings_save.py,sha256=
|
|
34
|
-
lamindb_setup/core/_settings_storage.py,sha256=
|
|
35
|
-
lamindb_setup/core/_settings_store.py,sha256=
|
|
31
|
+
lamindb_setup/core/_settings_instance.py,sha256=iUoLJj_bl8axs4fiNA5jgLjIEvrMaJBzz-PUDWXmUwg,19395
|
|
32
|
+
lamindb_setup/core/_settings_load.py,sha256=BaII7qH3hKbttHHtB5YECoz0c2SnxL_IcUEMZhuqHCo,4342
|
|
33
|
+
lamindb_setup/core/_settings_save.py,sha256=P04ZnWFSN4Qq_QFWXNvJDs4yxrY9CjQBol_BDku_JEw,3227
|
|
34
|
+
lamindb_setup/core/_settings_storage.py,sha256=op31t12T_fCfTG8OSfnXnIxB9VCQPg8Oo2cnZHVap8Y,14203
|
|
35
|
+
lamindb_setup/core/_settings_store.py,sha256=zyybc6fJXJcFNTjBrgtgbPhriInuhpmQKNoGmy8ScFA,2298
|
|
36
36
|
lamindb_setup/core/_settings_user.py,sha256=lWqV3HmZCsEq2UsU_iVNW0p9ddsNg7-B6xOaMNH1aw0,1475
|
|
37
|
-
lamindb_setup/core/_setup_bionty_sources.py,sha256=
|
|
37
|
+
lamindb_setup/core/_setup_bionty_sources.py,sha256=ox3X-SHiHa2lNPSWjwZhINypbLacX6kGwH6hVVrSFZc,1505
|
|
38
38
|
lamindb_setup/core/cloud_sqlite_locker.py,sha256=i6TrT7HG0lqliPvZTlsZ_uplPaqhPBbabyfeR32SkA8,7107
|
|
39
|
-
lamindb_setup/core/django.py,sha256=
|
|
39
|
+
lamindb_setup/core/django.py,sha256=2qIt3t9Ra_24aKU7q1r30JY87TM_rT6Cvh8rNPlqRc8,4876
|
|
40
40
|
lamindb_setup/core/exceptions.py,sha256=4NpLUNUIfXYVTFX2FvLZF8RW34exk2Vn2X3G4YhnTRg,276
|
|
41
41
|
lamindb_setup/core/hashing.py,sha256=M3Q1-ywnqh4Uy5zojbQfLju19HU0ySp8Oi7FGIJXfFI,3667
|
|
42
42
|
lamindb_setup/core/types.py,sha256=zJii2le38BJUmsNVvzDrbzGYr0yaeb-9Rw9IKmsBr3k,523
|
|
43
|
-
lamindb_setup/core/upath.py,sha256=
|
|
44
|
-
lamindb_setup-1.
|
|
45
|
-
lamindb_setup-1.
|
|
46
|
-
lamindb_setup-1.
|
|
47
|
-
lamindb_setup-1.
|
|
43
|
+
lamindb_setup/core/upath.py,sha256=bjKlQZFe-azlZZZ_VjH9bzfh93G6b88b_IE6E6Dg7cY,33662
|
|
44
|
+
lamindb_setup-1.4.0.dist-info/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
|
|
45
|
+
lamindb_setup-1.4.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
46
|
+
lamindb_setup-1.4.0.dist-info/METADATA,sha256=eirnixadBvZvh_WfrT4ZnGPcpUcUj5nTTw36NDkEi00,1762
|
|
47
|
+
lamindb_setup-1.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|