lamindb_setup 0.69.4__py2.py3-none-any.whl → 0.70.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 +1 -1
- lamindb_setup/_connect_instance.py +19 -10
- lamindb_setup/_delete.py +85 -19
- lamindb_setup/_init_instance.py +1 -1
- lamindb_setup/_migrate.py +4 -1
- lamindb_setup/_register_instance.py +1 -1
- lamindb_setup/_setup_user.py +1 -1
- lamindb_setup/core/_hub_core.py +27 -16
- lamindb_setup/core/_settings_instance.py +67 -13
- lamindb_setup/core/_settings_load.py +1 -1
- lamindb_setup/core/_settings_save.py +1 -1
- lamindb_setup/core/_settings_storage.py +26 -58
- lamindb_setup/core/_settings_user.py +1 -1
- lamindb_setup/core/django.py +3 -0
- lamindb_setup/core/upath.py +49 -15
- {lamindb_setup-0.69.4.dist-info → lamindb_setup-0.70.0.dist-info}/METADATA +1 -1
- {lamindb_setup-0.69.4.dist-info → lamindb_setup-0.70.0.dist-info}/RECORD +19 -19
- {lamindb_setup-0.69.4.dist-info → lamindb_setup-0.70.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-0.69.4.dist-info → lamindb_setup-0.70.0.dist-info}/WHEEL +0 -0
lamindb_setup/__init__.py
CHANGED
|
@@ -27,6 +27,17 @@ from ._migrate import check_whether_migrations_in_sync
|
|
|
27
27
|
_TEST_FAILED_LOAD = False
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
INSTANCE_NOT_FOUND_MESSAGE = (
|
|
31
|
+
"'{owner}/{name}' not found:"
|
|
32
|
+
" '{hub_result}'\nCheck your permissions:"
|
|
33
|
+
" https://lamin.ai/{owner}/{name}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class InstanceNotFoundError(SystemExit):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
30
41
|
def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
|
|
31
42
|
return (
|
|
32
43
|
db_dsn_hub.scheme == db_dsn_local.scheme
|
|
@@ -121,6 +132,8 @@ def connect(
|
|
|
121
132
|
make_hub_request = True
|
|
122
133
|
if settings_file.exists():
|
|
123
134
|
isettings = load_instance_settings(settings_file)
|
|
135
|
+
# mimic instance_result from hub
|
|
136
|
+
instance_result = {"id": isettings.id.hex}
|
|
124
137
|
# skip hub request for a purely local instance
|
|
125
138
|
make_hub_request = isettings.is_remote
|
|
126
139
|
|
|
@@ -139,7 +152,6 @@ def connect(
|
|
|
139
152
|
root=storage_result["root"],
|
|
140
153
|
region=storage_result["region"],
|
|
141
154
|
uid=storage_result["lnid"],
|
|
142
|
-
is_hybrid=instance_result["storage_mode"] == "hybrid",
|
|
143
155
|
)
|
|
144
156
|
isettings = InstanceSettings(
|
|
145
157
|
id=UUID(instance_result["id"]),
|
|
@@ -149,27 +161,24 @@ def connect(
|
|
|
149
161
|
db=db_updated,
|
|
150
162
|
schema=instance_result["schema_str"],
|
|
151
163
|
git_repo=instance_result["git_repo"],
|
|
164
|
+
local_storage=instance_result["storage_mode"] == "hybrid",
|
|
152
165
|
)
|
|
153
166
|
check_whether_migrations_in_sync(instance_result["lamindb_version"])
|
|
154
167
|
else:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
f" '{hub_result}'\nCheck your permissions:"
|
|
158
|
-
f" https://lamin.ai/{owner}/{name}?tab=collaborators"
|
|
168
|
+
message = INSTANCE_NOT_FOUND_MESSAGE.format(
|
|
169
|
+
owner=owner, name=name, hub_result=hub_result
|
|
159
170
|
)
|
|
160
171
|
if settings_file.exists():
|
|
161
172
|
isettings = load_instance_settings(settings_file)
|
|
162
173
|
if isettings.is_remote:
|
|
163
174
|
if _raise_not_reachable_error:
|
|
164
|
-
raise
|
|
175
|
+
raise InstanceNotFoundError(message)
|
|
165
176
|
return "instance-not-reachable"
|
|
166
177
|
|
|
167
178
|
else:
|
|
168
179
|
if _raise_not_reachable_error:
|
|
169
|
-
raise
|
|
180
|
+
raise InstanceNotFoundError(message)
|
|
170
181
|
return "instance-not-reachable"
|
|
171
|
-
# mimic instance_result from hub
|
|
172
|
-
instance_result = {"id": isettings.id.hex}
|
|
173
182
|
|
|
174
183
|
if storage is not None:
|
|
175
184
|
update_isettings_with_storage(isettings, storage)
|
|
@@ -255,7 +264,7 @@ def update_isettings_with_storage(
|
|
|
255
264
|
isettings: InstanceSettings, storage: UPathStr
|
|
256
265
|
) -> None:
|
|
257
266
|
ssettings = StorageSettings(storage)
|
|
258
|
-
if ssettings.
|
|
267
|
+
if ssettings.type_is_cloud:
|
|
259
268
|
try: # triggering ssettings.id makes a lookup in the storage table
|
|
260
269
|
logger.success(f"loaded storage: {ssettings.id} / {ssettings.root_as_str}")
|
|
261
270
|
except RuntimeError as e:
|
lamindb_setup/_delete.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import shutil
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from lamin_utils import logger
|
|
4
|
+
from uuid import UUID
|
|
4
5
|
from typing import Optional
|
|
5
6
|
from .core._settings_instance import InstanceSettings
|
|
7
|
+
from .core._settings_storage import StorageSettings
|
|
6
8
|
from .core._settings import settings
|
|
7
9
|
from .core._settings_load import load_instance_settings
|
|
8
10
|
from .core._settings_store import instance_settings_file
|
|
11
|
+
from .core.upath import check_storage_is_empty, hosted_buckets
|
|
12
|
+
from .core._hub_core import delete_instance as delete_instance_on_hub
|
|
13
|
+
from .core._hub_core import connect_instance as load_instance_from_hub
|
|
14
|
+
from ._connect_instance import INSTANCE_NOT_FOUND_MESSAGE
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
def delete_cache(cache_dir: Path):
|
|
@@ -13,6 +19,12 @@ def delete_cache(cache_dir: Path):
|
|
|
13
19
|
shutil.rmtree(cache_dir)
|
|
14
20
|
|
|
15
21
|
|
|
22
|
+
def delete_exclusion_dir(isettings: InstanceSettings) -> None:
|
|
23
|
+
exclusion_dir = isettings.storage.root / f".lamindb/_exclusion/{isettings.id.hex}"
|
|
24
|
+
if exclusion_dir.exists():
|
|
25
|
+
exclusion_dir.rmdir()
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
17
29
|
settings_file = isettings._get_settings_file()
|
|
18
30
|
if settings_file.exists():
|
|
@@ -22,29 +34,31 @@ def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
|
22
34
|
try:
|
|
23
35
|
if isettings._sqlite_file.exists():
|
|
24
36
|
isettings._sqlite_file.unlink()
|
|
25
|
-
exclusion_dir = (
|
|
26
|
-
isettings.storage.root / f".lamindb/_exclusion/{isettings.id.hex}"
|
|
27
|
-
)
|
|
28
|
-
if exclusion_dir.exists():
|
|
29
|
-
exclusion_dir.rmdir()
|
|
30
37
|
except PermissionError:
|
|
31
38
|
logger.warning(
|
|
32
39
|
"Did not have permission to delete SQLite file:"
|
|
33
40
|
f" {isettings._sqlite_file}"
|
|
34
41
|
)
|
|
35
42
|
pass
|
|
36
|
-
if isettings.is_remote:
|
|
37
|
-
logger.warning("manually delete your remote instance on lamin.ai")
|
|
38
|
-
logger.warning(f"manually delete your stored data: {isettings.storage.root}")
|
|
39
43
|
# unset the global instance settings
|
|
40
44
|
if settings._instance_exists and isettings.slug == settings.instance.slug:
|
|
41
45
|
if settings._instance_settings_path.exists():
|
|
42
46
|
settings._instance_settings_path.unlink()
|
|
43
47
|
settings._instance_settings = None
|
|
48
|
+
if isettings.storage._mark_storage_root.exists():
|
|
49
|
+
isettings.storage._mark_storage_root.unlink()
|
|
44
50
|
|
|
45
51
|
|
|
46
|
-
def delete(
|
|
47
|
-
|
|
52
|
+
def delete(
|
|
53
|
+
instance_name: str, force: bool = False, require_empty: bool = True
|
|
54
|
+
) -> Optional[int]:
|
|
55
|
+
"""Delete a LaminDB instance.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
instance_name (str): The name of the instance to delete.
|
|
59
|
+
force (bool): Whether to skip the confirmation prompt.
|
|
60
|
+
require_empty (bool): Whether to check if the instance is empty before deleting.
|
|
61
|
+
"""
|
|
48
62
|
if "/" in instance_name:
|
|
49
63
|
logger.warning(
|
|
50
64
|
"Deleting the instance of another user is currently not supported with the"
|
|
@@ -53,6 +67,44 @@ def delete(instance_name: str, force: bool = False) -> Optional[int]:
|
|
|
53
67
|
)
|
|
54
68
|
raise ValueError("Invalid instance name: '/' delimiter not allowed.")
|
|
55
69
|
instance_slug = f"{settings.user.handle}/{instance_name}"
|
|
70
|
+
if settings._instance_exists and settings.instance.name == instance_name:
|
|
71
|
+
isettings = settings.instance
|
|
72
|
+
else:
|
|
73
|
+
settings_file = instance_settings_file(instance_name, settings.user.handle)
|
|
74
|
+
if not settings_file.exists():
|
|
75
|
+
hub_result = load_instance_from_hub(
|
|
76
|
+
owner=settings.user.handle, name=instance_name
|
|
77
|
+
)
|
|
78
|
+
if isinstance(hub_result, str):
|
|
79
|
+
message = INSTANCE_NOT_FOUND_MESSAGE.format(
|
|
80
|
+
owner=settings.user.handle,
|
|
81
|
+
name=instance_name,
|
|
82
|
+
hub_result=hub_result,
|
|
83
|
+
)
|
|
84
|
+
logger.warning(message)
|
|
85
|
+
return None
|
|
86
|
+
instance_result, storage_result = hub_result
|
|
87
|
+
ssettings = StorageSettings(
|
|
88
|
+
root=storage_result["root"],
|
|
89
|
+
region=storage_result["region"],
|
|
90
|
+
uid=storage_result["lnid"],
|
|
91
|
+
)
|
|
92
|
+
isettings = InstanceSettings(
|
|
93
|
+
id=UUID(instance_result["id"]),
|
|
94
|
+
owner=settings.user.handle,
|
|
95
|
+
name=instance_name,
|
|
96
|
+
storage=ssettings,
|
|
97
|
+
local_storage=instance_result["storage_mode"] == "hybrid",
|
|
98
|
+
db=instance_result["db"] if "db" in instance_result else None,
|
|
99
|
+
schema=instance_result["schema_str"],
|
|
100
|
+
git_repo=instance_result["git_repo"],
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
isettings = load_instance_settings(settings_file)
|
|
104
|
+
if isettings.dialect != "sqlite":
|
|
105
|
+
logger.warning(
|
|
106
|
+
f"delete() does not yet affect your Postgres database at {isettings.db}"
|
|
107
|
+
)
|
|
56
108
|
if not force:
|
|
57
109
|
valid_responses = ["y", "yes"]
|
|
58
110
|
user_input = (
|
|
@@ -62,17 +114,31 @@ def delete(instance_name: str, force: bool = False) -> Optional[int]:
|
|
|
62
114
|
)
|
|
63
115
|
if user_input not in valid_responses:
|
|
64
116
|
return -1
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
117
|
+
|
|
118
|
+
# the actual deletion process begins here
|
|
119
|
+
if isettings.dialect == "sqlite" and isettings.is_remote:
|
|
120
|
+
# delete the exlusion dir first because it's hard to count its objects
|
|
121
|
+
delete_exclusion_dir(isettings)
|
|
122
|
+
if isettings.storage.type_is_cloud and isettings.storage.root_as_str.startswith(
|
|
123
|
+
hosted_buckets
|
|
124
|
+
):
|
|
125
|
+
if not require_empty:
|
|
70
126
|
logger.warning(
|
|
71
|
-
"
|
|
72
|
-
" provide a wrong instance name? could you try loading it?"
|
|
127
|
+
"hosted storage always has to be empty, ignoring `require_empty`"
|
|
73
128
|
)
|
|
74
|
-
|
|
75
|
-
|
|
129
|
+
require_empty = True
|
|
130
|
+
n_objects = check_storage_is_empty(
|
|
131
|
+
isettings.storage.root,
|
|
132
|
+
raise_error=require_empty,
|
|
133
|
+
account_for_sqlite_file=isettings.dialect == "sqlite",
|
|
134
|
+
)
|
|
76
135
|
logger.info(f"deleting instance {instance_slug}")
|
|
136
|
+
# below we can skip check_storage_is_empty() because we already called
|
|
137
|
+
# it above
|
|
138
|
+
delete_instance_on_hub(isettings.id, require_empty=False)
|
|
77
139
|
delete_by_isettings(isettings)
|
|
140
|
+
if n_objects == 0 and isettings.storage.type == "local":
|
|
141
|
+
# dir is only empty after sqlite file was delete via delete_by_isettings
|
|
142
|
+
(isettings.storage.root / ".lamindb").rmdir()
|
|
143
|
+
isettings.storage.root.rmdir()
|
|
78
144
|
return None
|
lamindb_setup/_init_instance.py
CHANGED
|
@@ -269,7 +269,7 @@ def init(
|
|
|
269
269
|
delete_instance_record(isettings.id)
|
|
270
270
|
isettings._get_settings_file().unlink(missing_ok=True) # type: ignore
|
|
271
271
|
if ssettings is not None:
|
|
272
|
-
delete_storage_record(ssettings.
|
|
272
|
+
delete_storage_record(ssettings._uuid) # type: ignore
|
|
273
273
|
raise e
|
|
274
274
|
return None
|
|
275
275
|
|
lamindb_setup/_migrate.py
CHANGED
|
@@ -16,6 +16,9 @@ def check_whether_migrations_in_sync(db_version_str: str):
|
|
|
16
16
|
installed_version_str = metadata.version("lamindb")
|
|
17
17
|
except metadata.PackageNotFoundError:
|
|
18
18
|
return None
|
|
19
|
+
if db_version_str is None:
|
|
20
|
+
logger.warning("no lamindb version stored to compare with installed version")
|
|
21
|
+
return None
|
|
19
22
|
installed_version = version.parse(installed_version_str)
|
|
20
23
|
db_version = version.parse(db_version_str)
|
|
21
24
|
if (
|
|
@@ -83,7 +86,7 @@ class migrate:
|
|
|
83
86
|
collaborator = call_with_fallback_auth(
|
|
84
87
|
select_collaborator,
|
|
85
88
|
instance_id=instance_id_str,
|
|
86
|
-
account_id=settings.user.
|
|
89
|
+
account_id=settings.user._uuid,
|
|
87
90
|
)
|
|
88
91
|
if collaborator is None or collaborator["role"] != "admin":
|
|
89
92
|
raise SystemExit(
|
|
@@ -17,6 +17,6 @@ def register(_test: bool = False):
|
|
|
17
17
|
if ssettings._uid is None and _test:
|
|
18
18
|
# because django isn't up, we can't get it from the database
|
|
19
19
|
ssettings._uid = base62(8)
|
|
20
|
-
ssettings.
|
|
20
|
+
ssettings._uuid_ = init_storage_hub(ssettings)
|
|
21
21
|
init_instance_hub(isettings)
|
|
22
22
|
isettings._persist()
|
lamindb_setup/_setup_user.py
CHANGED
|
@@ -99,7 +99,7 @@ def login(
|
|
|
99
99
|
user_settings.uid = user_id
|
|
100
100
|
user_settings.handle = user_handle
|
|
101
101
|
user_settings.name = user_name
|
|
102
|
-
user_settings.
|
|
102
|
+
user_settings._uuid = user_uuid
|
|
103
103
|
user_settings.access_token = access_token
|
|
104
104
|
save_user_settings(user_settings)
|
|
105
105
|
|
lamindb_setup/core/_hub_core.py
CHANGED
|
@@ -48,6 +48,14 @@ def _delete_storage_record(storage_uuid: UUID, client: Client) -> None:
|
|
|
48
48
|
client.table("storage").delete().eq("id", storage_uuid.hex).execute()
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def update_instance_record(instance_uuid: UUID, fields: Dict) -> None:
|
|
52
|
+
from ._hub_crud import update_instance
|
|
53
|
+
|
|
54
|
+
return call_with_fallback_auth(
|
|
55
|
+
update_instance, instance_id=instance_uuid.hex, instance_fields=fields
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
51
59
|
def init_storage(
|
|
52
60
|
ssettings: StorageSettings,
|
|
53
61
|
) -> UUID:
|
|
@@ -70,7 +78,7 @@ def _init_storage(ssettings: StorageSettings, client: Client) -> UUID:
|
|
|
70
78
|
fields = dict(
|
|
71
79
|
id=id.hex,
|
|
72
80
|
lnid=ssettings.uid,
|
|
73
|
-
created_by=settings.user.
|
|
81
|
+
created_by=settings.user._uuid.hex, # type: ignore
|
|
74
82
|
root=root,
|
|
75
83
|
region=ssettings.region,
|
|
76
84
|
type=ssettings.type,
|
|
@@ -81,13 +89,13 @@ def _init_storage(ssettings: StorageSettings, client: Client) -> UUID:
|
|
|
81
89
|
return id
|
|
82
90
|
|
|
83
91
|
|
|
84
|
-
def delete_instance(identifier: Union[UUID, str]):
|
|
92
|
+
def delete_instance(identifier: Union[UUID, str], require_empty: bool = True) -> None:
|
|
85
93
|
"""Fully delete an instance in the hub.
|
|
86
94
|
|
|
87
95
|
This function deletes the relevant instance and storage records in the hub,
|
|
88
96
|
conditional on the emptiness of the storage location.
|
|
89
97
|
"""
|
|
90
|
-
from .upath import
|
|
98
|
+
from .upath import check_storage_is_empty, create_path
|
|
91
99
|
from ._settings_storage import mark_storage_root
|
|
92
100
|
|
|
93
101
|
if isinstance(identifier, UUID):
|
|
@@ -103,19 +111,22 @@ def delete_instance(identifier: Union[UUID, str]):
|
|
|
103
111
|
name=name,
|
|
104
112
|
)
|
|
105
113
|
|
|
106
|
-
if instance_with_storage is
|
|
114
|
+
if instance_with_storage is None:
|
|
115
|
+
logger.warning("instance not found")
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
if require_empty:
|
|
107
119
|
root_string = instance_with_storage["storage"]["root"]
|
|
108
120
|
# gate storage and instance deletion on empty storage location for
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
delete_storage_record(UUID(instance_with_storage["storage_id"]))
|
|
121
|
+
root_path = create_path(root_string)
|
|
122
|
+
mark_storage_root(root_path) # address permission error
|
|
123
|
+
account_for_sqlite_file = instance_with_storage["db_scheme"] is None
|
|
124
|
+
check_storage_is_empty(
|
|
125
|
+
root_path, account_for_sqlite_file=account_for_sqlite_file
|
|
126
|
+
)
|
|
127
|
+
delete_instance_record(UUID(instance_with_storage["id"]))
|
|
128
|
+
delete_storage_record(UUID(instance_with_storage["storage_id"]))
|
|
129
|
+
return None
|
|
119
130
|
|
|
120
131
|
|
|
121
132
|
def delete_instance_record(
|
|
@@ -140,9 +151,9 @@ def _init_instance(isettings: InstanceSettings, client: Client) -> None:
|
|
|
140
151
|
lamindb_version = None
|
|
141
152
|
fields = dict(
|
|
142
153
|
id=isettings.id.hex,
|
|
143
|
-
account_id=settings.user.
|
|
154
|
+
account_id=settings.user._uuid.hex, # type: ignore
|
|
144
155
|
name=isettings.name,
|
|
145
|
-
storage_id=isettings.storage.
|
|
156
|
+
storage_id=isettings.storage._uuid.hex, # type: ignore
|
|
146
157
|
schema_str=isettings._schema_str,
|
|
147
158
|
lamindb_version=lamindb_version,
|
|
148
159
|
public=False,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
import shutil
|
|
5
|
+
from .upath import LocalPathClasses, convert_pathlike
|
|
3
6
|
from pathlib import Path
|
|
4
7
|
from typing import Literal, Optional, Set, Tuple
|
|
5
8
|
from uuid import UUID
|
|
@@ -16,6 +19,11 @@ from ._settings_storage import StorageSettings
|
|
|
16
19
|
from ._settings_store import current_instance_settings_file, instance_settings_file
|
|
17
20
|
from .upath import UPath
|
|
18
21
|
|
|
22
|
+
LOCAL_STORAGE_ROOT_WARNING = (
|
|
23
|
+
"No local storage root found, set via `ln.setup.settings.instance.local_storage ="
|
|
24
|
+
" local_root`"
|
|
25
|
+
)
|
|
26
|
+
|
|
19
27
|
|
|
20
28
|
def sanitize_git_repo_url(repo_url: str) -> str:
|
|
21
29
|
assert repo_url.startswith("https://")
|
|
@@ -31,6 +39,7 @@ class InstanceSettings:
|
|
|
31
39
|
owner: str, # owner handle
|
|
32
40
|
name: str, # instance name
|
|
33
41
|
storage: StorageSettings, # storage location
|
|
42
|
+
local_storage: bool = False, # default to local storage
|
|
34
43
|
uid: Optional[str] = None, # instance uid/lnid
|
|
35
44
|
db: Optional[str] = None, # DB URI
|
|
36
45
|
schema: Optional[str] = None, # comma-separated string of schema names
|
|
@@ -47,6 +56,9 @@ class InstanceSettings:
|
|
|
47
56
|
self._db: Optional[str] = db
|
|
48
57
|
self._schema_str: Optional[str] = schema
|
|
49
58
|
self._git_repo = None if git_repo is None else sanitize_git_repo_url(git_repo)
|
|
59
|
+
# local storage
|
|
60
|
+
self._local_storage_on = local_storage
|
|
61
|
+
self._local_storage = None
|
|
50
62
|
|
|
51
63
|
def __repr__(self):
|
|
52
64
|
"""Rich string representation."""
|
|
@@ -85,14 +97,60 @@ class InstanceSettings:
|
|
|
85
97
|
"""Instance name."""
|
|
86
98
|
return self._name
|
|
87
99
|
|
|
100
|
+
def _search_local_root(self):
|
|
101
|
+
from lnschema_core.models import Storage
|
|
102
|
+
|
|
103
|
+
records = Storage.objects.filter(type="local").all()
|
|
104
|
+
for record in records:
|
|
105
|
+
if Path(record.root).exists():
|
|
106
|
+
self._local_storage = StorageSettings(record.root)
|
|
107
|
+
logger.important(f"defaulting to local storage: {record}")
|
|
108
|
+
break
|
|
109
|
+
if self._local_storage is None:
|
|
110
|
+
logger.warning(LOCAL_STORAGE_ROOT_WARNING)
|
|
111
|
+
|
|
88
112
|
@property
|
|
89
|
-
def
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
def local_storage(self) -> StorageSettings:
|
|
114
|
+
"""Default local storage.
|
|
115
|
+
|
|
116
|
+
Warning: Only enable if you're sure you want to use the more complicated
|
|
117
|
+
storage mode across local & cloud locations.
|
|
118
|
+
|
|
119
|
+
As an admin, enable via: `ln.setup.settings.instance.local_storage =
|
|
120
|
+
local_root`.
|
|
121
|
+
|
|
122
|
+
If enabled, you'll save artifacts to a default local storage
|
|
123
|
+
location at :attr:`~lamindb.setup.settings.InstanceSettings.local_storage`.
|
|
124
|
+
|
|
125
|
+
Upon passing `upload=True` in `artifact.save(upload=True)`, you upload the
|
|
126
|
+
artifact to the default cloud storage location:
|
|
127
|
+
:attr:`~lamindb.setup.core.InstanceSettings.storage`.
|
|
128
|
+
"""
|
|
129
|
+
if not self._local_storage_on:
|
|
130
|
+
raise ValueError("Local storage is not enabled for this instance.")
|
|
131
|
+
if self._local_storage is None:
|
|
132
|
+
self._search_local_root()
|
|
133
|
+
if self._local_storage is None:
|
|
134
|
+
raise ValueError(LOCAL_STORAGE_ROOT_WARNING)
|
|
135
|
+
return self._local_storage
|
|
136
|
+
|
|
137
|
+
@local_storage.setter
|
|
138
|
+
def local_storage(self, local_root: Path):
|
|
139
|
+
from ._hub_core import update_instance_record
|
|
140
|
+
from .._init_instance import register_storage
|
|
141
|
+
|
|
142
|
+
self._search_local_root()
|
|
143
|
+
if self._local_storage is not None:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"You already configured a local storage root for this instance in this"
|
|
146
|
+
f" environment: {self.local_storage.root}"
|
|
147
|
+
)
|
|
148
|
+
local_root = convert_pathlike(local_root)
|
|
149
|
+
assert isinstance(local_root, LocalPathClasses)
|
|
150
|
+
self._local_storage = StorageSettings(local_root) # type: ignore
|
|
151
|
+
register_storage(self._local_storage) # type: ignore
|
|
152
|
+
self._local_storage_on = True
|
|
153
|
+
update_instance_record(self.id, {"storage_mode": "hybrid"})
|
|
96
154
|
|
|
97
155
|
@property
|
|
98
156
|
def slug(self) -> str:
|
|
@@ -211,16 +269,12 @@ class InstanceSettings:
|
|
|
211
269
|
assert self._db.startswith("postgresql"), f"Unexpected DB value: {self._db}"
|
|
212
270
|
return "postgresql"
|
|
213
271
|
|
|
214
|
-
@property
|
|
215
|
-
def session(self):
|
|
216
|
-
raise NotImplementedError
|
|
217
|
-
|
|
218
272
|
@property
|
|
219
273
|
def _is_cloud_sqlite(self) -> bool:
|
|
220
274
|
# can we make this a private property, Sergei?
|
|
221
275
|
# as it's not relevant to the user
|
|
222
276
|
"""Is this a cloud instance with sqlite db."""
|
|
223
|
-
return self.dialect == "sqlite" and self.storage.
|
|
277
|
+
return self.dialect == "sqlite" and self.storage.type_is_cloud
|
|
224
278
|
|
|
225
279
|
@property
|
|
226
280
|
def _cloud_sqlite_locker(self):
|
|
@@ -244,7 +298,7 @@ class InstanceSettings:
|
|
|
244
298
|
@property
|
|
245
299
|
def is_remote(self) -> bool:
|
|
246
300
|
"""Boolean indicating if an instance has no local component."""
|
|
247
|
-
if not self.storage.
|
|
301
|
+
if not self.storage.type_is_cloud:
|
|
248
302
|
return False
|
|
249
303
|
|
|
250
304
|
def is_local_uri(uri: str):
|
|
@@ -95,5 +95,5 @@ def setup_user_from_store(store: UserSettingsStore) -> UserSettings:
|
|
|
95
95
|
settings.uid = store.uid
|
|
96
96
|
settings.handle = store.handle if store.handle != "null" else "anonymous"
|
|
97
97
|
settings.name = store.name if store.name != "null" else None
|
|
98
|
-
settings.
|
|
98
|
+
settings._uuid = UUID(store.uuid) if store.uuid != "null" else None
|
|
99
99
|
return settings
|
|
@@ -44,7 +44,7 @@ def save_settings(
|
|
|
44
44
|
elif store_key == "storage_region":
|
|
45
45
|
value = settings.storage.region
|
|
46
46
|
else:
|
|
47
|
-
if store_key in {"db", "schema_str", "name_"}:
|
|
47
|
+
if store_key in {"db", "schema_str", "name_", "uuid"}:
|
|
48
48
|
settings_key = f"_{store_key.rstrip('_')}"
|
|
49
49
|
else:
|
|
50
50
|
settings_key = store_key
|
|
@@ -56,8 +56,9 @@ def get_storage_region(storage_root: UPathStr) -> Optional[str]:
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def mark_storage_root(root: UPathStr):
|
|
59
|
-
# we need to touch a 0-byte object in
|
|
59
|
+
# we need to touch a 0-byte object in folder-like storage location on S3 to avoid
|
|
60
60
|
# permission errors from leveraging s3fs on an empty hosted storage location
|
|
61
|
+
# for consistency, we write this file everywhere
|
|
61
62
|
root_upath = convert_pathlike(root)
|
|
62
63
|
mark_upath = root_upath / IS_INITIALIZED_KEY
|
|
63
64
|
mark_upath.touch()
|
|
@@ -92,14 +93,13 @@ def init_storage(root: UPathStr) -> "StorageSettings":
|
|
|
92
93
|
logger.error("`storage` is not a valid local, GCP storage or AWS S3 path")
|
|
93
94
|
raise e
|
|
94
95
|
ssettings = StorageSettings(uid=uid, root=root_str, region=region)
|
|
95
|
-
if ssettings.
|
|
96
|
+
if ssettings.type_is_cloud:
|
|
96
97
|
from ._hub_core import init_storage as init_storage_hub
|
|
97
98
|
|
|
98
99
|
ssettings._description = f"Created as default storage for instance {uid}"
|
|
99
|
-
ssettings.
|
|
100
|
+
ssettings._uuid_ = init_storage_hub(ssettings)
|
|
100
101
|
logger.important(f"registered storage: {ssettings.root_as_str}")
|
|
101
|
-
|
|
102
|
-
mark_storage_root(ssettings.root)
|
|
102
|
+
mark_storage_root(ssettings.root)
|
|
103
103
|
return ssettings
|
|
104
104
|
|
|
105
105
|
|
|
@@ -121,20 +121,17 @@ class StorageSettings:
|
|
|
121
121
|
self,
|
|
122
122
|
root: UPathStr,
|
|
123
123
|
region: Optional[str] = None,
|
|
124
|
-
is_hybrid: bool = False, # refers to storage mode
|
|
125
124
|
uid: Optional[str] = None,
|
|
126
125
|
uuid: Optional[UUID] = None,
|
|
127
126
|
access_token: Optional[str] = None,
|
|
128
127
|
):
|
|
129
128
|
self._uid = uid
|
|
130
|
-
self.
|
|
131
|
-
self._is_hybrid = is_hybrid
|
|
129
|
+
self._uuid_ = uuid
|
|
132
130
|
self._root_init = convert_pathlike(root)
|
|
133
131
|
if isinstance(self._root_init, LocalPathClasses): # local paths
|
|
134
|
-
self._root_init.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
(self._root_init / ".lamindb").mkdir(parents=True, exist_ok=True)
|
|
135
133
|
self._root_init = self._root_init.resolve()
|
|
136
134
|
self._root = None
|
|
137
|
-
self._remote_root = None
|
|
138
135
|
self._aws_account_id: Optional[int] = None
|
|
139
136
|
self._description: Optional[str] = None
|
|
140
137
|
# we don't yet infer region here to make init fast
|
|
@@ -155,15 +152,19 @@ class StorageSettings:
|
|
|
155
152
|
# save access_token here for use in self.root
|
|
156
153
|
self.access_token = access_token
|
|
157
154
|
|
|
155
|
+
# local storage
|
|
156
|
+
self._has_local = False
|
|
157
|
+
self._local = None
|
|
158
|
+
|
|
158
159
|
@property
|
|
159
160
|
def id(self) -> int:
|
|
160
161
|
"""Storage id."""
|
|
161
162
|
return self.record.id
|
|
162
163
|
|
|
163
164
|
@property
|
|
164
|
-
def
|
|
165
|
-
"""
|
|
166
|
-
return self.
|
|
165
|
+
def _uuid(self) -> Optional[UUID]:
|
|
166
|
+
"""Lamin's internal storage uuid."""
|
|
167
|
+
return self._uuid_
|
|
167
168
|
|
|
168
169
|
@property
|
|
169
170
|
def uid(self) -> Optional[str]:
|
|
@@ -172,6 +173,10 @@ class StorageSettings:
|
|
|
172
173
|
self._uid = self.record.uid
|
|
173
174
|
return self._uid
|
|
174
175
|
|
|
176
|
+
@property
|
|
177
|
+
def _mark_storage_root(self) -> UPath:
|
|
178
|
+
return self.root / IS_INITIALIZED_KEY
|
|
179
|
+
|
|
175
180
|
@property
|
|
176
181
|
def record(self) -> Any:
|
|
177
182
|
"""Storage record."""
|
|
@@ -180,63 +185,26 @@ class StorageSettings:
|
|
|
180
185
|
from lnschema_core.models import Storage
|
|
181
186
|
from ._settings import settings
|
|
182
187
|
|
|
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
|
|
188
|
+
self._record = Storage.objects.using(settings._using_key).get(
|
|
189
|
+
root=self.root_as_str
|
|
190
|
+
)
|
|
195
191
|
return self._record
|
|
196
192
|
|
|
197
193
|
def __repr__(self):
|
|
198
194
|
"""String rep."""
|
|
199
195
|
s = f"root='{self.root_as_str}', uid='{self.uid}'"
|
|
200
|
-
if self.
|
|
201
|
-
s += f", uuid='{self.
|
|
196
|
+
if self._uuid is not None:
|
|
197
|
+
s += f", uuid='{self._uuid.hex}'"
|
|
202
198
|
return f"StorageSettings({s})"
|
|
203
199
|
|
|
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
200
|
@property
|
|
217
201
|
def root(self) -> UPath:
|
|
218
202
|
"""Root storage location."""
|
|
219
203
|
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
|
|
204
|
+
# below makes network requests to get credentials
|
|
205
|
+
self._root = create_path(self._root_init, access_token=self.access_token)
|
|
227
206
|
return self._root
|
|
228
207
|
|
|
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
208
|
def _set_fs_kwargs(self, **kwargs):
|
|
241
209
|
"""Set additional fsspec arguments for cloud root.
|
|
242
210
|
|
|
@@ -299,7 +267,7 @@ class StorageSettings:
|
|
|
299
267
|
raise e
|
|
300
268
|
|
|
301
269
|
@property
|
|
302
|
-
def
|
|
270
|
+
def type_is_cloud(self) -> bool:
|
|
303
271
|
"""`True` if `storage_root` is in cloud, `False` otherwise."""
|
|
304
272
|
return self.type != "local"
|
|
305
273
|
|
lamindb_setup/core/django.py
CHANGED
lamindb_setup/core/upath.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# we are not documenting UPath here because it's documented at lamindb.UPath
|
|
2
2
|
"""Paths & file systems."""
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
import os
|
|
5
7
|
from datetime import datetime, timezone
|
|
6
8
|
import botocore.session
|
|
@@ -165,7 +167,7 @@ class ProgressCallback(fsspec.callbacks.Callback):
|
|
|
165
167
|
return None
|
|
166
168
|
|
|
167
169
|
|
|
168
|
-
def download_to(self, path, print_progress: bool = False, **kwargs):
|
|
170
|
+
def download_to(self, path: UPathStr, print_progress: bool = False, **kwargs):
|
|
169
171
|
"""Download to a path."""
|
|
170
172
|
if print_progress:
|
|
171
173
|
# can't do path.is_dir() because path doesn't exist
|
|
@@ -180,20 +182,38 @@ def download_to(self, path, print_progress: bool = False, **kwargs):
|
|
|
180
182
|
self.fs.download(str(self), str(path), **kwargs)
|
|
181
183
|
|
|
182
184
|
|
|
183
|
-
def upload_from(
|
|
185
|
+
def upload_from(
|
|
186
|
+
self,
|
|
187
|
+
path: UPathStr,
|
|
188
|
+
dir_inplace: bool = False,
|
|
189
|
+
print_progress: bool = False,
|
|
190
|
+
**kwargs,
|
|
191
|
+
):
|
|
184
192
|
"""Upload from a local path."""
|
|
193
|
+
path_is_dir = os.path.isdir(path)
|
|
194
|
+
if not path_is_dir:
|
|
195
|
+
dir_inplace = False
|
|
196
|
+
|
|
185
197
|
if print_progress:
|
|
186
|
-
if not
|
|
198
|
+
if not path_is_dir:
|
|
187
199
|
cb = ProgressCallback("uploading")
|
|
188
200
|
else:
|
|
189
201
|
# todo: make proper progress bar for directories
|
|
190
202
|
cb = fsspec.callbacks.NoOpCallback()
|
|
191
203
|
kwargs["callback"] = cb
|
|
204
|
+
|
|
205
|
+
if dir_inplace:
|
|
206
|
+
path = Path(path)
|
|
207
|
+
source = [f for f in path.rglob("*") if f.is_file()]
|
|
208
|
+
destination = [str(self / f.relative_to(path)) for f in source]
|
|
209
|
+
source = [str(f) for f in source] # type: ignore
|
|
210
|
+
else:
|
|
211
|
+
source = str(path) # type: ignore
|
|
212
|
+
destination = str(self) # type: ignore
|
|
192
213
|
# this weird thing is to avoid s3fs triggering create_bucket in upload
|
|
193
214
|
# if dirs are present
|
|
194
215
|
# it allows to avoid permission error
|
|
195
|
-
|
|
196
|
-
if self.protocol != "s3" or os.path.isfile(path):
|
|
216
|
+
if self.protocol != "s3" or not path_is_dir or dir_inplace:
|
|
197
217
|
cleanup_cache = False
|
|
198
218
|
else:
|
|
199
219
|
bucket = self._url.netloc
|
|
@@ -205,7 +225,7 @@ def upload_from(self, path, print_progress: bool = False, **kwargs):
|
|
|
205
225
|
else:
|
|
206
226
|
cleanup_cache = False
|
|
207
227
|
|
|
208
|
-
self.fs.upload(
|
|
228
|
+
self.fs.upload(source, destination, **kwargs)
|
|
209
229
|
|
|
210
230
|
if cleanup_cache:
|
|
211
231
|
# normally this is invalidated after the upload but still better to check
|
|
@@ -670,20 +690,34 @@ class InstanceNotEmpty(Exception):
|
|
|
670
690
|
pass
|
|
671
691
|
|
|
672
692
|
|
|
673
|
-
|
|
693
|
+
# is as fast as boto3: https://lamin.ai/laminlabs/lamindata/transform/krGp3hT1f78N5zKv
|
|
694
|
+
def check_storage_is_empty(
|
|
695
|
+
root: UPathStr, *, raise_error: bool = True, account_for_sqlite_file: bool = False
|
|
696
|
+
) -> int:
|
|
674
697
|
root_upath = convert_pathlike(root)
|
|
675
698
|
root_string = root_upath.as_posix() # type: ignore
|
|
676
699
|
# we currently touch a 0-byte file in the root of a hosted storage location
|
|
677
700
|
# ({storage_root}/.lamindb/_is_initialized) during storage initialization
|
|
678
701
|
# since path.fs.find raises a PermissionError on empty hosted
|
|
679
702
|
# subdirectories (see lamindb_setup/core/_settings_storage/init_storage).
|
|
680
|
-
|
|
681
|
-
if
|
|
682
|
-
|
|
703
|
+
n_offset_objects = 1 # because of touched dummy file, see mark_storage_root()
|
|
704
|
+
if account_for_sqlite_file:
|
|
705
|
+
n_offset_objects += 1 # because of SQLite file
|
|
683
706
|
objects = root_upath.fs.find(root_string)
|
|
684
707
|
n_objects = len(objects)
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
708
|
+
n_diff = n_objects - n_offset_objects
|
|
709
|
+
ask_for_deletion = (
|
|
710
|
+
"delete them prior to deleting the instance"
|
|
711
|
+
if raise_error
|
|
712
|
+
else "consider deleting them"
|
|
713
|
+
)
|
|
714
|
+
message = (
|
|
715
|
+
f"Storage location contains {n_objects} objects "
|
|
716
|
+
f"({n_offset_objects} ignored) - {ask_for_deletion}\n{objects}"
|
|
717
|
+
)
|
|
718
|
+
if n_diff > 0:
|
|
719
|
+
if raise_error:
|
|
720
|
+
raise InstanceNotEmpty(message)
|
|
721
|
+
else:
|
|
722
|
+
logger.warning(message)
|
|
723
|
+
return n_diff
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
lamindb_setup/__init__.py,sha256=
|
|
1
|
+
lamindb_setup/__init__.py,sha256=yTrf2HNpLb9hFr44mDAEjKjXDwrKYs_n2wX6JmyjOkQ,1558
|
|
2
2
|
lamindb_setup/_add_remote_storage.py,sha256=S2rFvn-JcGaBOiBNQoAiaTPM590GQh-g4OJeNsben0Q,1802
|
|
3
3
|
lamindb_setup/_cache.py,sha256=wrwlHi2IfxcWDfOW-cHzzkQNlLvasoQPPNxihICbwew,809
|
|
4
4
|
lamindb_setup/_check.py,sha256=xgc5kF2HA4lGuAA9FJT9BJyQV5z1EwYMr92RTbGcQg4,92
|
|
5
5
|
lamindb_setup/_check_setup.py,sha256=LLdTn4JYiyWKji_sexq2eENbmvA6_gdxqMKjDnF-nJc,2543
|
|
6
6
|
lamindb_setup/_close.py,sha256=XtP7uyKRbwCGkXdSkwuiEnLYn5PEN3t3q50dPHNJRSU,1150
|
|
7
|
-
lamindb_setup/_connect_instance.py,sha256=
|
|
8
|
-
lamindb_setup/_delete.py,sha256=
|
|
7
|
+
lamindb_setup/_connect_instance.py,sha256=XzHbYPmzPmAJZ1EaFtDBzw2skh1s0tppKCg2HOxx1HQ,11908
|
|
8
|
+
lamindb_setup/_delete.py,sha256=aIfQpOmb-icGsNVOd9EhHGGBQvsSJF7fBW_fzSwPhuM,5861
|
|
9
9
|
lamindb_setup/_django.py,sha256=Yv-bNj80I5Fj9AJ2BFgcv9JZg4WUrlh3UStty82_jXQ,1500
|
|
10
10
|
lamindb_setup/_exportdb.py,sha256=f5XUhRxTsU-yqfPM92OxjZKRxJo3lKaqYTRh9oytDfY,2084
|
|
11
11
|
lamindb_setup/_importdb.py,sha256=bxFZNjtVOAW4yeMQakVYZE4-WNi4QE-MdMynHShMOgI,1836
|
|
12
|
-
lamindb_setup/_init_instance.py,sha256=
|
|
13
|
-
lamindb_setup/_migrate.py,sha256=
|
|
14
|
-
lamindb_setup/_register_instance.py,sha256=
|
|
12
|
+
lamindb_setup/_init_instance.py,sha256=DlVHpz8avTYaL-IoxSO4XM1rfZmtZXsWpjtY95vYmK0,11480
|
|
13
|
+
lamindb_setup/_migrate.py,sha256=wVDfIN1z2OIJztrenpYx42XN09retbPek49TzzmFQ6I,8810
|
|
14
|
+
lamindb_setup/_register_instance.py,sha256=1cZXXuCf6cqbiDchmW5vYNTf34wMNSZ2QtHmIJbTT4c,796
|
|
15
15
|
lamindb_setup/_schema.py,sha256=edhbiUYatbO5FfBA6PYRmVmYWbtBXZBpATa70_MZiWo,642
|
|
16
|
-
lamindb_setup/_setup_user.py,sha256=
|
|
16
|
+
lamindb_setup/_setup_user.py,sha256=5zZ924ckd8CGBiD4-5bdTN-NUNMS7N5w8Ovy6JIYPMU,3658
|
|
17
17
|
lamindb_setup/_silence_loggers.py,sha256=auXnYVSx2ErZmyXZJ7FYYA69UQgUDQ5SnuznFXzfaWs,1548
|
|
18
18
|
lamindb_setup/core/__init__.py,sha256=xfhu3cO_zPtNztmu6vlWtdbX92gPpnjyff5dOqCHTvE,369
|
|
19
19
|
lamindb_setup/core/_aws_storage.py,sha256=GIT9SagV-1oFlLpRIogFgXuTwzMZsppFjqKA4-Vp66s,1762
|
|
20
20
|
lamindb_setup/core/_deprecated.py,sha256=DNO_90uyYGM9on7rzJCu9atS7Z1qOQypsuL9PI6I-OQ,2491
|
|
21
21
|
lamindb_setup/core/_docs.py,sha256=0ha3cZqzvIQnAXVC_JlAiRrFi3VpEEVookP_oURhBr4,240
|
|
22
22
|
lamindb_setup/core/_hub_client.py,sha256=H09wJRck4d3fQ8VBSK1v8Dbj9RfqLdW9UqoTs6OCyUM,5520
|
|
23
|
-
lamindb_setup/core/_hub_core.py,sha256=
|
|
23
|
+
lamindb_setup/core/_hub_core.py,sha256=Y_RnT82Y7fHgMmNvG8NTDTnx5Ybf_IwFQSMl6AFveVg,11294
|
|
24
24
|
lamindb_setup/core/_hub_crud.py,sha256=0qKKsnHlx_FceX-jahdS2vJc2IuGRzouQrtAMn6MTSA,4505
|
|
25
25
|
lamindb_setup/core/_hub_utils.py,sha256=4bjtm4QVivDwtVq3Bx9hQFZtdU2WmqrVdpupdvxMHcM,1862
|
|
26
26
|
lamindb_setup/core/_settings.py,sha256=ogeOddaJ9tiolzpGwUIu6i4vhcViXQvsREEzZ95uyvI,3241
|
|
27
|
-
lamindb_setup/core/_settings_instance.py,sha256=
|
|
28
|
-
lamindb_setup/core/_settings_load.py,sha256=
|
|
29
|
-
lamindb_setup/core/_settings_save.py,sha256=
|
|
30
|
-
lamindb_setup/core/_settings_storage.py,sha256=
|
|
27
|
+
lamindb_setup/core/_settings_instance.py,sha256=nAaTW4kd_wYM5hKlRwpLgBd-ATiEDvtHOX1NCnpkFyQ,13927
|
|
28
|
+
lamindb_setup/core/_settings_load.py,sha256=RkH_zfdGAlBO-4sovmFNGSP56XhSJpUYdvlZlNh-Qqs,3745
|
|
29
|
+
lamindb_setup/core/_settings_save.py,sha256=QnUQsYMAXv9xUYAsQ-5vldntSnAvNb3TMmJ61J3kU_I,2571
|
|
30
|
+
lamindb_setup/core/_settings_storage.py,sha256=Y2FsDmOFW7Kz1bh2MAMyQkWPJjoYN12ObpqVp82qRag,11750
|
|
31
31
|
lamindb_setup/core/_settings_store.py,sha256=iM3Ei-YTSQo6wbcUhOliQx_KmHo8sxDx0-elJPt9AwQ,1929
|
|
32
|
-
lamindb_setup/core/_settings_user.py,sha256=
|
|
32
|
+
lamindb_setup/core/_settings_user.py,sha256=ouRhbnIN12cjvVR0Iz98hUyYyGbqcGNRb1OCeI2T6wo,1282
|
|
33
33
|
lamindb_setup/core/_setup_bionty_sources.py,sha256=xhIIJqtCzrjTONzU_68bq5kQxsNRDfAprHi2oJvY_LY,2908
|
|
34
34
|
lamindb_setup/core/cloud_sqlite_locker.py,sha256=nFwAupnbIy_sxzlJBayfD2Dh8ZMUGNxbspIbv6BGrp8,6835
|
|
35
|
-
lamindb_setup/core/django.py,sha256=
|
|
35
|
+
lamindb_setup/core/django.py,sha256=bKwuv83ubKVsMeunnFtpWd1C-y29I2cuvpW10rCPpZo,3411
|
|
36
36
|
lamindb_setup/core/exceptions.py,sha256=YzkJA51ZAa2w9lawAggSdg-NA-EIYJGNRnFd0DN3_bE,275
|
|
37
37
|
lamindb_setup/core/hashing.py,sha256=nbQO_CSiX09O4bznHABft94fdFZAHPm2NIyvH5xagoo,2011
|
|
38
38
|
lamindb_setup/core/types.py,sha256=fR71SLjoN1MkCjx8TJcjYDgmgO4bPXBX5J_RKpmmy_o,497
|
|
39
|
-
lamindb_setup/core/upath.py,sha256=
|
|
40
|
-
lamindb_setup-0.
|
|
41
|
-
lamindb_setup-0.
|
|
42
|
-
lamindb_setup-0.
|
|
43
|
-
lamindb_setup-0.
|
|
39
|
+
lamindb_setup/core/upath.py,sha256=v8lPEA5j_SH0QzrHfw02x3HDt1ikFVc3gdoCCVfwfmw,25078
|
|
40
|
+
lamindb_setup-0.70.0.dist-info/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
|
|
41
|
+
lamindb_setup-0.70.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
|
42
|
+
lamindb_setup-0.70.0.dist-info/METADATA,sha256=9hjTjaw1xv6SQR_ugZg7DMKxGu0SqgJy0b9VJuR2rVw,1469
|
|
43
|
+
lamindb_setup-0.70.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|