lamindb_setup 0.69.5__py2.py3-none-any.whl → 0.71.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 +15 -15
- lamindb_setup/_add_remote_storage.py +22 -33
- lamindb_setup/_cache.py +4 -1
- lamindb_setup/_check.py +3 -0
- lamindb_setup/_check_setup.py +13 -7
- lamindb_setup/_close.py +2 -0
- lamindb_setup/_connect_instance.py +50 -34
- lamindb_setup/_delete.py +121 -22
- lamindb_setup/_django.py +4 -1
- lamindb_setup/_exportdb.py +4 -2
- lamindb_setup/_importdb.py +5 -1
- lamindb_setup/_init_instance.py +58 -46
- lamindb_setup/_migrate.py +20 -14
- lamindb_setup/_register_instance.py +10 -3
- lamindb_setup/_schema.py +6 -3
- lamindb_setup/_setup_user.py +8 -8
- lamindb_setup/_silence_loggers.py +4 -2
- lamindb_setup/core/__init__.py +4 -3
- lamindb_setup/core/_aws_storage.py +3 -0
- lamindb_setup/core/_deprecated.py +2 -7
- lamindb_setup/core/_docs.py +2 -0
- lamindb_setup/core/_hub_client.py +12 -10
- lamindb_setup/core/_hub_core.py +206 -85
- lamindb_setup/core/_hub_crud.py +15 -11
- lamindb_setup/core/_hub_utils.py +11 -8
- lamindb_setup/core/_settings.py +23 -26
- lamindb_setup/core/_settings_instance.py +164 -42
- lamindb_setup/core/_settings_load.py +13 -8
- lamindb_setup/core/_settings_save.py +11 -8
- lamindb_setup/core/_settings_storage.py +104 -95
- lamindb_setup/core/_settings_store.py +3 -2
- lamindb_setup/core/_settings_user.py +10 -6
- lamindb_setup/core/_setup_bionty_sources.py +9 -2
- lamindb_setup/core/cloud_sqlite_locker.py +13 -10
- lamindb_setup/core/django.py +5 -0
- lamindb_setup/core/exceptions.py +4 -2
- lamindb_setup/core/hashing.py +15 -5
- lamindb_setup/core/types.py +5 -2
- lamindb_setup/core/upath.py +202 -92
- {lamindb_setup-0.69.5.dist-info → lamindb_setup-0.71.0.dist-info}/METADATA +6 -4
- lamindb_setup-0.71.0.dist-info/RECORD +43 -0
- lamindb_setup-0.69.5.dist-info/RECORD +0 -43
- {lamindb_setup-0.69.5.dist-info → lamindb_setup-0.71.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-0.69.5.dist-info → lamindb_setup-0.71.0.dist-info}/WHEEL +0 -0
|
@@ -1,19 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
4
|
+
import secrets
|
|
2
5
|
import shutil
|
|
3
|
-
|
|
6
|
+
import string
|
|
4
7
|
from pathlib import Path
|
|
5
|
-
from typing import Any, Optional, Union
|
|
6
|
-
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
|
9
|
+
|
|
7
10
|
from appdirs import AppDirs
|
|
11
|
+
from lamin_utils import logger
|
|
12
|
+
|
|
13
|
+
from ._aws_storage import find_closest_aws_region
|
|
8
14
|
from ._settings_save import save_system_storage_settings
|
|
9
15
|
from ._settings_store import system_storage_settings_file
|
|
10
|
-
from .upath import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
from .upath import (
|
|
17
|
+
HOSTED_REGIONS,
|
|
18
|
+
LocalPathClasses,
|
|
19
|
+
UPath,
|
|
20
|
+
convert_pathlike,
|
|
21
|
+
create_path,
|
|
22
|
+
)
|
|
16
23
|
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from uuid import UUID
|
|
26
|
+
|
|
27
|
+
from .types import UPathStr
|
|
17
28
|
|
|
18
29
|
DIRS = AppDirs("lamindb", "laminlabs")
|
|
19
30
|
IS_INITIALIZED_KEY = ".lamindb/_is_initialized"
|
|
@@ -26,7 +37,7 @@ def base62(n_char: int) -> str:
|
|
|
26
37
|
return id
|
|
27
38
|
|
|
28
39
|
|
|
29
|
-
def get_storage_region(storage_root: UPathStr) ->
|
|
40
|
+
def get_storage_region(storage_root: UPathStr) -> str | None:
|
|
30
41
|
storage_root_str = str(storage_root)
|
|
31
42
|
if storage_root_str.startswith("s3://"):
|
|
32
43
|
import botocore.session as session
|
|
@@ -55,19 +66,22 @@ def get_storage_region(storage_root: UPathStr) -> Optional[str]:
|
|
|
55
66
|
return storage_region
|
|
56
67
|
|
|
57
68
|
|
|
58
|
-
def mark_storage_root(root: UPathStr):
|
|
59
|
-
# we need to touch a 0-byte object in
|
|
69
|
+
def mark_storage_root(root: UPathStr, uid: str):
|
|
70
|
+
# we need to touch a 0-byte object in folder-like storage location on S3 to avoid
|
|
60
71
|
# permission errors from leveraging s3fs on an empty hosted storage location
|
|
72
|
+
# for consistency, we write this file everywhere
|
|
61
73
|
root_upath = convert_pathlike(root)
|
|
62
74
|
mark_upath = root_upath / IS_INITIALIZED_KEY
|
|
63
|
-
mark_upath.
|
|
75
|
+
mark_upath.write_text(uid)
|
|
64
76
|
|
|
65
77
|
|
|
66
|
-
def init_storage(
|
|
78
|
+
def init_storage(
|
|
79
|
+
root: UPathStr, instance_id: UUID | None = None, register_hub: bool | None = None
|
|
80
|
+
) -> StorageSettings:
|
|
67
81
|
if root is None:
|
|
68
82
|
raise ValueError("`storage` argument can't be `None`")
|
|
69
83
|
root_str = str(root) # ensure we have a string
|
|
70
|
-
uid = base62(
|
|
84
|
+
uid = base62(12)
|
|
71
85
|
region = None
|
|
72
86
|
lamin_env = os.getenv("LAMIN_ENV")
|
|
73
87
|
if root_str.startswith("create-s3"):
|
|
@@ -77,8 +91,8 @@ def init_storage(root: UPathStr) -> "StorageSettings":
|
|
|
77
91
|
if region is None:
|
|
78
92
|
region = find_closest_aws_region()
|
|
79
93
|
else:
|
|
80
|
-
if region not in
|
|
81
|
-
raise ValueError(f"region has to be one of {
|
|
94
|
+
if region not in HOSTED_REGIONS:
|
|
95
|
+
raise ValueError(f"region has to be one of {HOSTED_REGIONS}")
|
|
82
96
|
if lamin_env is None or lamin_env == "prod":
|
|
83
97
|
root_str = f"s3://lamin-{region}/{uid}"
|
|
84
98
|
else:
|
|
@@ -91,19 +105,23 @@ def init_storage(root: UPathStr) -> "StorageSettings":
|
|
|
91
105
|
except Exception as e:
|
|
92
106
|
logger.error("`storage` is not a valid local, GCP storage or AWS S3 path")
|
|
93
107
|
raise e
|
|
94
|
-
ssettings = StorageSettings(
|
|
95
|
-
|
|
108
|
+
ssettings = StorageSettings(
|
|
109
|
+
uid=uid,
|
|
110
|
+
root=root_str,
|
|
111
|
+
region=region,
|
|
112
|
+
instance_id=instance_id,
|
|
113
|
+
)
|
|
114
|
+
# the below might update the uid with one that's already taken on the hub
|
|
115
|
+
if ssettings.type_is_cloud or register_hub:
|
|
96
116
|
from ._hub_core import init_storage as init_storage_hub
|
|
97
117
|
|
|
98
|
-
ssettings
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if ssettings.is_cloud and root_str.startswith("create-s3"):
|
|
102
|
-
mark_storage_root(ssettings.root)
|
|
118
|
+
init_storage_hub(ssettings)
|
|
119
|
+
# below comes last only if everything else was successful
|
|
120
|
+
mark_storage_root(ssettings.root, ssettings.uid) # type: ignore
|
|
103
121
|
return ssettings
|
|
104
122
|
|
|
105
123
|
|
|
106
|
-
def _process_cache_path(cache_path:
|
|
124
|
+
def _process_cache_path(cache_path: str | Path | UPath | None):
|
|
107
125
|
if cache_path is None or cache_path == "null":
|
|
108
126
|
return None
|
|
109
127
|
cache_dir = UPath(cache_path)
|
|
@@ -115,32 +133,33 @@ def _process_cache_path(cache_path: Union[str, Path, UPath, None]):
|
|
|
115
133
|
|
|
116
134
|
|
|
117
135
|
class StorageSettings:
|
|
118
|
-
"""
|
|
136
|
+
"""Settings for a given storage location (local or cloud)."""
|
|
119
137
|
|
|
120
138
|
def __init__(
|
|
121
139
|
self,
|
|
122
140
|
root: UPathStr,
|
|
123
|
-
region:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
access_token:
|
|
141
|
+
region: str | None = None,
|
|
142
|
+
uid: str | None = None,
|
|
143
|
+
uuid: UUID | None = None,
|
|
144
|
+
instance_id: UUID | None = None,
|
|
145
|
+
access_token: str | None = None,
|
|
128
146
|
):
|
|
129
147
|
self._uid = uid
|
|
130
|
-
self.
|
|
131
|
-
self._is_hybrid = is_hybrid
|
|
148
|
+
self._uuid_ = uuid
|
|
132
149
|
self._root_init = convert_pathlike(root)
|
|
133
150
|
if isinstance(self._root_init, LocalPathClasses): # local paths
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
try:
|
|
152
|
+
(self._root_init / ".lamindb").mkdir(parents=True, exist_ok=True)
|
|
153
|
+
self._root_init = self._root_init.resolve()
|
|
154
|
+
except Exception:
|
|
155
|
+
logger.warning("unable to create .lamindb folder")
|
|
156
|
+
pass
|
|
136
157
|
self._root = None
|
|
137
|
-
self.
|
|
138
|
-
self._aws_account_id: Optional[int] = None
|
|
139
|
-
self._description: Optional[str] = None
|
|
158
|
+
self._instance_id = instance_id
|
|
140
159
|
# we don't yet infer region here to make init fast
|
|
141
160
|
self._region = region
|
|
142
161
|
# would prefer to type below as Registry, but need to think through import order
|
|
143
|
-
self._record:
|
|
162
|
+
self._record: Any | None = None
|
|
144
163
|
# cache settings
|
|
145
164
|
self._storage_settings_file = system_storage_settings_file()
|
|
146
165
|
if self._storage_settings_file.exists():
|
|
@@ -155,88 +174,61 @@ class StorageSettings:
|
|
|
155
174
|
# save access_token here for use in self.root
|
|
156
175
|
self.access_token = access_token
|
|
157
176
|
|
|
177
|
+
# local storage
|
|
178
|
+
self._has_local = False
|
|
179
|
+
self._local = None
|
|
180
|
+
self._is_on_hub: bool | None = None
|
|
181
|
+
|
|
158
182
|
@property
|
|
159
183
|
def id(self) -> int:
|
|
160
|
-
"""Storage id."""
|
|
184
|
+
"""Storage id in current instance."""
|
|
161
185
|
return self.record.id
|
|
162
186
|
|
|
163
187
|
@property
|
|
164
|
-
def
|
|
165
|
-
"""
|
|
166
|
-
return self.
|
|
188
|
+
def _uuid(self) -> UUID | None:
|
|
189
|
+
"""Lamin's internal storage uuid."""
|
|
190
|
+
return self._uuid_
|
|
167
191
|
|
|
168
192
|
@property
|
|
169
|
-
def uid(self) ->
|
|
193
|
+
def uid(self) -> str | None:
|
|
170
194
|
"""Storage id."""
|
|
171
195
|
if self._uid is None:
|
|
172
196
|
self._uid = self.record.uid
|
|
173
197
|
return self._uid
|
|
174
198
|
|
|
199
|
+
@property
|
|
200
|
+
def _mark_storage_root(self) -> UPath:
|
|
201
|
+
return self.root / IS_INITIALIZED_KEY
|
|
202
|
+
|
|
175
203
|
@property
|
|
176
204
|
def record(self) -> Any:
|
|
177
|
-
"""Storage record."""
|
|
205
|
+
"""Storage record in current instance."""
|
|
178
206
|
if self._record is None:
|
|
179
207
|
# dynamic import because of import order
|
|
180
208
|
from lnschema_core.models import Storage
|
|
209
|
+
|
|
181
210
|
from ._settings import settings
|
|
182
211
|
|
|
183
|
-
|
|
184
|
-
self.
|
|
185
|
-
|
|
186
|
-
)
|
|
187
|
-
else:
|
|
188
|
-
# this has to be redone
|
|
189
|
-
records = Storage.objects.filter(type="local").all()
|
|
190
|
-
for record in records:
|
|
191
|
-
if Path(record.root).exists():
|
|
192
|
-
self._record = record
|
|
193
|
-
logger.warning("found local storage location")
|
|
194
|
-
break
|
|
212
|
+
self._record = Storage.objects.using(settings._using_key).get(
|
|
213
|
+
root=self.root_as_str
|
|
214
|
+
)
|
|
195
215
|
return self._record
|
|
196
216
|
|
|
197
217
|
def __repr__(self):
|
|
198
218
|
"""String rep."""
|
|
199
219
|
s = f"root='{self.root_as_str}', uid='{self.uid}'"
|
|
200
|
-
if self.
|
|
201
|
-
s += f", uuid='{self.
|
|
220
|
+
if self._uuid is not None:
|
|
221
|
+
s += f", uuid='{self._uuid.hex}'"
|
|
202
222
|
return f"StorageSettings({s})"
|
|
203
223
|
|
|
204
|
-
@property
|
|
205
|
-
def is_hybrid(self) -> bool:
|
|
206
|
-
"""Qualifies storage mode.
|
|
207
|
-
|
|
208
|
-
A storage location can be local, in the cloud, or hybrid. See
|
|
209
|
-
:attr:`~lamindb.setup.core.StorageSettings.type`.
|
|
210
|
-
|
|
211
|
-
Hybrid means that a default local storage location is backed by an
|
|
212
|
-
optional cloud storage location.
|
|
213
|
-
"""
|
|
214
|
-
return self._is_hybrid
|
|
215
|
-
|
|
216
224
|
@property
|
|
217
225
|
def root(self) -> UPath:
|
|
218
226
|
"""Root storage location."""
|
|
219
227
|
if self._root is None:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
root_path = create_path(self._root_init, access_token=self.access_token)
|
|
223
|
-
else:
|
|
224
|
-
# this is a local path
|
|
225
|
-
root_path = create_path(self.record.root)
|
|
226
|
-
self._root = root_path
|
|
228
|
+
# below makes network requests to get credentials
|
|
229
|
+
self._root = create_path(self._root_init, access_token=self.access_token)
|
|
227
230
|
return self._root
|
|
228
231
|
|
|
229
|
-
@property
|
|
230
|
-
def remote_root(self) -> UPath:
|
|
231
|
-
"""Remote storage location. Only needed for hybrid storage."""
|
|
232
|
-
if not self.is_hybrid:
|
|
233
|
-
raise ValueError("remote_root is only defined for hybrid storage")
|
|
234
|
-
if self._remote_root is None:
|
|
235
|
-
self._remote_root = create_path(
|
|
236
|
-
self._root_init, access_token=self.access_token
|
|
237
|
-
)
|
|
238
|
-
return self._remote_root
|
|
239
|
-
|
|
240
232
|
def _set_fs_kwargs(self, **kwargs):
|
|
241
233
|
"""Set additional fsspec arguments for cloud root.
|
|
242
234
|
|
|
@@ -299,12 +291,12 @@ class StorageSettings:
|
|
|
299
291
|
raise e
|
|
300
292
|
|
|
301
293
|
@property
|
|
302
|
-
def
|
|
294
|
+
def type_is_cloud(self) -> bool:
|
|
303
295
|
"""`True` if `storage_root` is in cloud, `False` otherwise."""
|
|
304
296
|
return self.type != "local"
|
|
305
297
|
|
|
306
298
|
@property
|
|
307
|
-
def region(self) ->
|
|
299
|
+
def region(self) -> str | None:
|
|
308
300
|
"""Storage region."""
|
|
309
301
|
if self._region is None:
|
|
310
302
|
self._region = get_storage_region(self.root_as_str)
|
|
@@ -322,11 +314,28 @@ class StorageSettings:
|
|
|
322
314
|
protocol = fsspec.utils.get_protocol(self.root_as_str)
|
|
323
315
|
return convert.get(protocol, protocol) # type: ignore
|
|
324
316
|
|
|
325
|
-
|
|
317
|
+
@property
|
|
318
|
+
def is_on_hub(self) -> bool:
|
|
319
|
+
"""Is this instance on the hub.
|
|
320
|
+
|
|
321
|
+
Only works if user has access to the instance.
|
|
322
|
+
"""
|
|
323
|
+
if self._is_on_hub is None:
|
|
324
|
+
from ._hub_client import call_with_fallback_auth
|
|
325
|
+
from ._hub_crud import select_storage
|
|
326
|
+
|
|
327
|
+
response = call_with_fallback_auth(select_storage, id=self._uuid.hex) # type: ignore
|
|
328
|
+
if response is None:
|
|
329
|
+
self._is_on_hub = False
|
|
330
|
+
else:
|
|
331
|
+
self._is_on_hub = True
|
|
332
|
+
return self._is_on_hub
|
|
333
|
+
|
|
334
|
+
def key_to_filepath(self, filekey: Path | UPath | str) -> UPath:
|
|
326
335
|
"""Cloud or local filepath from filekey."""
|
|
327
336
|
return self.root / filekey
|
|
328
337
|
|
|
329
|
-
def cloud_to_local(self, filepath:
|
|
338
|
+
def cloud_to_local(self, filepath: Path | UPath, **kwargs) -> UPath:
|
|
330
339
|
"""Local (cache) filepath from filepath."""
|
|
331
340
|
local_filepath = self.cloud_to_local_no_update(filepath) # type: ignore
|
|
332
341
|
if isinstance(filepath, UPath) and not isinstance(filepath, LocalPathClasses):
|
|
@@ -341,9 +350,9 @@ class StorageSettings:
|
|
|
341
350
|
# using the `.parts` attribute in the following line
|
|
342
351
|
def cloud_to_local_no_update(self, filepath: UPath) -> UPath:
|
|
343
352
|
if isinstance(filepath, UPath) and not isinstance(filepath, LocalPathClasses):
|
|
344
|
-
return self.cache_dir.joinpath(filepath._url.netloc, *filepath.parts[1:]) # type: ignore
|
|
353
|
+
return self.cache_dir.joinpath(filepath._url.netloc, *filepath.parts[1:]) # type: ignore
|
|
345
354
|
return filepath
|
|
346
355
|
|
|
347
|
-
def local_filepath(self, filekey:
|
|
356
|
+
def local_filepath(self, filekey: Path | UPath | str) -> UPath:
|
|
348
357
|
"""Local (cache) filepath from filekey: `local(filepath(...))`."""
|
|
349
358
|
return self.cloud_to_local(self.key_to_filepath(filekey))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Optional
|
|
4
|
+
|
|
4
5
|
from pydantic import BaseSettings
|
|
5
6
|
|
|
6
7
|
if "LAMIN_SETTINGS_DIR" in os.environ:
|
|
@@ -51,8 +52,8 @@ class InstanceSettingsStore(BaseSettings):
|
|
|
51
52
|
owner: str
|
|
52
53
|
name: str
|
|
53
54
|
storage_root: str
|
|
54
|
-
storage_region: Optional[str]
|
|
55
|
-
db: Optional[str]
|
|
55
|
+
storage_region: Optional[str] # take old type annotations here because pydantic
|
|
56
|
+
db: Optional[str] # doesn't like new types on 3.9 even with future annotations
|
|
56
57
|
schema_str: Optional[str]
|
|
57
58
|
id: str
|
|
58
59
|
git_repo: Optional[str]
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from uuid import UUID
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
class user_description:
|
|
@@ -19,15 +23,15 @@ class UserSettings:
|
|
|
19
23
|
"""Unique handle."""
|
|
20
24
|
email: str = None # type: ignore
|
|
21
25
|
"""User email."""
|
|
22
|
-
password:
|
|
26
|
+
password: str | None = None
|
|
23
27
|
"""API key or legacy password."""
|
|
24
|
-
access_token:
|
|
28
|
+
access_token: str | None = None
|
|
25
29
|
"""User access token."""
|
|
26
30
|
uid: str = "null"
|
|
27
31
|
"""Universal user ID."""
|
|
28
|
-
|
|
32
|
+
_uuid: UUID | None = None
|
|
29
33
|
"""Lamin's internal user ID."""
|
|
30
|
-
name:
|
|
34
|
+
name: str | None = None
|
|
31
35
|
"""Full name."""
|
|
32
36
|
|
|
33
37
|
def __repr__(self) -> str:
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
2
5
|
from django.db.utils import OperationalError, ProgrammingError
|
|
3
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ._settings_instance import InstanceSettings
|
|
9
|
+
|
|
4
10
|
|
|
5
11
|
def write_bionty_sources(isettings: InstanceSettings) -> None:
|
|
6
12
|
"""Write bionty sources to PublicSource table."""
|
|
7
13
|
if "bionty" not in isettings.schema:
|
|
8
14
|
return None
|
|
9
15
|
import shutil
|
|
10
|
-
|
|
16
|
+
|
|
11
17
|
import bionty_base
|
|
18
|
+
from bionty_base.dev._handle_sources import parse_sources_yaml
|
|
12
19
|
from lnschema_bionty.models import PublicSource
|
|
13
20
|
|
|
14
21
|
shutil.copy(
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from datetime import datetime, timezone
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Optional, Union
|
|
4
4
|
from functools import wraps
|
|
5
|
-
from
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
6
|
+
|
|
6
7
|
from lamin_utils import logger
|
|
7
8
|
|
|
8
|
-
from .upath import UPath,
|
|
9
|
+
from .upath import UPath, create_mapper, infer_filesystem
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from uuid import UUID
|
|
9
14
|
|
|
10
15
|
EXPIRATION_TIME = 24 * 60 * 60 * 7 # 7 days
|
|
11
16
|
|
|
@@ -31,9 +36,7 @@ class empty_locker:
|
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
class Locker:
|
|
34
|
-
def __init__(
|
|
35
|
-
self, user_uid: str, storage_root: Union[UPath, Path], instance_id: UUID
|
|
36
|
-
):
|
|
39
|
+
def __init__(self, user_uid: str, storage_root: UPath | Path, instance_id: UUID):
|
|
37
40
|
logger.debug(
|
|
38
41
|
f"init cloud sqlite locker: {user_uid}, {storage_root}, {instance_id}."
|
|
39
42
|
)
|
|
@@ -169,7 +172,7 @@ class Locker:
|
|
|
169
172
|
return self._has_lock
|
|
170
173
|
|
|
171
174
|
|
|
172
|
-
_locker:
|
|
175
|
+
_locker: Locker | None = None
|
|
173
176
|
|
|
174
177
|
|
|
175
178
|
def get_locker(isettings) -> Locker:
|
|
@@ -184,9 +187,9 @@ def get_locker(isettings) -> Locker:
|
|
|
184
187
|
_locker is None
|
|
185
188
|
or _locker.user != user_uid
|
|
186
189
|
or _locker.root is not storage_root
|
|
187
|
-
or _locker.instance_id != isettings.
|
|
190
|
+
or _locker.instance_id != isettings._id
|
|
188
191
|
):
|
|
189
|
-
_locker = Locker(user_uid, storage_root, isettings.
|
|
192
|
+
_locker = Locker(user_uid, storage_root, isettings._id)
|
|
190
193
|
|
|
191
194
|
return _locker
|
|
192
195
|
|
lamindb_setup/core/django.py
CHANGED
lamindb_setup/core/exceptions.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import Optional
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class DefaultMessageException(Exception):
|
|
5
|
-
default_message:
|
|
7
|
+
default_message: str | None = None
|
|
6
8
|
|
|
7
|
-
def __init__(self, message:
|
|
9
|
+
def __init__(self, message: str | None = None):
|
|
8
10
|
if message is None:
|
|
9
11
|
message = self.default_message
|
|
10
12
|
super().__init__(message)
|
lamindb_setup/core/hashing.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""Hashing.
|
|
2
4
|
|
|
3
5
|
.. autosummary::
|
|
@@ -10,8 +12,16 @@
|
|
|
10
12
|
|
|
11
13
|
import base64
|
|
12
14
|
import hashlib
|
|
13
|
-
from typing import
|
|
14
|
-
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .types import Path, UPathStr
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def hash_and_encode_as_b62(s: str) -> str:
|
|
22
|
+
from lamin_utils._base62 import encodebytes
|
|
23
|
+
|
|
24
|
+
return encodebytes(hashlib.md5(s.encode()).digest())
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
def to_b64_str(bstr: bytes):
|
|
@@ -24,13 +34,13 @@ def b16_to_b64(s: str):
|
|
|
24
34
|
|
|
25
35
|
|
|
26
36
|
# a lot to read about this: lamin-notes/2022/hashing
|
|
27
|
-
def hash_set(s:
|
|
37
|
+
def hash_set(s: set[str]) -> str:
|
|
28
38
|
bstr = ":".join(sorted(s)).encode("utf-8")
|
|
29
39
|
# as we're truncating at 20 b64, we choose md5 over sha512
|
|
30
40
|
return to_b64_str(hashlib.md5(bstr).digest())[:20]
|
|
31
41
|
|
|
32
42
|
|
|
33
|
-
def hash_md5s_from_dir(etags:
|
|
43
|
+
def hash_md5s_from_dir(etags: list[str]) -> tuple[str, str]:
|
|
34
44
|
# need to sort below because we don't want the order of parsing the dir to
|
|
35
45
|
# affect the hash
|
|
36
46
|
digests = b"".join(
|
|
@@ -49,7 +59,7 @@ def hash_code(file_path: UPathStr):
|
|
|
49
59
|
return hashlib.sha1(blob)
|
|
50
60
|
|
|
51
61
|
|
|
52
|
-
def hash_file(file_path: Path, chunk_size=50 * 1024 * 1024) ->
|
|
62
|
+
def hash_file(file_path: Path, chunk_size=50 * 1024 * 1024) -> tuple[str, str]:
|
|
53
63
|
chunks = []
|
|
54
64
|
with open(file_path, "rb") as fp:
|
|
55
65
|
# read first chunk
|
lamindb_setup/core/types.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""Types.
|
|
2
4
|
|
|
3
5
|
.. autosummary::
|
|
@@ -5,10 +7,11 @@
|
|
|
5
7
|
|
|
6
8
|
UPathStr
|
|
7
9
|
"""
|
|
10
|
+
# we need Union here because __future__ annotations doesn't work with TypeAlias
|
|
11
|
+
from pathlib import Path
|
|
8
12
|
from typing import (
|
|
9
13
|
Union,
|
|
10
|
-
)
|
|
11
|
-
from pathlib import Path
|
|
14
|
+
)
|
|
12
15
|
|
|
13
16
|
# UPath is subclass of Path, hence, it's not necessary to list UPath
|
|
14
17
|
# we keep it in the name of the TypeAlias to make it clear to users that
|