lamindb_setup 1.16.0__py3-none-any.whl → 1.18.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 +6 -3
- lamindb_setup/_check_setup.py +24 -85
- lamindb_setup/_connect_instance.py +23 -33
- lamindb_setup/_delete.py +10 -5
- lamindb_setup/_disconnect.py +12 -9
- lamindb_setup/_init_instance.py +0 -1
- lamindb_setup/_migrate.py +56 -30
- lamindb_setup/_set_managed_storage.py +3 -0
- lamindb_setup/_setup_user.py +8 -5
- lamindb_setup/_silence_loggers.py +2 -0
- lamindb_setup/core/__init__.py +0 -3
- lamindb_setup/core/_aws_options.py +18 -8
- lamindb_setup/core/_clone.py +1 -125
- lamindb_setup/core/_hub_client.py +1 -2
- lamindb_setup/core/_hub_core.py +9 -6
- lamindb_setup/core/_private_django_api.py +1 -3
- lamindb_setup/core/_settings.py +14 -10
- lamindb_setup/core/_settings_instance.py +40 -24
- lamindb_setup/core/_settings_load.py +25 -7
- lamindb_setup/core/_settings_storage.py +0 -5
- lamindb_setup/core/django.py +42 -14
- lamindb_setup/core/hashing.py +5 -5
- lamindb_setup/core/lamin.db.gz +0 -0
- lamindb_setup/core/upath.py +15 -6
- lamindb_setup/errors.py +5 -12
- lamindb_setup/io.py +16 -5
- {lamindb_setup-1.16.0.dist-info → lamindb_setup-1.18.0.dist-info}/METADATA +5 -6
- lamindb_setup-1.18.0.dist-info/RECORD +51 -0
- lamindb_setup-1.16.0.dist-info/RECORD +0 -50
- {lamindb_setup-1.16.0.dist-info → lamindb_setup-1.18.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-1.16.0.dist-info → lamindb_setup-1.18.0.dist-info}/WHEEL +0 -0
lamindb_setup/__init__.py
CHANGED
|
@@ -35,14 +35,17 @@ Migration management
|
|
|
35
35
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
|
-
__version__ = "1.
|
|
38
|
+
__version__ = "1.18.0" # denote a release candidate for 0.1.0 with 0.1rc1
|
|
39
39
|
|
|
40
40
|
import os
|
|
41
41
|
import warnings
|
|
42
42
|
|
|
43
|
-
# ignore for now,
|
|
43
|
+
# ignore for now, this is for timeout parameter,
|
|
44
|
+
# it is more convenient to specify it directly for now
|
|
44
45
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="supabase")
|
|
45
|
-
warnings.filterwarnings(
|
|
46
|
+
warnings.filterwarnings(
|
|
47
|
+
"ignore", category=DeprecationWarning, module="supabase_functions"
|
|
48
|
+
)
|
|
46
49
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="postgrest")
|
|
47
50
|
|
|
48
51
|
from packaging import version as packaging_version
|
lamindb_setup/_check_setup.py
CHANGED
|
@@ -4,7 +4,6 @@ import functools
|
|
|
4
4
|
import importlib as il
|
|
5
5
|
import inspect
|
|
6
6
|
import os
|
|
7
|
-
from importlib.metadata import distributions
|
|
8
7
|
from typing import TYPE_CHECKING
|
|
9
8
|
from uuid import UUID
|
|
10
9
|
|
|
@@ -16,7 +15,6 @@ from .core._settings import settings
|
|
|
16
15
|
from .core._settings_store import current_instance_settings_file
|
|
17
16
|
from .errors import (
|
|
18
17
|
MODULE_WASNT_CONFIGURED_MESSAGE_TEMPLATE,
|
|
19
|
-
InstanceNotSetupError,
|
|
20
18
|
ModuleWasntConfigured,
|
|
21
19
|
)
|
|
22
20
|
|
|
@@ -26,8 +24,6 @@ if TYPE_CHECKING:
|
|
|
26
24
|
from .core._settings_instance import InstanceSettings
|
|
27
25
|
|
|
28
26
|
|
|
29
|
-
CURRENT_ISETTINGS: InstanceSettings | None = None
|
|
30
|
-
MODULE_CANDIDATES: set[str] | None = None
|
|
31
27
|
IS_LOADING: bool = False
|
|
32
28
|
|
|
33
29
|
|
|
@@ -45,55 +41,6 @@ def disable_auto_connect(func: Callable):
|
|
|
45
41
|
return wrapper
|
|
46
42
|
|
|
47
43
|
|
|
48
|
-
def find_module_candidates():
|
|
49
|
-
"""Find all local packages that depend on lamindb."""
|
|
50
|
-
global MODULE_CANDIDATES
|
|
51
|
-
if MODULE_CANDIDATES is not None:
|
|
52
|
-
return MODULE_CANDIDATES
|
|
53
|
-
all_dists = list(distributions())
|
|
54
|
-
lamindb_deps = {
|
|
55
|
-
dist.metadata["Name"].lower()
|
|
56
|
-
for dist in all_dists
|
|
57
|
-
if dist.requires and any("lamindb" in req.lower() for req in dist.requires)
|
|
58
|
-
}
|
|
59
|
-
lamindb_deps.remove("lamindb")
|
|
60
|
-
MODULE_CANDIDATES = lamindb_deps
|
|
61
|
-
return lamindb_deps
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _get_current_instance_settings(from_module: str | None = None) -> InstanceSettings:
|
|
65
|
-
from .core._settings_instance import InstanceSettings
|
|
66
|
-
|
|
67
|
-
global CURRENT_ISETTINGS
|
|
68
|
-
|
|
69
|
-
if CURRENT_ISETTINGS is not None:
|
|
70
|
-
return CURRENT_ISETTINGS
|
|
71
|
-
if current_instance_settings_file().exists():
|
|
72
|
-
from .core._settings_load import load_instance_settings
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
isettings = load_instance_settings()
|
|
76
|
-
except Exception as e:
|
|
77
|
-
# user will get more detailed traceback once they run the CLI
|
|
78
|
-
logger.error(
|
|
79
|
-
"Current instance cannot be reached, disconnect from it: `lamin disconnect`\n"
|
|
80
|
-
"Alternatively, init or load a connectable instance on the"
|
|
81
|
-
" command line: `lamin connect <instance>` or `lamin init <...>`"
|
|
82
|
-
)
|
|
83
|
-
raise e
|
|
84
|
-
else:
|
|
85
|
-
module_candidates = find_module_candidates()
|
|
86
|
-
isettings = InstanceSettings(
|
|
87
|
-
id=UUID("00000000-0000-0000-0000-000000000000"),
|
|
88
|
-
owner="none",
|
|
89
|
-
name="none",
|
|
90
|
-
storage=None,
|
|
91
|
-
modules=",".join(module_candidates),
|
|
92
|
-
)
|
|
93
|
-
CURRENT_ISETTINGS = isettings
|
|
94
|
-
return isettings
|
|
95
|
-
|
|
96
|
-
|
|
97
44
|
def _normalize_module_name(module_name: str) -> str:
|
|
98
45
|
return module_name.replace("lnschema_", "").replace("_", "-")
|
|
99
46
|
|
|
@@ -137,12 +84,9 @@ def _infer_callers_module_name() -> str | None:
|
|
|
137
84
|
# users should not see it
|
|
138
85
|
def _check_instance_setup(from_module: str | None = None) -> bool:
|
|
139
86
|
if django_lamin.IS_SETUP:
|
|
140
|
-
# reload logic here because module might not yet have been imported
|
|
141
|
-
# upon first setup
|
|
142
87
|
if from_module is not None:
|
|
143
88
|
if from_module != "lamindb":
|
|
144
89
|
_check_module_in_instance_modules(from_module)
|
|
145
|
-
il.reload(il.import_module(from_module))
|
|
146
90
|
else:
|
|
147
91
|
infer_module = _infer_callers_module_name()
|
|
148
92
|
if infer_module is not None and infer_module not in {
|
|
@@ -159,34 +103,29 @@ def _check_instance_setup(from_module: str | None = None) -> bool:
|
|
|
159
103
|
"errors in regular lamindb usage"
|
|
160
104
|
)
|
|
161
105
|
return True
|
|
162
|
-
|
|
163
|
-
if
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if isettings.slug != "none/none":
|
|
174
|
-
logger.important(f"connected lamindb: {isettings.slug}")
|
|
175
|
-
# update of local storage location through search_local_root()
|
|
176
|
-
settings._instance_settings = isettings
|
|
177
|
-
else:
|
|
178
|
-
logger.warning("not connected, call: ln.connect('account/name')")
|
|
106
|
+
|
|
107
|
+
if IS_LOADING or from_module is None:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
not settings._instance_exists
|
|
112
|
+
and os.environ.get("LAMIN_CURRENT_INSTANCE") is not None
|
|
113
|
+
):
|
|
114
|
+
from ._connect_instance import connect
|
|
115
|
+
|
|
116
|
+
connect(_write_settings=False, _reload_lamindb=False)
|
|
179
117
|
return django_lamin.IS_SETUP
|
|
180
118
|
else:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
119
|
+
isettings = settings.instance
|
|
120
|
+
if from_module != "lamindb":
|
|
121
|
+
_check_module_in_instance_modules(from_module, isettings)
|
|
122
|
+
|
|
123
|
+
import lamindb # connect to the instance
|
|
124
|
+
else:
|
|
125
|
+
# disable_auto_connect to avoid triggering _check_instance_setup in modules
|
|
126
|
+
disable_auto_connect(django_lamin.setup_django)(isettings)
|
|
127
|
+
if isettings.slug != "none/none":
|
|
128
|
+
logger.important(f"connected lamindb: {isettings.slug}")
|
|
129
|
+
# update of local storage location through search_local_root()
|
|
130
|
+
settings._instance_settings = isettings
|
|
131
|
+
return django_lamin.IS_SETUP
|
|
@@ -9,10 +9,7 @@ from uuid import UUID
|
|
|
9
9
|
|
|
10
10
|
from lamin_utils import logger
|
|
11
11
|
|
|
12
|
-
from ._check_setup import
|
|
13
|
-
_check_instance_setup,
|
|
14
|
-
_get_current_instance_settings,
|
|
15
|
-
)
|
|
12
|
+
from ._check_setup import _check_instance_setup
|
|
16
13
|
from ._disconnect import disconnect
|
|
17
14
|
from ._init_instance import load_from_isettings
|
|
18
15
|
from ._silence_loggers import silence_loggers
|
|
@@ -25,7 +22,7 @@ from .core._settings_storage import StorageSettings
|
|
|
25
22
|
from .core._settings_store import instance_settings_file
|
|
26
23
|
from .core.cloud_sqlite_locker import unlock_cloud_sqlite_upon_exception
|
|
27
24
|
from .core.django import reset_django
|
|
28
|
-
from .errors import CannotSwitchDefaultInstance
|
|
25
|
+
from .errors import CannotSwitchDefaultInstance, InstanceNotFoundError
|
|
29
26
|
|
|
30
27
|
if TYPE_CHECKING:
|
|
31
28
|
from pathlib import Path
|
|
@@ -36,8 +33,6 @@ if TYPE_CHECKING:
|
|
|
36
33
|
# this is for testing purposes only
|
|
37
34
|
# set to True only to test failed load
|
|
38
35
|
_TEST_FAILED_LOAD = False
|
|
39
|
-
|
|
40
|
-
|
|
41
36
|
INSTANCE_NOT_FOUND_MESSAGE = (
|
|
42
37
|
"'{owner}/{name}' not found:"
|
|
43
38
|
" '{hub_result}'\nCheck your permissions:"
|
|
@@ -45,10 +40,6 @@ INSTANCE_NOT_FOUND_MESSAGE = (
|
|
|
45
40
|
)
|
|
46
41
|
|
|
47
42
|
|
|
48
|
-
class InstanceNotFoundError(SystemExit):
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
|
|
52
43
|
def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
|
|
53
44
|
return (
|
|
54
45
|
db_dsn_hub.scheme == db_dsn_local.scheme
|
|
@@ -105,6 +96,7 @@ def _connect_instance(
|
|
|
105
96
|
use_root_db_user: bool = False,
|
|
106
97
|
use_proxy_db: bool = False,
|
|
107
98
|
access_token: str | None = None,
|
|
99
|
+
raise_systemexit: bool = False,
|
|
108
100
|
) -> InstanceSettings:
|
|
109
101
|
settings_file = instance_settings_file(name, owner)
|
|
110
102
|
make_hub_request = True
|
|
@@ -173,12 +165,17 @@ def _connect_instance(
|
|
|
173
165
|
)
|
|
174
166
|
else:
|
|
175
167
|
message = "It is not possible to load an anonymous-owned instance from the hub"
|
|
168
|
+
exception = (
|
|
169
|
+
SystemExit(message)
|
|
170
|
+
if raise_systemexit
|
|
171
|
+
else InstanceNotFoundError(message)
|
|
172
|
+
)
|
|
176
173
|
if settings_file.exists():
|
|
177
174
|
isettings = load_instance_settings(settings_file)
|
|
178
175
|
if isettings.is_remote:
|
|
179
|
-
raise
|
|
176
|
+
raise exception
|
|
180
177
|
else:
|
|
181
|
-
raise
|
|
178
|
+
raise exception
|
|
182
179
|
return isettings
|
|
183
180
|
|
|
184
181
|
|
|
@@ -242,7 +239,11 @@ def _connect_cli(
|
|
|
242
239
|
|
|
243
240
|
owner, name = get_owner_name_from_identifier(instance)
|
|
244
241
|
isettings = _connect_instance(
|
|
245
|
-
owner,
|
|
242
|
+
owner,
|
|
243
|
+
name,
|
|
244
|
+
use_root_db_user=use_root_db_user,
|
|
245
|
+
use_proxy_db=use_proxy_db,
|
|
246
|
+
raise_systemexit=True,
|
|
246
247
|
)
|
|
247
248
|
isettings._persist(write_to_disk=True)
|
|
248
249
|
if not isettings.is_on_hub or isettings._is_cloud_sqlite:
|
|
@@ -266,13 +267,8 @@ def validate_connection_state(
|
|
|
266
267
|
from django.db import connection
|
|
267
268
|
|
|
268
269
|
if (
|
|
269
|
-
settings._instance_exists
|
|
270
|
+
settings._instance_exists # exists only for real instances, not for none/none
|
|
270
271
|
and f"{owner}/{name}" == settings.instance.slug
|
|
271
|
-
# below is to ensure that if another process interferes
|
|
272
|
-
# we don't use the in-memory mock database
|
|
273
|
-
# could be made more specific by checking whether the django
|
|
274
|
-
# configured database is the same as the one in settings
|
|
275
|
-
and connection.settings_dict["NAME"] != ":memory:"
|
|
276
272
|
and not use_root_db_user # always re-connect for root db user
|
|
277
273
|
):
|
|
278
274
|
logger.important(
|
|
@@ -280,7 +276,7 @@ def validate_connection_state(
|
|
|
280
276
|
)
|
|
281
277
|
return None
|
|
282
278
|
else:
|
|
283
|
-
if settings._instance_exists
|
|
279
|
+
if settings._instance_exists:
|
|
284
280
|
import lamindb as ln
|
|
285
281
|
|
|
286
282
|
if ln.context.transform is not None:
|
|
@@ -292,7 +288,9 @@ def validate_connection_state(
|
|
|
292
288
|
|
|
293
289
|
@unlock_cloud_sqlite_upon_exception(ignore_prev_locker=True)
|
|
294
290
|
def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
295
|
-
"""Connect
|
|
291
|
+
"""Connect the global default instance.
|
|
292
|
+
|
|
293
|
+
If you want to create a read-only database client, use :class:`~lamindb.DB` instead.
|
|
296
294
|
|
|
297
295
|
Args:
|
|
298
296
|
instance: Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
|
|
@@ -340,12 +338,9 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
340
338
|
if settings._instance_exists:
|
|
341
339
|
isettings = settings.instance
|
|
342
340
|
else:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
"No instance was connected through the CLI, pass a value to `instance` or connect via the CLI."
|
|
347
|
-
)
|
|
348
|
-
isettings = isettings_or_none
|
|
341
|
+
raise ValueError(
|
|
342
|
+
"No instance was connected through the CLI, pass a value to `instance` or connect via the CLI."
|
|
343
|
+
)
|
|
349
344
|
if use_root_db_user:
|
|
350
345
|
reset_django()
|
|
351
346
|
owner, name = isettings.owner, isettings.name
|
|
@@ -414,7 +409,6 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
414
409
|
|
|
415
410
|
load_from_isettings(isettings, user=_user, write_settings=_write_settings)
|
|
416
411
|
if _reload_lamindb:
|
|
417
|
-
importlib.reload(importlib.import_module("lamindb"))
|
|
418
412
|
reset_django_module_variables()
|
|
419
413
|
if isettings.slug != "none/none":
|
|
420
414
|
logger.important(f"connected lamindb: {isettings.slug}")
|
|
@@ -424,10 +418,6 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
424
418
|
isettings._get_settings_file().unlink(missing_ok=True) # type: ignore
|
|
425
419
|
settings._instance_settings = None
|
|
426
420
|
raise e
|
|
427
|
-
if settings.dev_dir is None:
|
|
428
|
-
logger.important_hint(
|
|
429
|
-
"to map a local dev directory, set: ln.setup.settings.dev_dir = '.'"
|
|
430
|
-
)
|
|
431
421
|
return None
|
|
432
422
|
|
|
433
423
|
|
lamindb_setup/_delete.py
CHANGED
|
@@ -11,6 +11,7 @@ from .core._aws_options import HOSTED_BUCKETS
|
|
|
11
11
|
from .core._hub_core import delete_instance as delete_instance_on_hub
|
|
12
12
|
from .core._hub_core import get_storage_records_for_instance
|
|
13
13
|
from .core._settings import settings
|
|
14
|
+
from .core._settings_load import load_instance_settings
|
|
14
15
|
from .core._settings_storage import StorageSettings
|
|
15
16
|
from .core.upath import LocalPathClasses, check_storage_is_empty
|
|
16
17
|
|
|
@@ -38,6 +39,8 @@ def delete_exclusion_dir(isettings: InstanceSettings) -> None:
|
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
42
|
+
assert isettings.slug != "none/none"
|
|
43
|
+
|
|
41
44
|
settings_file = isettings._get_settings_file()
|
|
42
45
|
if settings_file.exists():
|
|
43
46
|
settings_file.unlink()
|
|
@@ -51,12 +54,14 @@ def delete_by_isettings(isettings: InstanceSettings) -> None:
|
|
|
51
54
|
"Did not have permission to delete SQLite file:"
|
|
52
55
|
f" {isettings._sqlite_file}"
|
|
53
56
|
)
|
|
54
|
-
pass
|
|
55
57
|
# unset the global instance settings
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
settings.
|
|
58
|
+
isettings_on_disk = load_instance_settings()
|
|
59
|
+
if isettings_on_disk.slug == isettings.slug:
|
|
60
|
+
settings._instance_settings_path.unlink() # current instance settings file
|
|
61
|
+
# settings.instance can differ from instance in current_settings_file()
|
|
62
|
+
# due to connect() in the same process
|
|
63
|
+
if settings.instance.slug == isettings.slug:
|
|
64
|
+
settings._instance_settings = None
|
|
60
65
|
|
|
61
66
|
|
|
62
67
|
def delete(slug: str, force: bool = False, require_empty: bool = True) -> int | None:
|
lamindb_setup/_disconnect.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from lamin_utils import logger
|
|
4
4
|
|
|
5
5
|
from .core._settings import settings
|
|
6
|
-
from .core.
|
|
6
|
+
from .core._settings_load import load_instance_settings
|
|
7
7
|
from .core.cloud_sqlite_locker import clear_locker
|
|
8
8
|
|
|
9
9
|
|
|
@@ -15,10 +15,11 @@ def disconnect(mute: bool = False) -> None:
|
|
|
15
15
|
See Also:
|
|
16
16
|
Clear default instance configuration via the CLI, see `here <https://docs.lamin.ai/cli#disconnect>`__.
|
|
17
17
|
"""
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# settings._instance_exists can be true due to connect even without having a file
|
|
19
|
+
if settings._instance_exists:
|
|
20
|
+
instance = settings.instance
|
|
20
21
|
try:
|
|
21
|
-
|
|
22
|
+
instance._update_cloud_sqlite_file()
|
|
22
23
|
except Exception as e:
|
|
23
24
|
if isinstance(e, FileNotFoundError):
|
|
24
25
|
logger.warning("did not find local cache file")
|
|
@@ -26,10 +27,12 @@ def disconnect(mute: bool = False) -> None:
|
|
|
26
27
|
logger.warning("did not upload cache file - not enough permissions")
|
|
27
28
|
else:
|
|
28
29
|
raise e
|
|
29
|
-
current_instance_settings_file().unlink()
|
|
30
30
|
clear_locker()
|
|
31
|
+
# instance in current instance file can differ from instance in settings
|
|
32
|
+
if load_instance_settings().slug == instance.slug:
|
|
33
|
+
settings._instance_settings_path.unlink(missing_ok=True)
|
|
34
|
+
settings._instance_settings = None
|
|
31
35
|
if not mute:
|
|
32
|
-
logger.success(f"disconnected instance: {instance}")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
logger.info("no instance loaded")
|
|
36
|
+
logger.success(f"disconnected instance: {instance.slug}")
|
|
37
|
+
elif not mute:
|
|
38
|
+
logger.info("no instance loaded")
|
lamindb_setup/_init_instance.py
CHANGED
|
@@ -340,7 +340,6 @@ def init(
|
|
|
340
340
|
from ._schema_metadata import update_schema_in_hub
|
|
341
341
|
|
|
342
342
|
update_schema_in_hub(access_token=access_token)
|
|
343
|
-
importlib.reload(importlib.import_module("lamindb"))
|
|
344
343
|
reset_django_module_variables()
|
|
345
344
|
logger.important(f"initialized lamindb: {isettings.slug}")
|
|
346
345
|
except Exception as e:
|
lamindb_setup/_migrate.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
|
|
3
5
|
import httpx
|
|
4
6
|
from django.db import connection
|
|
5
7
|
from django.db.migrations.loader import MigrationLoader
|
|
@@ -98,16 +100,19 @@ class migrate:
|
|
|
98
100
|
|
|
99
101
|
@classmethod
|
|
100
102
|
def deploy(cls, package_name: str | None = None, number: int | None = None) -> None:
|
|
101
|
-
|
|
103
|
+
assert settings._instance_exists, (
|
|
104
|
+
"Not connected to an instance, please connect to migrate."
|
|
105
|
+
)
|
|
102
106
|
|
|
103
107
|
# NOTE: this is a temporary solution to avoid breaking tests
|
|
104
108
|
LAMIN_MIGRATE_ON_LAMBDA = (
|
|
105
|
-
os.
|
|
109
|
+
os.getenv("LAMIN_MIGRATE_ON_LAMBDA", "false") == "true"
|
|
106
110
|
)
|
|
111
|
+
isettings = settings.instance
|
|
107
112
|
|
|
108
|
-
if
|
|
113
|
+
if isettings.is_on_hub and LAMIN_MIGRATE_ON_LAMBDA:
|
|
109
114
|
response = httpx.post(
|
|
110
|
-
f"{
|
|
115
|
+
f"{isettings.api_url}/instances/{isettings._id}/migrate",
|
|
111
116
|
headers={"Authorization": f"Bearer {settings.user.access_token}"},
|
|
112
117
|
timeout=None, # this can take time
|
|
113
118
|
)
|
|
@@ -125,49 +130,41 @@ class migrate:
|
|
|
125
130
|
from lamindb_setup._schema_metadata import update_schema_in_hub
|
|
126
131
|
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
127
132
|
from lamindb_setup.core._hub_crud import (
|
|
128
|
-
select_collaborator,
|
|
129
133
|
update_instance,
|
|
130
134
|
)
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
instance_id=settings.instance._id,
|
|
138
|
-
account_id=settings.user._uuid,
|
|
139
|
-
fine_grained_access=settings.instance._fine_grained_access,
|
|
140
|
-
)
|
|
141
|
-
if collaborator is None or collaborator["role"] != "admin":
|
|
142
|
-
raise SystemExit(
|
|
143
|
-
"❌ Only admins can deploy migrations, please ensure that you're an"
|
|
144
|
-
f" admin: https://lamin.ai/{settings.instance.slug}/settings"
|
|
145
|
-
)
|
|
136
|
+
isettings = settings.instance
|
|
137
|
+
is_managed_by_hub = isettings.is_managed_by_hub
|
|
138
|
+
is_on_hub = is_managed_by_hub or isettings.is_on_hub
|
|
139
|
+
|
|
140
|
+
if is_managed_by_hub and "root" not in isettings.db:
|
|
146
141
|
# ensure we connect with the root user
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
connect(use_root_db_user=True)
|
|
143
|
+
assert "root" in (instance_db := settings.instance.db), instance_db
|
|
144
|
+
if is_on_hub:
|
|
150
145
|
# we need lamindb to be installed, otherwise we can't populate the version
|
|
151
146
|
# information in the hub
|
|
147
|
+
# this also connects
|
|
152
148
|
import lamindb
|
|
153
|
-
|
|
149
|
+
# this is needed to avoid connecting on importing apps inside setup_django process
|
|
150
|
+
setup_django_disable_autoconnect = disable_auto_connect(setup_django)
|
|
154
151
|
# this sets up django and deploys the migrations
|
|
155
152
|
if package_name is not None and number is not None:
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
setup_django_disable_autoconnect(
|
|
154
|
+
isettings,
|
|
158
155
|
deploy_migrations=True,
|
|
159
156
|
appname_number=(package_name, number),
|
|
160
157
|
)
|
|
161
158
|
else:
|
|
162
|
-
|
|
159
|
+
setup_django_disable_autoconnect(isettings, deploy_migrations=True)
|
|
163
160
|
# this populates the hub
|
|
164
|
-
if
|
|
161
|
+
if is_on_hub:
|
|
165
162
|
logger.important(f"updating lamindb version in hub: {lamindb.__version__}")
|
|
166
|
-
if
|
|
163
|
+
if isettings.dialect != "sqlite":
|
|
167
164
|
update_schema_in_hub()
|
|
168
165
|
call_with_fallback_auth(
|
|
169
166
|
update_instance,
|
|
170
|
-
instance_id=
|
|
167
|
+
instance_id=isettings._id.hex,
|
|
171
168
|
instance_fields={"lamindb_version": lamindb.__version__},
|
|
172
169
|
)
|
|
173
170
|
|
|
@@ -175,16 +172,45 @@ class migrate:
|
|
|
175
172
|
@disable_auto_connect
|
|
176
173
|
def check(cls) -> bool:
|
|
177
174
|
"""Check whether Registry definitions are in sync with migrations."""
|
|
175
|
+
import io
|
|
176
|
+
|
|
178
177
|
from django.core.management import call_command
|
|
179
178
|
|
|
180
179
|
setup_django(settings.instance)
|
|
180
|
+
|
|
181
|
+
# Capture stdout/stderr to show what migrations are needed if check fails
|
|
182
|
+
stdout = io.StringIO()
|
|
183
|
+
stderr = io.StringIO()
|
|
184
|
+
|
|
181
185
|
try:
|
|
182
|
-
call_command(
|
|
186
|
+
call_command(
|
|
187
|
+
"makemigrations", check_changes=True, stdout=stdout, stderr=stderr
|
|
188
|
+
)
|
|
183
189
|
except SystemExit:
|
|
184
190
|
logger.error(
|
|
185
191
|
"migrations are not in sync with ORMs, please create a migration: lamin"
|
|
186
192
|
" migrate create"
|
|
187
193
|
)
|
|
194
|
+
# Print captured output from the check
|
|
195
|
+
if stdout.getvalue():
|
|
196
|
+
logger.error(f"makemigrations --check stdout:\n{stdout.getvalue()}")
|
|
197
|
+
if stderr.getvalue():
|
|
198
|
+
logger.error(f"makemigrations --check stderr:\n{stderr.getvalue()}")
|
|
199
|
+
|
|
200
|
+
# Run makemigrations --dry-run to show what would be created
|
|
201
|
+
stdout2 = io.StringIO()
|
|
202
|
+
stderr2 = io.StringIO()
|
|
203
|
+
try:
|
|
204
|
+
call_command(
|
|
205
|
+
"makemigrations", dry_run=True, stdout=stdout2, stderr=stderr2
|
|
206
|
+
)
|
|
207
|
+
except SystemExit:
|
|
208
|
+
pass
|
|
209
|
+
if stdout2.getvalue():
|
|
210
|
+
logger.error(f"makemigrations --dry-run stdout:\n{stdout2.getvalue()}")
|
|
211
|
+
if stderr2.getvalue():
|
|
212
|
+
logger.error(f"makemigrations --dry-run stderr:\n{stderr2.getvalue()}")
|
|
213
|
+
|
|
188
214
|
return False
|
|
189
215
|
return True
|
|
190
216
|
|
|
@@ -16,6 +16,9 @@ if TYPE_CHECKING:
|
|
|
16
16
|
def set_managed_storage(root: UPathStr, host: str | None = None, **fs_kwargs):
|
|
17
17
|
"""Add or switch to another managed storage location.
|
|
18
18
|
|
|
19
|
+
Note: This function should be called `set_writeable_storage_location` instead. But likely it will disappear
|
|
20
|
+
in refactoring that consolidates with the `ln.Storage()` path.
|
|
21
|
+
|
|
19
22
|
Args:
|
|
20
23
|
root: `UPathStr` - The new storage root, e.g., an S3 bucket.
|
|
21
24
|
host: `str | None = None` For a shared local storage location, pass a globally unique host identifier, e.g. `"my-institute-cluster-1"`, `"my-server-abcd"`, ...
|
lamindb_setup/_setup_user.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from time import sleep
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from lamin_utils import logger
|
|
@@ -59,15 +60,14 @@ def login(
|
|
|
59
60
|
|
|
60
61
|
`login()` prompts for your API key unless you set it via the `LAMIN_API_KEY` environment variable or pass it as an argument.
|
|
61
62
|
|
|
62
|
-
You can create your API key in your account settings on LaminHub
|
|
63
|
+
You can create your API key in your account settings on LaminHub at `lamin.ai/settings <https://lamin.ai/settings>`__.
|
|
64
|
+
|
|
65
|
+
Note that the preferred method is to use the CLI command `lamin login`: `docs.lamin.ai/cli#login <https://docs.lamin.ai/cli#login>`__.
|
|
63
66
|
|
|
64
67
|
Args:
|
|
65
68
|
user: User handle.
|
|
66
69
|
api_key: API key.
|
|
67
70
|
|
|
68
|
-
See Also:
|
|
69
|
-
Login via the CLI command `lamin login`, see `here <https://docs.lamin.ai/cli#login>`__.
|
|
70
|
-
|
|
71
71
|
Examples:
|
|
72
72
|
|
|
73
73
|
Logging in the first time::
|
|
@@ -80,12 +80,15 @@ def login(
|
|
|
80
80
|
|
|
81
81
|
ln.setup.login("myhandle") # pass your user handle
|
|
82
82
|
"""
|
|
83
|
+
from getpass import getpass
|
|
84
|
+
|
|
83
85
|
if user is None:
|
|
84
86
|
if api_key is None:
|
|
85
87
|
if "LAMIN_API_KEY" in os.environ:
|
|
86
88
|
api_key = os.environ["LAMIN_API_KEY"]
|
|
87
89
|
else:
|
|
88
|
-
|
|
90
|
+
print("Copy your API key. To create one: https://lamin.ai/settings")
|
|
91
|
+
api_key = getpass("Paste it: ")
|
|
89
92
|
elif api_key is not None:
|
|
90
93
|
raise ValueError("Please provide either 'user' or 'api_key', not both.")
|
|
91
94
|
|
|
@@ -25,6 +25,8 @@ def silence_loggers():
|
|
|
25
25
|
set_stream_logger(name="botocore.auth", level=logging.WARNING)
|
|
26
26
|
set_stream_logger(name="botocore.endpoint", level=logging.WARNING)
|
|
27
27
|
set_stream_logger(name="httpx", level=logging.WARNING)
|
|
28
|
+
set_stream_logger(name="httpcore", level=logging.WARNING)
|
|
29
|
+
set_stream_logger(name="hpack", level=logging.WARNING)
|
|
28
30
|
try:
|
|
29
31
|
import aiobotocore
|
|
30
32
|
|
lamindb_setup/core/__init__.py
CHANGED