lamindb_setup 1.6.1__py3-none-any.whl → 1.7.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 +5 -3
- lamindb_setup/_cache.py +46 -11
- lamindb_setup/_check_setup.py +9 -22
- lamindb_setup/_connect_instance.py +9 -157
- lamindb_setup/_init_instance.py +39 -53
- lamindb_setup/_register_instance.py +1 -1
- lamindb_setup/_schema_metadata.py +4 -0
- lamindb_setup/_set_managed_storage.py +6 -4
- lamindb_setup/core/__init__.py +1 -1
- lamindb_setup/core/_aws_options.py +3 -3
- lamindb_setup/core/_hub_client.py +2 -3
- lamindb_setup/core/_hub_core.py +68 -35
- lamindb_setup/core/_hub_crud.py +17 -3
- lamindb_setup/core/_settings.py +28 -10
- lamindb_setup/core/_settings_instance.py +8 -5
- lamindb_setup/core/_settings_load.py +23 -14
- lamindb_setup/core/_settings_save.py +5 -4
- lamindb_setup/core/_settings_storage.py +105 -46
- lamindb_setup/core/_settings_store.py +11 -2
- lamindb_setup/core/cloud_sqlite_locker.py +2 -6
- lamindb_setup/core/django.py +7 -2
- lamindb_setup/core/exceptions.py +1 -10
- lamindb_setup/core/hashing.py +1 -1
- lamindb_setup/core/types.py +1 -17
- lamindb_setup/core/upath.py +16 -10
- lamindb_setup/errors.py +70 -0
- lamindb_setup/types.py +20 -0
- {lamindb_setup-1.6.1.dist-info → lamindb_setup-1.7.0.dist-info}/METADATA +2 -2
- lamindb_setup-1.7.0.dist-info/RECORD +50 -0
- lamindb_setup-1.6.1.dist-info/RECORD +0 -48
- {lamindb_setup-1.6.1.dist-info → lamindb_setup-1.7.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-1.6.1.dist-info → lamindb_setup-1.7.0.dist-info}/WHEEL +0 -0
lamindb_setup/__init__.py
CHANGED
|
@@ -30,18 +30,20 @@ Modules & settings:
|
|
|
30
30
|
settings
|
|
31
31
|
core
|
|
32
32
|
django
|
|
33
|
+
errors
|
|
34
|
+
types
|
|
33
35
|
|
|
34
36
|
"""
|
|
35
37
|
|
|
36
|
-
__version__ = "1.
|
|
38
|
+
__version__ = "1.7.0" # denote a release candidate for 0.1.0 with 0.1rc1
|
|
37
39
|
|
|
38
40
|
import os
|
|
39
41
|
|
|
40
42
|
from packaging import version as packaging_version
|
|
41
43
|
|
|
42
|
-
from . import core
|
|
44
|
+
from . import core, errors, types
|
|
43
45
|
from ._check_setup import _check_instance_setup
|
|
44
|
-
from ._connect_instance import connect
|
|
46
|
+
from ._connect_instance import connect
|
|
45
47
|
from ._delete import delete
|
|
46
48
|
from ._disconnect import disconnect
|
|
47
49
|
from ._django import django
|
lamindb_setup/_cache.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
6
|
+
from dotenv import dotenv_values
|
|
5
7
|
from lamin_utils import logger
|
|
6
8
|
|
|
7
|
-
from .core._settings_save import
|
|
9
|
+
from .core._settings_save import save_platform_user_storage_settings
|
|
10
|
+
from .core._settings_store import system_settings_file
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
def clear_cache_dir():
|
|
@@ -13,7 +16,7 @@ def clear_cache_dir():
|
|
|
13
16
|
try:
|
|
14
17
|
if settings.instance._is_cloud_sqlite:
|
|
15
18
|
logger.warning(
|
|
16
|
-
"
|
|
19
|
+
"disconnecting the current instance to update the cloud sqlite database."
|
|
17
20
|
)
|
|
18
21
|
disconnect()
|
|
19
22
|
except SystemExit as e:
|
|
@@ -21,9 +24,12 @@ def clear_cache_dir():
|
|
|
21
24
|
raise e
|
|
22
25
|
|
|
23
26
|
cache_dir = settings.cache_dir
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
if cache_dir.exists():
|
|
28
|
+
shutil.rmtree(cache_dir)
|
|
29
|
+
cache_dir.mkdir()
|
|
30
|
+
logger.success("the cache directory was cleared")
|
|
31
|
+
else:
|
|
32
|
+
logger.warning("the cache directory doesn't exist")
|
|
27
33
|
|
|
28
34
|
|
|
29
35
|
def get_cache_dir():
|
|
@@ -41,12 +47,41 @@ def set_cache_dir(cache_dir: str):
|
|
|
41
47
|
|
|
42
48
|
old_cache_dir = settings.cache_dir
|
|
43
49
|
new_cache_dir = _process_cache_path(cache_dir)
|
|
50
|
+
|
|
51
|
+
system_cache_dir = None
|
|
52
|
+
if (system_settings := system_settings_file()).exists():
|
|
53
|
+
system_cache_dir = dotenv_values(system_settings).get(
|
|
54
|
+
"lamindb_cache_path", None
|
|
55
|
+
)
|
|
56
|
+
system_cache_dir = (
|
|
57
|
+
Path(system_cache_dir) if system_cache_dir is not None else None
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
need_reset = False
|
|
44
61
|
if new_cache_dir is None:
|
|
45
|
-
|
|
62
|
+
need_reset = True
|
|
63
|
+
new_cache_dir = (
|
|
64
|
+
DEFAULT_CACHE_DIR if system_cache_dir is None else system_cache_dir
|
|
65
|
+
)
|
|
66
|
+
|
|
46
67
|
if new_cache_dir != old_cache_dir:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
if old_cache_dir.exists():
|
|
69
|
+
shutil.copytree(old_cache_dir, new_cache_dir, dirs_exist_ok=True)
|
|
70
|
+
logger.info(
|
|
71
|
+
f"the current cache directory was copied to {new_cache_dir.as_posix()} "
|
|
72
|
+
)
|
|
73
|
+
if old_cache_dir != system_cache_dir:
|
|
74
|
+
shutil.rmtree(old_cache_dir)
|
|
75
|
+
logger.info(
|
|
76
|
+
f"cleared the old cache directory {old_cache_dir.as_posix()}"
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
logger.info(
|
|
80
|
+
f"didn't clear the system cache directory {system_cache_dir.as_posix()}, "
|
|
81
|
+
"please clear it manually if you need"
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
new_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
new_cache_dir = new_cache_dir.resolve()
|
|
86
|
+
save_platform_user_storage_settings(None if need_reset else new_cache_dir)
|
|
52
87
|
settings._cache_dir = new_cache_dir
|
lamindb_setup/_check_setup.py
CHANGED
|
@@ -12,7 +12,11 @@ from ._silence_loggers import silence_loggers
|
|
|
12
12
|
from .core import django as django_lamin
|
|
13
13
|
from .core._settings import settings
|
|
14
14
|
from .core._settings_store import current_instance_settings_file
|
|
15
|
-
from .
|
|
15
|
+
from .errors import (
|
|
16
|
+
MODULE_WASNT_CONFIGURED_MESSAGE_TEMPLATE,
|
|
17
|
+
InstanceNotSetupError,
|
|
18
|
+
ModuleWasntConfigured,
|
|
19
|
+
)
|
|
16
20
|
|
|
17
21
|
if TYPE_CHECKING:
|
|
18
22
|
from collections.abc import Callable
|
|
@@ -20,24 +24,10 @@ if TYPE_CHECKING:
|
|
|
20
24
|
from .core._settings_instance import InstanceSettings
|
|
21
25
|
|
|
22
26
|
|
|
23
|
-
class InstanceNotSetupError(DefaultMessageException):
|
|
24
|
-
default_message = """\
|
|
25
|
-
To use lamindb, you need to connect to an instance.
|
|
26
|
-
|
|
27
|
-
Connect to an instance: `ln.connect()`. Init an instance: `ln.setup.init()`.
|
|
28
|
-
|
|
29
|
-
If you used the CLI to set up lamindb in a notebook, restart the Python session.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
|
|
33
27
|
CURRENT_ISETTINGS: InstanceSettings | None = None
|
|
34
28
|
IS_LOADING: bool = False
|
|
35
29
|
|
|
36
30
|
|
|
37
|
-
class ModuleWasntConfigured(SystemExit):
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
|
-
|
|
41
31
|
# decorator to disable auto-connect when importing a module such as lamindb
|
|
42
32
|
def disable_auto_connect(func: Callable):
|
|
43
33
|
@functools.wraps(func)
|
|
@@ -84,18 +74,15 @@ def _normalize_module_name(module_name: str) -> str:
|
|
|
84
74
|
def _check_module_in_instance_modules(
|
|
85
75
|
module: str, isettings: InstanceSettings | None = None
|
|
86
76
|
) -> None:
|
|
87
|
-
not_in_instance_msg = (
|
|
88
|
-
f"'{module}' is missing from this instance. "
|
|
89
|
-
"Please go to your instance settings page and add it under 'schema modules'."
|
|
90
|
-
)
|
|
91
|
-
|
|
92
77
|
if isettings is not None:
|
|
93
78
|
modules_raw = isettings.modules
|
|
94
79
|
modules = set(modules_raw).union(
|
|
95
80
|
_normalize_module_name(module) for module in modules_raw
|
|
96
81
|
)
|
|
97
82
|
if _normalize_module_name(module) not in modules and module not in modules:
|
|
98
|
-
raise ModuleWasntConfigured(
|
|
83
|
+
raise ModuleWasntConfigured(
|
|
84
|
+
MODULE_WASNT_CONFIGURED_MESSAGE_TEMPLATE.format(module)
|
|
85
|
+
)
|
|
99
86
|
else:
|
|
100
87
|
return
|
|
101
88
|
|
|
@@ -105,7 +92,7 @@ def _check_module_in_instance_modules(
|
|
|
105
92
|
# app.name is always unnormalized module (python package) name
|
|
106
93
|
if module == app.name or module == _normalize_module_name(app.name):
|
|
107
94
|
return
|
|
108
|
-
raise ModuleWasntConfigured(
|
|
95
|
+
raise ModuleWasntConfigured(MODULE_WASNT_CONFIGURED_MESSAGE_TEMPLATE.format(module))
|
|
109
96
|
|
|
110
97
|
|
|
111
98
|
# infer the name of the module that calls this function
|
|
@@ -9,11 +9,7 @@ from lamin_utils import logger
|
|
|
9
9
|
|
|
10
10
|
from ._check_setup import _check_instance_setup, _get_current_instance_settings
|
|
11
11
|
from ._disconnect import disconnect
|
|
12
|
-
from ._init_instance import
|
|
13
|
-
MESSAGE_CANNOT_SWITCH_DEFAULT_INSTANCE,
|
|
14
|
-
CannotSwitchDefaultInstance,
|
|
15
|
-
load_from_isettings,
|
|
16
|
-
)
|
|
12
|
+
from ._init_instance import load_from_isettings
|
|
17
13
|
from ._silence_loggers import silence_loggers
|
|
18
14
|
from .core._hub_core import connect_instance_hub
|
|
19
15
|
from .core._hub_utils import (
|
|
@@ -26,12 +22,13 @@ from .core._settings_load import load_instance_settings
|
|
|
26
22
|
from .core._settings_storage import StorageSettings
|
|
27
23
|
from .core._settings_store import instance_settings_file, settings_dir
|
|
28
24
|
from .core.cloud_sqlite_locker import unlock_cloud_sqlite_upon_exception
|
|
25
|
+
from .errors import CannotSwitchDefaultInstance
|
|
29
26
|
|
|
30
27
|
if TYPE_CHECKING:
|
|
31
28
|
from pathlib import Path
|
|
32
29
|
|
|
33
30
|
from .core._settings_user import UserSettings
|
|
34
|
-
from .
|
|
31
|
+
from .types import UPathStr
|
|
35
32
|
|
|
36
33
|
# this is for testing purposes only
|
|
37
34
|
# set to True only to test failed load
|
|
@@ -158,7 +155,7 @@ def _connect_instance(
|
|
|
158
155
|
isettings = InstanceSettings(
|
|
159
156
|
id=UUID(instance_result["id"]),
|
|
160
157
|
owner=owner,
|
|
161
|
-
name=name,
|
|
158
|
+
name=instance_result["name"],
|
|
162
159
|
storage=ssettings,
|
|
163
160
|
db=db_updated,
|
|
164
161
|
modules=instance_result["schema_str"],
|
|
@@ -211,7 +208,7 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
211
208
|
isettings: InstanceSettings = None # type: ignore
|
|
212
209
|
# _db is still needed because it is called in init
|
|
213
210
|
_db: str | None = kwargs.get("_db", None)
|
|
214
|
-
_write_settings: bool = kwargs.get("_write_settings",
|
|
211
|
+
_write_settings: bool = kwargs.get("_write_settings", False)
|
|
215
212
|
_raise_not_found_error: bool = kwargs.get("_raise_not_found_error", True)
|
|
216
213
|
_reload_lamindb: bool = kwargs.get("_reload_lamindb", True)
|
|
217
214
|
_test: bool = kwargs.get("_test", False)
|
|
@@ -241,9 +238,9 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
241
238
|
logger.important(f"connected lamindb: {settings.instance.slug}")
|
|
242
239
|
return None
|
|
243
240
|
else:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
)
|
|
241
|
+
from lamindb_setup.core.django import reset_django
|
|
242
|
+
|
|
243
|
+
reset_django()
|
|
247
244
|
elif (
|
|
248
245
|
_write_settings
|
|
249
246
|
and settings._instance_exists
|
|
@@ -270,15 +267,6 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
270
267
|
if _test:
|
|
271
268
|
return None
|
|
272
269
|
silence_loggers()
|
|
273
|
-
# migrate away from lnschema-core
|
|
274
|
-
no_lnschema_core_file = (
|
|
275
|
-
settings_dir / f"no_lnschema_core-{isettings.slug.replace('/', '--')}"
|
|
276
|
-
)
|
|
277
|
-
if not no_lnschema_core_file.exists():
|
|
278
|
-
# sqlite file for cloud sqlite instances is already updated here
|
|
279
|
-
migrate_lnschema_core(
|
|
280
|
-
isettings, no_lnschema_core_file, write_file=_write_settings
|
|
281
|
-
)
|
|
282
270
|
check, msg = isettings._load_db()
|
|
283
271
|
if not check:
|
|
284
272
|
local_db = (
|
|
@@ -305,8 +293,7 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
305
293
|
load_from_isettings(isettings, user=_user, write_settings=_write_settings)
|
|
306
294
|
if _reload_lamindb:
|
|
307
295
|
importlib.reload(importlib.import_module("lamindb"))
|
|
308
|
-
|
|
309
|
-
logger.important(f"connected lamindb: {isettings.slug}")
|
|
296
|
+
logger.important(f"connected lamindb: {isettings.slug}")
|
|
310
297
|
except Exception as e:
|
|
311
298
|
if isettings is not None:
|
|
312
299
|
if _write_settings:
|
|
@@ -316,141 +303,6 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
316
303
|
return None
|
|
317
304
|
|
|
318
305
|
|
|
319
|
-
def load(slug: str) -> str | tuple | None:
|
|
320
|
-
"""Connect to instance and set ``auto-connect`` to true.
|
|
321
|
-
|
|
322
|
-
This is exactly the same as ``ln.connect()`` except for that
|
|
323
|
-
``ln.connect()`` doesn't change the state of ``auto-connect``.
|
|
324
|
-
"""
|
|
325
|
-
print("Warning: This is deprecated and will be removed.")
|
|
326
|
-
result = connect(slug)
|
|
327
|
-
settings.auto_connect = True
|
|
328
|
-
return result
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def migrate_lnschema_core(
|
|
332
|
-
isettings: InstanceSettings, no_lnschema_core_file: Path, write_file: bool = True
|
|
333
|
-
):
|
|
334
|
-
"""Migrate lnschema_core tables to lamindb tables."""
|
|
335
|
-
from urllib.parse import urlparse
|
|
336
|
-
|
|
337
|
-
# we need to do this because the sqlite file should be already synced
|
|
338
|
-
# has no effect if not cloud sqlite
|
|
339
|
-
# errors if the sqlite file is not in the cloud and doesn't exist locally
|
|
340
|
-
# isettings.db syncs but doesn't error in this case due to error_no_origin=False
|
|
341
|
-
isettings._update_local_sqlite_file()
|
|
342
|
-
|
|
343
|
-
parsed_uri = urlparse(isettings.db)
|
|
344
|
-
db_type = parsed_uri.scheme
|
|
345
|
-
|
|
346
|
-
if db_type == "sqlite":
|
|
347
|
-
import sqlite3
|
|
348
|
-
|
|
349
|
-
# maybe also use LAMINDB_DJANGO_DATABASE_URL here?
|
|
350
|
-
conn = sqlite3.connect(parsed_uri.path)
|
|
351
|
-
elif db_type in ["postgresql", "postgres"]:
|
|
352
|
-
import psycopg2
|
|
353
|
-
|
|
354
|
-
# do not ignore LAMINDB_DJANGO_DATABASE_URL if it is set
|
|
355
|
-
conn = psycopg2.connect(
|
|
356
|
-
os.environ.get("LAMINDB_DJANGO_DATABASE_URL", isettings.db)
|
|
357
|
-
)
|
|
358
|
-
else:
|
|
359
|
-
raise ValueError("Unsupported database type. Use 'sqlite' or 'postgresql' URI.")
|
|
360
|
-
|
|
361
|
-
cur = conn.cursor()
|
|
362
|
-
|
|
363
|
-
try:
|
|
364
|
-
if db_type == "sqlite":
|
|
365
|
-
cur.execute(
|
|
366
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='lamindb_user'"
|
|
367
|
-
)
|
|
368
|
-
migrated = cur.fetchone() is not None
|
|
369
|
-
|
|
370
|
-
# tables that need to be renamed
|
|
371
|
-
cur.execute(
|
|
372
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'lnschema_core_%'"
|
|
373
|
-
)
|
|
374
|
-
tables_to_rename = [
|
|
375
|
-
row[0][len("lnschema_core_") :] for row in cur.fetchall()
|
|
376
|
-
]
|
|
377
|
-
else: # postgres
|
|
378
|
-
cur.execute(
|
|
379
|
-
"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'lamindb_user')"
|
|
380
|
-
)
|
|
381
|
-
migrated = cur.fetchone()[0]
|
|
382
|
-
|
|
383
|
-
# tables that need to be renamed
|
|
384
|
-
cur.execute(
|
|
385
|
-
"SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'lnschema_core_%'"
|
|
386
|
-
)
|
|
387
|
-
tables_to_rename = [
|
|
388
|
-
row[0][len("lnschema_core_") :] for row in cur.fetchall()
|
|
389
|
-
]
|
|
390
|
-
|
|
391
|
-
if migrated:
|
|
392
|
-
if write_file:
|
|
393
|
-
no_lnschema_core_file.touch(exist_ok=True)
|
|
394
|
-
else:
|
|
395
|
-
try:
|
|
396
|
-
response = input(
|
|
397
|
-
f"Do you want to migrate to lamindb 1.0 (integrate lnschema_core into lamindb)? (y/n) -- Will rename {tables_to_rename}"
|
|
398
|
-
)
|
|
399
|
-
if response != "y":
|
|
400
|
-
print("Aborted.")
|
|
401
|
-
quit()
|
|
402
|
-
if isettings.is_on_hub:
|
|
403
|
-
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
404
|
-
from lamindb_setup.core._hub_crud import (
|
|
405
|
-
select_collaborator,
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
# double check that user is an admin, otherwise will fail below
|
|
409
|
-
# due to insufficient SQL permissions with cryptic error
|
|
410
|
-
collaborator = call_with_fallback_auth(
|
|
411
|
-
select_collaborator,
|
|
412
|
-
instance_id=settings.instance._id,
|
|
413
|
-
account_id=settings.user._uuid,
|
|
414
|
-
)
|
|
415
|
-
if collaborator is None or collaborator["role"] != "admin":
|
|
416
|
-
raise SystemExit(
|
|
417
|
-
"❌ Only admins can deploy migrations, please ensure that you're an"
|
|
418
|
-
f" admin: https://lamin.ai/{settings.instance.slug}/settings"
|
|
419
|
-
)
|
|
420
|
-
for table in tables_to_rename:
|
|
421
|
-
if db_type == "sqlite":
|
|
422
|
-
cur.execute(
|
|
423
|
-
f"ALTER TABLE lnschema_core_{table} RENAME TO lamindb_{table}"
|
|
424
|
-
)
|
|
425
|
-
else: # postgres
|
|
426
|
-
cur.execute(
|
|
427
|
-
f"ALTER TABLE lnschema_core_{table} RENAME TO lamindb_{table};"
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
cur.execute(
|
|
431
|
-
"UPDATE django_migrations SET app = 'lamindb' WHERE app = 'lnschema_core'"
|
|
432
|
-
)
|
|
433
|
-
print(
|
|
434
|
-
"Renaming tables finished.\nNow, *please* call: lamin migrate deploy"
|
|
435
|
-
)
|
|
436
|
-
if write_file:
|
|
437
|
-
no_lnschema_core_file.touch(exist_ok=True)
|
|
438
|
-
except Exception:
|
|
439
|
-
# read-only users can't rename tables
|
|
440
|
-
pass
|
|
441
|
-
|
|
442
|
-
conn.commit()
|
|
443
|
-
|
|
444
|
-
except Exception as e:
|
|
445
|
-
print(f"An error occurred: {e}")
|
|
446
|
-
conn.rollback()
|
|
447
|
-
|
|
448
|
-
finally:
|
|
449
|
-
# close the cursor and connection
|
|
450
|
-
cur.close()
|
|
451
|
-
conn.close()
|
|
452
|
-
|
|
453
|
-
|
|
454
306
|
def get_owner_name_from_identifier(identifier: str):
|
|
455
307
|
if "/" in identifier:
|
|
456
308
|
if identifier.startswith("https://lamin.ai/"):
|
lamindb_setup/_init_instance.py
CHANGED
|
@@ -8,7 +8,7 @@ from uuid import UUID
|
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
from django.core.exceptions import FieldError
|
|
11
|
-
from django.db.utils import OperationalError, ProgrammingError
|
|
11
|
+
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
|
12
12
|
from lamin_utils import logger
|
|
13
13
|
|
|
14
14
|
from ._disconnect import disconnect
|
|
@@ -19,12 +19,13 @@ from .core._settings import settings
|
|
|
19
19
|
from .core._settings_instance import is_local_db_url
|
|
20
20
|
from .core._settings_storage import StorageSettings, init_storage
|
|
21
21
|
from .core.upath import UPath
|
|
22
|
+
from .errors import CannotSwitchDefaultInstance
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
25
|
from pydantic import PostgresDsn
|
|
25
26
|
|
|
26
27
|
from .core._settings_user import UserSettings
|
|
27
|
-
from .
|
|
28
|
+
from .types import UPathStr
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class InstanceNotCreated(click.ClickException):
|
|
@@ -49,37 +50,31 @@ def get_schema_module_name(module_name, raise_import_error: bool = True) -> str
|
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def register_storage_in_instance(ssettings: StorageSettings):
|
|
52
|
-
from lamindb.base.users import current_user_id
|
|
53
53
|
from lamindb.models import Storage
|
|
54
54
|
|
|
55
|
-
from .core.hashing import hash_and_encode_as_b62
|
|
56
|
-
|
|
57
|
-
if ssettings._instance_id is not None:
|
|
58
|
-
instance_uid = hash_and_encode_as_b62(ssettings._instance_id.hex)[:12]
|
|
59
|
-
else:
|
|
60
|
-
instance_uid = None
|
|
61
55
|
# how do we ensure that this function is only called passing
|
|
62
56
|
# the managing instance?
|
|
63
|
-
|
|
57
|
+
kwargs = {
|
|
64
58
|
"root": ssettings.root_as_str,
|
|
65
59
|
"type": ssettings.type,
|
|
66
60
|
"region": ssettings.region,
|
|
67
|
-
"instance_uid": instance_uid,
|
|
68
|
-
"created_by_id": current_user_id(),
|
|
61
|
+
"instance_uid": ssettings.instance_uid,
|
|
69
62
|
"run": None,
|
|
63
|
+
"_skip_preparation": True,
|
|
70
64
|
}
|
|
71
65
|
if ssettings._uid is not None:
|
|
72
|
-
|
|
73
|
-
storage
|
|
74
|
-
|
|
75
|
-
defaults=defaults,
|
|
76
|
-
)
|
|
66
|
+
kwargs["uid"] = ssettings._uid
|
|
67
|
+
# this checks if the storage already exists under the hood
|
|
68
|
+
storage = Storage(**kwargs).save()
|
|
77
69
|
return storage
|
|
78
70
|
|
|
79
71
|
|
|
80
|
-
def register_user(usettings):
|
|
72
|
+
def register_user(usettings: UserSettings, update_user: bool = True):
|
|
81
73
|
from lamindb.models import User
|
|
82
74
|
|
|
75
|
+
if not update_user and User.objects.filter(uid=usettings.uid).exists():
|
|
76
|
+
return
|
|
77
|
+
|
|
83
78
|
try:
|
|
84
79
|
# need to have try except because of integer primary key migration
|
|
85
80
|
user, created = User.objects.update_or_create(
|
|
@@ -90,37 +85,38 @@ def register_user(usettings):
|
|
|
90
85
|
},
|
|
91
86
|
)
|
|
92
87
|
# for users with only read access, except via ProgrammingError
|
|
93
|
-
# ProgrammingError: permission denied for table
|
|
94
|
-
|
|
88
|
+
# ProgrammingError: permission denied for table lamindb_user
|
|
89
|
+
# IntegrityError: when trying to update a user on a fine-grained access instance
|
|
90
|
+
except (OperationalError, FieldError, ProgrammingError, IntegrityError):
|
|
95
91
|
pass
|
|
96
92
|
|
|
97
93
|
|
|
98
|
-
def register_initial_records(isettings: InstanceSettings, usettings):
|
|
94
|
+
def register_initial_records(isettings: InstanceSettings, usettings: UserSettings):
|
|
99
95
|
"""Register space, user & storage in DB."""
|
|
100
96
|
from django.db.utils import OperationalError
|
|
101
97
|
from lamindb.models import Branch, Space
|
|
102
98
|
|
|
103
99
|
try:
|
|
104
100
|
Space.objects.get_or_create(
|
|
105
|
-
uid="
|
|
106
|
-
name="
|
|
101
|
+
uid="a",
|
|
102
|
+
name="all",
|
|
107
103
|
description="Every team & user with access to the instance has access.",
|
|
108
104
|
)
|
|
109
105
|
Branch.objects.get_or_create(
|
|
110
106
|
id=-1,
|
|
111
|
-
uid="
|
|
112
|
-
name="
|
|
107
|
+
uid="t",
|
|
108
|
+
name="trash",
|
|
113
109
|
description="The trash.",
|
|
114
110
|
)
|
|
115
111
|
Branch.objects.get_or_create(
|
|
116
112
|
id=0,
|
|
117
|
-
uid="
|
|
118
|
-
name="
|
|
113
|
+
uid="a",
|
|
114
|
+
name="archive",
|
|
119
115
|
description="The archive.",
|
|
120
116
|
)
|
|
121
117
|
Branch.objects.get_or_create(
|
|
122
|
-
uid="
|
|
123
|
-
name="
|
|
118
|
+
uid="m",
|
|
119
|
+
name="main",
|
|
124
120
|
description="The main & default branch of the instance.",
|
|
125
121
|
)
|
|
126
122
|
register_user(usettings)
|
|
@@ -178,7 +174,7 @@ def validate_init_args(
|
|
|
178
174
|
_user: UserSettings | None = None,
|
|
179
175
|
) -> tuple[
|
|
180
176
|
str,
|
|
181
|
-
UUID
|
|
177
|
+
UUID,
|
|
182
178
|
Literal[
|
|
183
179
|
"connected",
|
|
184
180
|
"instance-corrupted-or-deleted",
|
|
@@ -204,33 +200,21 @@ def validate_init_args(
|
|
|
204
200
|
_write_settings=_write_settings,
|
|
205
201
|
_user=_user,
|
|
206
202
|
)
|
|
203
|
+
instance_id: UUID
|
|
207
204
|
instance_state: Literal[
|
|
208
205
|
"connected",
|
|
209
206
|
"instance-corrupted-or-deleted",
|
|
210
207
|
"account-not-exists",
|
|
211
208
|
"instance-not-found",
|
|
212
|
-
]
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
]
|
|
210
|
+
if response is None:
|
|
211
|
+
instance_state, instance_id = "connected", settings.instance._id
|
|
212
|
+
else:
|
|
215
213
|
instance_id, instance_state = process_connect_response(response, instance_slug)
|
|
216
214
|
modules = process_modules_arg(modules)
|
|
217
215
|
return name_str, instance_id, instance_state, instance_slug
|
|
218
216
|
|
|
219
217
|
|
|
220
|
-
class CannotSwitchDefaultInstance(SystemExit):
|
|
221
|
-
pass
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
MESSAGE_CANNOT_SWITCH_DEFAULT_INSTANCE = """
|
|
225
|
-
You cannot write to different instances in the same Python session.
|
|
226
|
-
|
|
227
|
-
Do you want to read from another instance via `SQLRecord.using()`? For example:
|
|
228
|
-
|
|
229
|
-
ln.Artifact.using("laminlabs/cellxgene").filter()
|
|
230
|
-
|
|
231
|
-
Or do you want to switch off auto-connect via `lamin settings set auto-connect false`?
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
218
|
DOC_STORAGE_ARG = "A local or remote folder (`'s3://...'` or `'gs://...'`). Defaults to current working directory."
|
|
235
219
|
DOC_INSTANCE_NAME = (
|
|
236
220
|
"Instance name. If not passed, it will equal the folder name passed to `storage`."
|
|
@@ -278,7 +262,9 @@ def init(
|
|
|
278
262
|
from ._check_setup import _check_instance_setup
|
|
279
263
|
|
|
280
264
|
if _check_instance_setup() and not _test:
|
|
281
|
-
|
|
265
|
+
from lamindb_setup.core.django import reset_django
|
|
266
|
+
|
|
267
|
+
reset_django()
|
|
282
268
|
elif _write_settings:
|
|
283
269
|
disconnect(mute=True)
|
|
284
270
|
from .core._hub_core import init_instance_hub
|
|
@@ -296,13 +282,11 @@ def init(
|
|
|
296
282
|
if _write_settings:
|
|
297
283
|
settings.auto_connect = True # we can also debate this switch here
|
|
298
284
|
return None
|
|
299
|
-
# the conditions here match `isettings.is_remote`, but I currently don't
|
|
300
|
-
# see a way of making this more elegant; should become possible if we
|
|
301
|
-
# remove the instance.storage_id FK on the hub
|
|
302
285
|
prevent_register_hub = is_local_db_url(db) if db is not None else False
|
|
303
286
|
ssettings, _ = init_storage(
|
|
304
287
|
storage,
|
|
305
288
|
instance_id=instance_id,
|
|
289
|
+
instance_slug=f"{user_handle}/{name_str}",
|
|
306
290
|
init_instance=True,
|
|
307
291
|
prevent_register_hub=prevent_register_hub,
|
|
308
292
|
created_by=user__uuid,
|
|
@@ -370,7 +354,7 @@ def init(
|
|
|
370
354
|
and (user_handle != "anonymous" or access_token is not None)
|
|
371
355
|
and ssettings.is_on_hub
|
|
372
356
|
):
|
|
373
|
-
delete_storage_record(ssettings
|
|
357
|
+
delete_storage_record(ssettings, access_token=access_token) # type: ignore
|
|
374
358
|
if isettings is not None:
|
|
375
359
|
if (
|
|
376
360
|
user_handle != "anonymous" or access_token is not None
|
|
@@ -404,7 +388,9 @@ def load_from_isettings(
|
|
|
404
388
|
# this is our best proxy for that the user might not
|
|
405
389
|
# yet be registered
|
|
406
390
|
if not isettings._get_settings_file().exists():
|
|
407
|
-
|
|
391
|
+
# do not try to update the user on fine grained access instances
|
|
392
|
+
# this is blocked anyways, only select and insert are allowed
|
|
393
|
+
register_user(user, update_user=not isettings._fine_grained_access)
|
|
408
394
|
isettings._persist(write_to_disk=write_settings)
|
|
409
395
|
|
|
410
396
|
|
|
@@ -12,7 +12,7 @@ def register(_test: bool = False):
|
|
|
12
12
|
from ._check_setup import _check_instance_setup
|
|
13
13
|
from .core._hub_core import init_instance_hub, init_storage_hub
|
|
14
14
|
|
|
15
|
-
logger.warning("
|
|
15
|
+
logger.warning("note that register() is only for testing purposes")
|
|
16
16
|
|
|
17
17
|
isettings = settings.instance
|
|
18
18
|
if not _check_instance_setup() and not _test:
|
|
@@ -16,6 +16,7 @@ from django.db.models import (
|
|
|
16
16
|
OneToOneField,
|
|
17
17
|
OneToOneRel,
|
|
18
18
|
)
|
|
19
|
+
from lamin_utils import logger
|
|
19
20
|
from pydantic import BaseModel
|
|
20
21
|
|
|
21
22
|
from lamindb_setup import settings
|
|
@@ -25,6 +26,9 @@ from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
|
25
26
|
# surpress pydantic warning about `model_` namespace
|
|
26
27
|
try:
|
|
27
28
|
BaseModel.model_config["protected_namespaces"] = ()
|
|
29
|
+
logger.debug(
|
|
30
|
+
"pydantic.BaseModel.model_config['protected_namespaces'] has been set to ()"
|
|
31
|
+
)
|
|
28
32
|
except Exception:
|
|
29
33
|
pass
|
|
30
34
|
|
|
@@ -10,7 +10,7 @@ from .core._settings import settings
|
|
|
10
10
|
from .core._settings_storage import init_storage
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
|
-
from lamindb_setup.
|
|
13
|
+
from lamindb_setup.types import UPathStr
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def set_managed_storage(root: UPathStr, **fs_kwargs):
|
|
@@ -33,7 +33,10 @@ def set_managed_storage(root: UPathStr, **fs_kwargs):
|
|
|
33
33
|
# hub_record_status="hub-record-created" if a new record is created
|
|
34
34
|
# "hub-record-retrieved" if the storage is in the hub already
|
|
35
35
|
ssettings, hub_record_status = init_storage(
|
|
36
|
-
root=root,
|
|
36
|
+
root=root,
|
|
37
|
+
instance_id=settings.instance._id,
|
|
38
|
+
instance_slug=settings.instance.slug,
|
|
39
|
+
register_hub=True,
|
|
37
40
|
)
|
|
38
41
|
if ssettings._instance_id is None:
|
|
39
42
|
raise ValueError(
|
|
@@ -47,9 +50,8 @@ def set_managed_storage(root: UPathStr, **fs_kwargs):
|
|
|
47
50
|
register_storage_in_instance(ssettings)
|
|
48
51
|
except Exception as e:
|
|
49
52
|
if hub_record_status == "hub-record-created" and ssettings._uuid is not None:
|
|
50
|
-
delete_storage_record(ssettings
|
|
53
|
+
delete_storage_record(ssettings)
|
|
51
54
|
raise e
|
|
52
55
|
|
|
53
56
|
settings.instance._storage = ssettings
|
|
54
|
-
settings.instance._persist() # this also updates the settings object
|
|
55
57
|
settings.storage._set_fs_kwargs(**fs_kwargs)
|