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
lamindb_setup/__init__.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"""Setup & configure LaminDB.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Typically, you'll want to use the CLI rather than this API.
|
|
3
|
+
Many functions in this "setup API" have a matching command in the :doc:`docs:cli` CLI.
|
|
6
4
|
|
|
7
5
|
Guide: :doc:`docs:setup`.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
Basic operations:
|
|
10
8
|
|
|
11
9
|
.. autosummary::
|
|
12
10
|
:toctree:
|
|
@@ -17,7 +15,7 @@ Setup:
|
|
|
17
15
|
close
|
|
18
16
|
delete
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
Instance operations:
|
|
21
19
|
|
|
22
20
|
.. autosummary::
|
|
23
21
|
:toctree:
|
|
@@ -36,22 +34,22 @@ Modules & settings:
|
|
|
36
34
|
|
|
37
35
|
"""
|
|
38
36
|
|
|
39
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.71.0" # denote a release candidate for 0.1.0 with 0.1rc1
|
|
40
38
|
|
|
41
39
|
import sys
|
|
42
40
|
from os import name as _os_name
|
|
43
41
|
|
|
44
42
|
from . import core
|
|
45
|
-
from ._close import close # noqa
|
|
46
|
-
from ._delete import delete # noqa
|
|
47
|
-
from ._init_instance import init # noqa
|
|
48
|
-
from ._connect_instance import connect, load # noqa
|
|
49
|
-
from ._migrate import migrate
|
|
50
|
-
from ._register_instance import register # noqa
|
|
51
|
-
from .core._settings import settings # noqa
|
|
52
|
-
from ._setup_user import login, logout # noqa
|
|
53
|
-
from ._django import django
|
|
54
43
|
from ._check_setup import _check_instance_setup
|
|
44
|
+
from ._close import close
|
|
45
|
+
from ._connect_instance import connect, load
|
|
46
|
+
from ._delete import delete
|
|
47
|
+
from ._django import django
|
|
48
|
+
from ._init_instance import init
|
|
49
|
+
from ._migrate import migrate
|
|
50
|
+
from ._register_instance import register
|
|
51
|
+
from ._setup_user import login, logout
|
|
52
|
+
from .core._settings import settings
|
|
55
53
|
|
|
56
54
|
dev = core # backward compat
|
|
57
55
|
_TESTING = False # used in lamindb tests
|
|
@@ -70,3 +68,5 @@ if _os_name == "nt":
|
|
|
70
68
|
_original_excepthook(args)
|
|
71
69
|
|
|
72
70
|
threading.excepthook = _except_hook
|
|
71
|
+
|
|
72
|
+
settings.__doc__ = """Global :class:`~lamindb.setup.core.SetupSettings`."""
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
|
-
from
|
|
5
|
+
from lamin_utils import logger
|
|
4
6
|
|
|
5
|
-
from ._init_instance import
|
|
7
|
+
from ._init_instance import register_storage_in_instance
|
|
6
8
|
from .core._settings import settings
|
|
7
|
-
from .core.
|
|
8
|
-
|
|
9
|
+
from .core._settings_storage import init_storage
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from lamindb_setup.core.types import UPathStr
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
def
|
|
15
|
+
def add_managed_storage(root: UPathStr, **fs_kwargs):
|
|
12
16
|
"""Add a remote default storage location to a local instance.
|
|
13
17
|
|
|
14
18
|
This can be used to selectively share data.
|
|
@@ -17,34 +21,19 @@ def switch_default_storage(root: UPathStr, **fs_kwargs):
|
|
|
17
21
|
root: `UPathStr` - The new storage root, e.g., an S3 bucket.
|
|
18
22
|
**fs_kwargs: Additional fsspec arguments for cloud root, e.g., profile.
|
|
19
23
|
|
|
20
|
-
Example:
|
|
21
|
-
>>> ln.setup.set.storage(
|
|
22
|
-
>>> "s3://some-bucket",
|
|
23
|
-
>>> profile="some_profile", # fsspec arg
|
|
24
|
-
>>> cache_regions=True # fsspec arg for s3
|
|
25
|
-
>>> )
|
|
26
|
-
|
|
27
24
|
"""
|
|
28
25
|
if settings.instance.dialect == "sqlite":
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
id=settings.instance.id,
|
|
26
|
+
raise ValueError(
|
|
27
|
+
"Can't add additional managed storage locations for sqlite instances."
|
|
28
|
+
)
|
|
29
|
+
if not settings.instance.is_on_hub:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"Can't add additional managed storage locations for instances that aren't managed through the hub."
|
|
32
|
+
)
|
|
33
|
+
ssettings = init_storage(
|
|
34
|
+
root=root, instance_id=settings.instance._id, register_hub=True
|
|
39
35
|
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# we are not doing this for now because of difficulties to define the right RLS policy # noqa
|
|
44
|
-
# https://laminlabs.slack.com/archives/C04FPE8V01W/p1687948324601929?thread_ts=1687531921.394119&cid=C04FPE8V01W
|
|
45
|
-
# if settings.instance.is_remote:
|
|
46
|
-
# init_storage_hub(
|
|
47
|
-
# root, account_handle=settings.instance.owner # type: ignore
|
|
48
|
-
# )
|
|
49
|
-
|
|
36
|
+
settings.instance._storage = ssettings
|
|
37
|
+
settings.instance._persist() # this also updates the settings object
|
|
38
|
+
register_storage_in_instance(ssettings)
|
|
50
39
|
settings.storage._set_fs_kwargs(**fs_kwargs)
|
lamindb_setup/_cache.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import shutil
|
|
4
|
+
|
|
2
5
|
from lamin_utils import logger
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
def clear_cache_dir():
|
|
6
|
-
from lamindb_setup import
|
|
9
|
+
from lamindb_setup import close, settings
|
|
7
10
|
|
|
8
11
|
if settings.instance._is_cloud_sqlite:
|
|
9
12
|
logger.warning(
|
lamindb_setup/_check.py
CHANGED
lamindb_setup/_check_setup.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
import os
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from lamin_utils import logger
|
|
7
|
+
|
|
4
8
|
from ._silence_loggers import silence_loggers
|
|
9
|
+
from .core import django
|
|
10
|
+
from .core._settings import settings
|
|
5
11
|
from .core._settings_store import current_instance_settings_file
|
|
6
12
|
from .core.exceptions import DefaultMessageException
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from .core import
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .core._settings_instance import InstanceSettings
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
class InstanceNotSetupError(DefaultMessageException):
|
|
@@ -19,10 +25,10 @@ If you used the CLI to set up lamindb in a notebook, restart the Python session.
|
|
|
19
25
|
"""
|
|
20
26
|
|
|
21
27
|
|
|
22
|
-
CURRENT_ISETTINGS:
|
|
28
|
+
CURRENT_ISETTINGS: InstanceSettings | None = None
|
|
23
29
|
|
|
24
30
|
|
|
25
|
-
def _get_current_instance_settings() ->
|
|
31
|
+
def _get_current_instance_settings() -> InstanceSettings | None:
|
|
26
32
|
global CURRENT_ISETTINGS
|
|
27
33
|
|
|
28
34
|
if CURRENT_ISETTINGS is not None:
|
lamindb_setup/_close.py
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from uuid import UUID
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
4
3
|
import os
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
5
7
|
from lamin_utils import logger
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
+
|
|
9
|
+
from ._check_setup import _check_instance_setup
|
|
10
|
+
from ._close import close as close_instance
|
|
11
|
+
from ._init_instance import MESSAGE_NO_MULTIPLE_INSTANCE, load_from_isettings
|
|
12
|
+
from ._migrate import check_whether_migrations_in_sync
|
|
13
|
+
from ._silence_loggers import silence_loggers
|
|
14
|
+
from .core._hub_core import connect_instance as load_instance_from_hub
|
|
15
|
+
from .core._hub_utils import (
|
|
8
16
|
LaminDsn,
|
|
9
17
|
LaminDsnModel,
|
|
10
18
|
)
|
|
11
|
-
from .
|
|
12
|
-
from .
|
|
13
|
-
from .core._settings import InstanceSettings, settings
|
|
14
|
-
from ._silence_loggers import silence_loggers
|
|
19
|
+
from .core._settings import settings
|
|
20
|
+
from .core._settings_instance import InstanceSettings
|
|
15
21
|
from .core._settings_load import load_instance_settings
|
|
16
22
|
from .core._settings_storage import StorageSettings
|
|
17
23
|
from .core._settings_store import instance_settings_file
|
|
18
24
|
from .core.cloud_sqlite_locker import unlock_cloud_sqlite_upon_exception
|
|
19
|
-
from ._init_instance import MESSAGE_NO_MULTIPLE_INSTANCE
|
|
20
|
-
from ._check_setup import _check_instance_setup
|
|
21
|
-
from .core._hub_core import connect_instance as load_instance_from_hub
|
|
22
|
-
from ._migrate import check_whether_migrations_in_sync
|
|
23
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from .core.types import UPathStr
|
|
24
30
|
|
|
25
31
|
# this is for testing purposes only
|
|
26
32
|
# set to True only to test failed load
|
|
27
33
|
_TEST_FAILED_LOAD = False
|
|
28
34
|
|
|
29
35
|
|
|
36
|
+
INSTANCE_NOT_FOUND_MESSAGE = (
|
|
37
|
+
"'{owner}/{name}' not found:"
|
|
38
|
+
" '{hub_result}'\nCheck your permissions:"
|
|
39
|
+
" https://lamin.ai/{owner}/{name}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InstanceNotFoundError(SystemExit):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
30
47
|
def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
|
|
31
48
|
return (
|
|
32
49
|
db_dsn_hub.scheme == db_dsn_local.scheme
|
|
@@ -37,8 +54,8 @@ def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
|
|
|
37
54
|
|
|
38
55
|
|
|
39
56
|
def update_db_using_local(
|
|
40
|
-
hub_instance_result:
|
|
41
|
-
) ->
|
|
57
|
+
hub_instance_result: dict[str, str], settings_file: Path, db: str | None = None
|
|
58
|
+
) -> str | None:
|
|
42
59
|
db_updated = None
|
|
43
60
|
# check if postgres
|
|
44
61
|
if hub_instance_result["db_scheme"] == "postgresql":
|
|
@@ -87,11 +104,11 @@ def update_db_using_local(
|
|
|
87
104
|
def connect(
|
|
88
105
|
slug: str,
|
|
89
106
|
*,
|
|
90
|
-
db:
|
|
91
|
-
storage:
|
|
107
|
+
db: str | None = None,
|
|
108
|
+
storage: UPathStr | None = None,
|
|
92
109
|
_raise_not_reachable_error: bool = True,
|
|
93
110
|
_test: bool = False,
|
|
94
|
-
) ->
|
|
111
|
+
) -> str | tuple | None:
|
|
95
112
|
"""Connect to instance.
|
|
96
113
|
|
|
97
114
|
Args:
|
|
@@ -121,6 +138,8 @@ def connect(
|
|
|
121
138
|
make_hub_request = True
|
|
122
139
|
if settings_file.exists():
|
|
123
140
|
isettings = load_instance_settings(settings_file)
|
|
141
|
+
# mimic instance_result from hub
|
|
142
|
+
instance_result = {"id": isettings._id.hex}
|
|
124
143
|
# skip hub request for a purely local instance
|
|
125
144
|
make_hub_request = isettings.is_remote
|
|
126
145
|
|
|
@@ -139,7 +158,6 @@ def connect(
|
|
|
139
158
|
root=storage_result["root"],
|
|
140
159
|
region=storage_result["region"],
|
|
141
160
|
uid=storage_result["lnid"],
|
|
142
|
-
is_hybrid=instance_result["storage_mode"] == "hybrid",
|
|
143
161
|
)
|
|
144
162
|
isettings = InstanceSettings(
|
|
145
163
|
id=UUID(instance_result["id"]),
|
|
@@ -149,27 +167,25 @@ def connect(
|
|
|
149
167
|
db=db_updated,
|
|
150
168
|
schema=instance_result["schema_str"],
|
|
151
169
|
git_repo=instance_result["git_repo"],
|
|
170
|
+
keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
|
|
171
|
+
is_on_hub=True,
|
|
152
172
|
)
|
|
153
173
|
check_whether_migrations_in_sync(instance_result["lamindb_version"])
|
|
154
174
|
else:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
f" '{hub_result}'\nCheck your permissions:"
|
|
158
|
-
f" https://lamin.ai/{owner}/{name}?tab=collaborators"
|
|
175
|
+
message = INSTANCE_NOT_FOUND_MESSAGE.format(
|
|
176
|
+
owner=owner, name=name, hub_result=hub_result
|
|
159
177
|
)
|
|
160
178
|
if settings_file.exists():
|
|
161
179
|
isettings = load_instance_settings(settings_file)
|
|
162
180
|
if isettings.is_remote:
|
|
163
181
|
if _raise_not_reachable_error:
|
|
164
|
-
raise
|
|
165
|
-
return "instance-not-
|
|
182
|
+
raise InstanceNotFoundError(message)
|
|
183
|
+
return "instance-not-found"
|
|
166
184
|
|
|
167
185
|
else:
|
|
168
186
|
if _raise_not_reachable_error:
|
|
169
|
-
raise
|
|
170
|
-
return "instance-not-
|
|
171
|
-
# mimic instance_result from hub
|
|
172
|
-
instance_result = {"id": isettings.id.hex}
|
|
187
|
+
raise InstanceNotFoundError(message)
|
|
188
|
+
return "instance-not-found"
|
|
173
189
|
|
|
174
190
|
if storage is not None:
|
|
175
191
|
update_isettings_with_storage(isettings, storage)
|
|
@@ -194,7 +210,7 @@ def connect(
|
|
|
194
210
|
raise SystemExit(msg)
|
|
195
211
|
else:
|
|
196
212
|
logger.warning(
|
|
197
|
-
f"instance exists with id {isettings.
|
|
213
|
+
f"instance exists with id {isettings._id.hex}, but database is not"
|
|
198
214
|
" loadable: re-initializing"
|
|
199
215
|
)
|
|
200
216
|
return "instance-corrupted-or-deleted", instance_result
|
|
@@ -215,9 +231,9 @@ def connect(
|
|
|
215
231
|
def load(
|
|
216
232
|
slug: str,
|
|
217
233
|
*,
|
|
218
|
-
db:
|
|
219
|
-
storage:
|
|
220
|
-
) ->
|
|
234
|
+
db: str | None = None,
|
|
235
|
+
storage: UPathStr | None = None,
|
|
236
|
+
) -> str | tuple | None:
|
|
221
237
|
"""Connect to instance and set ``auto-connect`` to true.
|
|
222
238
|
|
|
223
239
|
This is exactly the same as ``ln.connect()`` except for that
|
|
@@ -255,7 +271,7 @@ def update_isettings_with_storage(
|
|
|
255
271
|
isettings: InstanceSettings, storage: UPathStr
|
|
256
272
|
) -> None:
|
|
257
273
|
ssettings = StorageSettings(storage)
|
|
258
|
-
if ssettings.
|
|
274
|
+
if ssettings.type_is_cloud:
|
|
259
275
|
try: # triggering ssettings.id makes a lookup in the storage table
|
|
260
276
|
logger.success(f"loaded storage: {ssettings.id} / {ssettings.root_as_str}")
|
|
261
277
|
except RuntimeError as e:
|
lamindb_setup/_delete.py
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import shutil
|
|
2
|
-
from
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
3
7
|
from lamin_utils import logger
|
|
4
|
-
|
|
5
|
-
from .
|
|
8
|
+
|
|
9
|
+
from ._connect_instance import INSTANCE_NOT_FOUND_MESSAGE
|
|
10
|
+
from .core._hub_core import connect_instance as load_instance_from_hub
|
|
11
|
+
from .core._hub_core import delete_instance as delete_instance_on_hub
|
|
12
|
+
from .core._hub_core import get_storage_records_for_instance
|
|
6
13
|
from .core._settings import settings
|
|
14
|
+
from .core._settings_instance import InstanceSettings
|
|
7
15
|
from .core._settings_load import load_instance_settings
|
|
16
|
+
from .core._settings_storage import StorageSettings
|
|
8
17
|
from .core._settings_store import instance_settings_file
|
|
18
|
+
from .core.upath import HOSTED_BUCKETS, check_storage_is_empty
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from pathlib import Path
|
|
9
22
|
|
|
10
23
|
|
|
11
24
|
def delete_cache(cache_dir: Path):
|
|
@@ -13,6 +26,12 @@ def delete_cache(cache_dir: Path):
|
|
|
13
26
|
shutil.rmtree(cache_dir)
|
|
14
27
|
|
|
15
28
|
|
|
29
|
+
def delete_exclusion_dir(isettings: InstanceSettings) -> None:
|
|
30
|
+
exclusion_dir = isettings.storage.root / f".lamindb/_exclusion/{isettings._id.hex}"
|
|
31
|
+
if exclusion_dir.exists():
|
|
32
|
+
exclusion_dir.rmdir()
|
|
33
|
+
|
|
34
|
+
|
|
16
35
|
def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
17
36
|
settings_file = isettings._get_settings_file()
|
|
18
37
|
if settings_file.exists():
|
|
@@ -22,20 +41,12 @@ def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
|
22
41
|
try:
|
|
23
42
|
if isettings._sqlite_file.exists():
|
|
24
43
|
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
44
|
except PermissionError:
|
|
31
45
|
logger.warning(
|
|
32
46
|
"Did not have permission to delete SQLite file:"
|
|
33
47
|
f" {isettings._sqlite_file}"
|
|
34
48
|
)
|
|
35
49
|
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
50
|
# unset the global instance settings
|
|
40
51
|
if settings._instance_exists and isettings.slug == settings.instance.slug:
|
|
41
52
|
if settings._instance_settings_path.exists():
|
|
@@ -43,8 +54,16 @@ def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
|
43
54
|
settings._instance_settings = None
|
|
44
55
|
|
|
45
56
|
|
|
46
|
-
def delete(
|
|
47
|
-
|
|
57
|
+
def delete(
|
|
58
|
+
instance_name: str, force: bool = False, require_empty: bool = True
|
|
59
|
+
) -> int | None:
|
|
60
|
+
"""Delete a LaminDB instance.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
instance_name (str): The name of the instance to delete.
|
|
64
|
+
force (bool): Whether to skip the confirmation prompt.
|
|
65
|
+
require_empty (bool): Whether to check if the instance is empty before deleting.
|
|
66
|
+
"""
|
|
48
67
|
if "/" in instance_name:
|
|
49
68
|
logger.warning(
|
|
50
69
|
"Deleting the instance of another user is currently not supported with the"
|
|
@@ -53,6 +72,48 @@ def delete(instance_name: str, force: bool = False) -> Optional[int]:
|
|
|
53
72
|
)
|
|
54
73
|
raise ValueError("Invalid instance name: '/' delimiter not allowed.")
|
|
55
74
|
instance_slug = f"{settings.user.handle}/{instance_name}"
|
|
75
|
+
if settings._instance_exists and settings.instance.name == instance_name:
|
|
76
|
+
isettings = settings.instance
|
|
77
|
+
else:
|
|
78
|
+
settings_file = instance_settings_file(instance_name, settings.user.handle)
|
|
79
|
+
if not settings_file.exists():
|
|
80
|
+
hub_result = load_instance_from_hub(
|
|
81
|
+
owner=settings.user.handle, name=instance_name
|
|
82
|
+
)
|
|
83
|
+
if isinstance(hub_result, str):
|
|
84
|
+
message = INSTANCE_NOT_FOUND_MESSAGE.format(
|
|
85
|
+
owner=settings.user.handle,
|
|
86
|
+
name=instance_name,
|
|
87
|
+
hub_result=hub_result,
|
|
88
|
+
)
|
|
89
|
+
logger.warning(message)
|
|
90
|
+
return None
|
|
91
|
+
instance_result, storage_result = hub_result
|
|
92
|
+
ssettings = StorageSettings(
|
|
93
|
+
root=storage_result["root"],
|
|
94
|
+
region=storage_result["region"],
|
|
95
|
+
uid=storage_result["lnid"],
|
|
96
|
+
)
|
|
97
|
+
isettings = InstanceSettings(
|
|
98
|
+
id=UUID(instance_result["id"]),
|
|
99
|
+
owner=settings.user.handle,
|
|
100
|
+
name=instance_name,
|
|
101
|
+
storage=ssettings,
|
|
102
|
+
keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
|
|
103
|
+
db=instance_result["db"] if "db" in instance_result else None,
|
|
104
|
+
schema=instance_result["schema_str"],
|
|
105
|
+
git_repo=instance_result["git_repo"],
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
isettings = load_instance_settings(settings_file)
|
|
109
|
+
if isettings.dialect != "sqlite":
|
|
110
|
+
logger.warning(
|
|
111
|
+
f"delete() does not yet affect your Postgres database at {isettings.db}"
|
|
112
|
+
)
|
|
113
|
+
if isettings.is_on_hub and settings.user.handle == "anonymous":
|
|
114
|
+
logger.warning(
|
|
115
|
+
"won't delete the hub component of this instance because you're not logged in"
|
|
116
|
+
)
|
|
56
117
|
if not force:
|
|
57
118
|
valid_responses = ["y", "yes"]
|
|
58
119
|
user_input = (
|
|
@@ -62,17 +123,55 @@ def delete(instance_name: str, force: bool = False) -> Optional[int]:
|
|
|
62
123
|
)
|
|
63
124
|
if user_input not in valid_responses:
|
|
64
125
|
return -1
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
126
|
+
|
|
127
|
+
# the actual deletion process begins here
|
|
128
|
+
if isettings.dialect == "sqlite" and isettings.is_remote:
|
|
129
|
+
# delete the exlusion dir first because it's hard to count its objects
|
|
130
|
+
delete_exclusion_dir(isettings)
|
|
131
|
+
if isettings.storage.type_is_cloud and isettings.storage.root_as_str.startswith(
|
|
132
|
+
HOSTED_BUCKETS
|
|
133
|
+
):
|
|
134
|
+
if not require_empty:
|
|
70
135
|
logger.warning(
|
|
71
|
-
"
|
|
72
|
-
|
|
136
|
+
"hosted storage always has to be empty, ignoring `require_empty`"
|
|
137
|
+
)
|
|
138
|
+
require_empty = True
|
|
139
|
+
# first the default storage
|
|
140
|
+
n_objects = check_storage_is_empty(
|
|
141
|
+
isettings.storage.root,
|
|
142
|
+
raise_error=require_empty,
|
|
143
|
+
account_for_sqlite_file=isettings.dialect == "sqlite",
|
|
144
|
+
)
|
|
145
|
+
if isettings.storage._mark_storage_root.exists():
|
|
146
|
+
isettings.storage._mark_storage_root.unlink(
|
|
147
|
+
missing_ok=True # this is totally weird, but needed on Py3.11
|
|
148
|
+
)
|
|
149
|
+
# now everything that's on the hub
|
|
150
|
+
if settings.user.handle != "anonymous":
|
|
151
|
+
storage_records = get_storage_records_for_instance(isettings._id)
|
|
152
|
+
for storage_record in storage_records:
|
|
153
|
+
if storage_record["root"] == isettings.storage.root_as_str:
|
|
154
|
+
continue
|
|
155
|
+
check_storage_is_empty(
|
|
156
|
+
storage_record["root"], # type: ignore
|
|
157
|
+
raise_error=require_empty,
|
|
73
158
|
)
|
|
74
|
-
|
|
75
|
-
|
|
159
|
+
ssettings = StorageSettings(storage_record["root"]) # type: ignore
|
|
160
|
+
if ssettings._mark_storage_root.exists():
|
|
161
|
+
ssettings._mark_storage_root.unlink(
|
|
162
|
+
missing_ok=True # this is totally weird, but needed on Py3.11
|
|
163
|
+
)
|
|
76
164
|
logger.info(f"deleting instance {instance_slug}")
|
|
165
|
+
# below we can skip check_storage_is_empty() because we already called
|
|
166
|
+
# it above
|
|
167
|
+
if settings.user.handle != "anonymous":
|
|
168
|
+
delete_instance_on_hub(isettings._id, require_empty=False)
|
|
77
169
|
delete_by_isettings(isettings)
|
|
170
|
+
# if .lndb file was delete, then we might count -1
|
|
171
|
+
if n_objects <= 0 and isettings.storage.type == "local":
|
|
172
|
+
# dir is only empty after sqlite file was delete via delete_by_isettings
|
|
173
|
+
if (isettings.storage.root / ".lamindb").exists():
|
|
174
|
+
(isettings.storage.root / ".lamindb").rmdir()
|
|
175
|
+
if isettings.storage.root.exists():
|
|
176
|
+
isettings.storage.root.rmdir()
|
|
78
177
|
return None
|
lamindb_setup/_django.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import Optional
|
|
4
|
+
|
|
2
5
|
from .core._settings import settings
|
|
3
6
|
from .core.django import setup_django
|
|
4
7
|
|
|
5
8
|
|
|
6
|
-
def django(command: str, package_name:
|
|
9
|
+
def django(command: str, package_name: str | None = None, **kwargs):
|
|
7
10
|
r"""Manage migrations.
|
|
8
11
|
|
|
9
12
|
Examples:
|
lamindb_setup/_exportdb.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from
|
|
2
|
-
from importlib import import_module
|
|
1
|
+
from __future__ import annotations
|
|
3
2
|
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
MODELS = {
|
|
6
7
|
"core": {
|
|
@@ -46,6 +47,7 @@ def exportdb() -> None:
|
|
|
46
47
|
directory = Path("./lamindb_export/")
|
|
47
48
|
directory.mkdir(parents=True, exist_ok=True)
|
|
48
49
|
import pandas as pd
|
|
50
|
+
|
|
49
51
|
import lamindb_setup as ln_setup
|
|
50
52
|
|
|
51
53
|
def export_registry(registry, directory):
|
lamindb_setup/_importdb.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
1
4
|
from pathlib import Path
|
|
5
|
+
|
|
2
6
|
from ._exportdb import MODELS
|
|
3
|
-
from importlib import import_module
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
def import_registry(registry, directory, connection):
|
|
@@ -26,6 +29,7 @@ def importdb() -> None:
|
|
|
26
29
|
if response != "y":
|
|
27
30
|
return None
|
|
28
31
|
from sqlalchemy import create_engine, text
|
|
32
|
+
|
|
29
33
|
import lamindb_setup as ln_setup
|
|
30
34
|
|
|
31
35
|
engine = create_engine(ln_setup.settings.instance.db, echo=False)
|