lamindb_setup 1.15.2__py3-none-any.whl → 1.17.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 +9 -23
- lamindb_setup/_delete.py +10 -5
- lamindb_setup/_disconnect.py +12 -9
- lamindb_setup/_init_instance.py +0 -1
- lamindb_setup/_migrate.py +0 -14
- lamindb_setup/_schema_metadata.py +9 -11
- lamindb_setup/_setup_user.py +28 -7
- lamindb_setup/_silence_loggers.py +2 -0
- lamindb_setup/core/_aws_options.py +17 -7
- lamindb_setup/core/_clone.py +1 -1
- lamindb_setup/core/_hub_client.py +1 -2
- lamindb_setup/core/_hub_core.py +9 -6
- lamindb_setup/core/_private_django_api.py +0 -1
- lamindb_setup/core/_settings.py +14 -10
- lamindb_setup/core/_settings_instance.py +28 -6
- lamindb_setup/core/_settings_load.py +25 -7
- lamindb_setup/core/_settings_storage.py +3 -1
- lamindb_setup/core/django.py +48 -18
- lamindb_setup/core/lamin.db.gz +0 -0
- lamindb_setup/core/upath.py +34 -13
- lamindb_setup/errors.py +0 -12
- lamindb_setup/io.py +69 -33
- {lamindb_setup-1.15.2.dist-info → lamindb_setup-1.17.0.dist-info}/METADATA +5 -5
- lamindb_setup-1.17.0.dist-info/RECORD +51 -0
- {lamindb_setup-1.15.2.dist-info → lamindb_setup-1.17.0.dist-info}/WHEEL +1 -1
- lamindb_setup-1.15.2.dist-info/RECORD +0 -50
- {lamindb_setup-1.15.2.dist-info → lamindb_setup-1.17.0.dist-info/licenses}/LICENSE +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.17.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
|
|
@@ -266,13 +263,8 @@ def validate_connection_state(
|
|
|
266
263
|
from django.db import connection
|
|
267
264
|
|
|
268
265
|
if (
|
|
269
|
-
settings._instance_exists
|
|
266
|
+
settings._instance_exists # exists only for real instances, not for none/none
|
|
270
267
|
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
268
|
and not use_root_db_user # always re-connect for root db user
|
|
277
269
|
):
|
|
278
270
|
logger.important(
|
|
@@ -280,7 +272,7 @@ def validate_connection_state(
|
|
|
280
272
|
)
|
|
281
273
|
return None
|
|
282
274
|
else:
|
|
283
|
-
if settings._instance_exists
|
|
275
|
+
if settings._instance_exists:
|
|
284
276
|
import lamindb as ln
|
|
285
277
|
|
|
286
278
|
if ln.context.transform is not None:
|
|
@@ -292,7 +284,9 @@ def validate_connection_state(
|
|
|
292
284
|
|
|
293
285
|
@unlock_cloud_sqlite_upon_exception(ignore_prev_locker=True)
|
|
294
286
|
def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
295
|
-
"""Connect
|
|
287
|
+
"""Connect the global default instance.
|
|
288
|
+
|
|
289
|
+
If you want to create a read-only database client, use :class:`~lamindb.DB` instead.
|
|
296
290
|
|
|
297
291
|
Args:
|
|
298
292
|
instance: Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
|
|
@@ -340,12 +334,9 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
340
334
|
if settings._instance_exists:
|
|
341
335
|
isettings = settings.instance
|
|
342
336
|
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
|
|
337
|
+
raise ValueError(
|
|
338
|
+
"No instance was connected through the CLI, pass a value to `instance` or connect via the CLI."
|
|
339
|
+
)
|
|
349
340
|
if use_root_db_user:
|
|
350
341
|
reset_django()
|
|
351
342
|
owner, name = isettings.owner, isettings.name
|
|
@@ -414,7 +405,6 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
414
405
|
|
|
415
406
|
load_from_isettings(isettings, user=_user, write_settings=_write_settings)
|
|
416
407
|
if _reload_lamindb:
|
|
417
|
-
importlib.reload(importlib.import_module("lamindb"))
|
|
418
408
|
reset_django_module_variables()
|
|
419
409
|
if isettings.slug != "none/none":
|
|
420
410
|
logger.important(f"connected lamindb: {isettings.slug}")
|
|
@@ -424,10 +414,6 @@ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
|
|
|
424
414
|
isettings._get_settings_file().unlink(missing_ok=True) # type: ignore
|
|
425
415
|
settings._instance_settings = None
|
|
426
416
|
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
417
|
return None
|
|
432
418
|
|
|
433
419
|
|
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
|
@@ -125,24 +125,10 @@ class migrate:
|
|
|
125
125
|
from lamindb_setup._schema_metadata import update_schema_in_hub
|
|
126
126
|
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
127
127
|
from lamindb_setup.core._hub_crud import (
|
|
128
|
-
select_collaborator,
|
|
129
128
|
update_instance,
|
|
130
129
|
)
|
|
131
130
|
|
|
132
131
|
if settings.instance.is_on_hub:
|
|
133
|
-
# double check that user is an admin, otherwise will fail below
|
|
134
|
-
# due to insufficient SQL permissions with cryptic error
|
|
135
|
-
collaborator = call_with_fallback_auth(
|
|
136
|
-
select_collaborator,
|
|
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
|
-
)
|
|
146
132
|
# ensure we connect with the root user
|
|
147
133
|
if "root" not in settings.instance.db:
|
|
148
134
|
connect(use_root_db_user=True)
|
|
@@ -172,7 +172,8 @@ class _ModelHandler:
|
|
|
172
172
|
self.table_name = model._meta.db_table
|
|
173
173
|
self.included_modules = included_modules
|
|
174
174
|
self.fields = self._get_fields_metadata(self.model)
|
|
175
|
-
self.
|
|
175
|
+
self.is_auto_created = bool(model._meta.auto_created)
|
|
176
|
+
self.is_link_table = issubclass(model, IsLink) or self.is_auto_created
|
|
176
177
|
self.name_field = model._name_field if hasattr(model, "_name_field") else None
|
|
177
178
|
self.ontology_id_field = (
|
|
178
179
|
model._ontology_id_field if hasattr(model, "_ontology_id_field") else None
|
|
@@ -183,6 +184,7 @@ class _ModelHandler:
|
|
|
183
184
|
"fields": self.fields.copy(),
|
|
184
185
|
"class_name": self.class_name,
|
|
185
186
|
"table_name": self.table_name,
|
|
187
|
+
"is_auto_created": self.is_auto_created,
|
|
186
188
|
"is_link_table": self.is_link_table,
|
|
187
189
|
"name_field": self.name_field,
|
|
188
190
|
"ontology_id_field": self.ontology_id_field,
|
|
@@ -249,13 +251,13 @@ class _ModelHandler:
|
|
|
249
251
|
return related_fields
|
|
250
252
|
|
|
251
253
|
def _get_field_metadata(self, model, field: Field):
|
|
252
|
-
from lamindb.models import IsLink
|
|
254
|
+
from lamindb.models import IsLink, Registry
|
|
253
255
|
|
|
254
256
|
internal_type = field.get_internal_type()
|
|
255
257
|
model_name = field.model._meta.model_name
|
|
256
258
|
relation_type = self._get_relation_type(model, field)
|
|
257
259
|
|
|
258
|
-
schema_name = field.model
|
|
260
|
+
schema_name = Registry.__get_module_name__(field.model)
|
|
259
261
|
|
|
260
262
|
if field.related_model is None:
|
|
261
263
|
related_model_name = None
|
|
@@ -265,7 +267,7 @@ class _ModelHandler:
|
|
|
265
267
|
max_length = field.max_length
|
|
266
268
|
else:
|
|
267
269
|
related_model_name = field.related_model._meta.model_name
|
|
268
|
-
related_schema_name = field.related_model
|
|
270
|
+
related_schema_name = Registry.__get_module_name__(field.related_model)
|
|
269
271
|
related_field_name = field.remote_field.name
|
|
270
272
|
is_editable = False
|
|
271
273
|
max_length = None
|
|
@@ -418,14 +420,10 @@ class _SchemaHandler:
|
|
|
418
420
|
all_models = {module_name: {} for module_name in self.included_modules}
|
|
419
421
|
|
|
420
422
|
# Iterate through all registered Django models
|
|
421
|
-
for model in apps.get_models():
|
|
423
|
+
for model in apps.get_models(include_auto_created=True):
|
|
422
424
|
# Check if model meets the criteria
|
|
423
|
-
if
|
|
424
|
-
|
|
425
|
-
and model is not SQLRecord
|
|
426
|
-
and not model._meta.abstract
|
|
427
|
-
):
|
|
428
|
-
module_name = model.__get_module_name__()
|
|
425
|
+
if model is not SQLRecord and not model._meta.abstract:
|
|
426
|
+
module_name = Registry.__get_module_name__(model)
|
|
429
427
|
# Only include if module is in our included list
|
|
430
428
|
if module_name in self.included_modules:
|
|
431
429
|
model_name = model._meta.model_name
|
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
|
|
@@ -43,6 +44,14 @@ def load_user(email: str | None = None, handle: str | None = None) -> UserSettin
|
|
|
43
44
|
return user_settings
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
def current_user_uid() -> str:
|
|
48
|
+
current_user_settings = current_user_settings_file()
|
|
49
|
+
if current_user_settings.exists():
|
|
50
|
+
return load_user_settings(current_user_settings).uid
|
|
51
|
+
|
|
52
|
+
return "00000000" # anonymous
|
|
53
|
+
|
|
54
|
+
|
|
46
55
|
def login(
|
|
47
56
|
user: str | None = None, *, api_key: str | None = None, **kwargs
|
|
48
57
|
) -> UserSettings:
|
|
@@ -51,15 +60,14 @@ def login(
|
|
|
51
60
|
|
|
52
61
|
`login()` prompts for your API key unless you set it via the `LAMIN_API_KEY` environment variable or pass it as an argument.
|
|
53
62
|
|
|
54
|
-
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>`__.
|
|
55
66
|
|
|
56
67
|
Args:
|
|
57
68
|
user: User handle.
|
|
58
69
|
api_key: API key.
|
|
59
70
|
|
|
60
|
-
See Also:
|
|
61
|
-
Login via the CLI command `lamin login`, see `here <https://docs.lamin.ai/cli#login>`__.
|
|
62
|
-
|
|
63
71
|
Examples:
|
|
64
72
|
|
|
65
73
|
Logging in the first time::
|
|
@@ -72,12 +80,15 @@ def login(
|
|
|
72
80
|
|
|
73
81
|
ln.setup.login("myhandle") # pass your user handle
|
|
74
82
|
"""
|
|
83
|
+
from getpass import getpass
|
|
84
|
+
|
|
75
85
|
if user is None:
|
|
76
86
|
if api_key is None:
|
|
77
87
|
if "LAMIN_API_KEY" in os.environ:
|
|
78
88
|
api_key = os.environ["LAMIN_API_KEY"]
|
|
79
89
|
else:
|
|
80
|
-
|
|
90
|
+
print("Copy your API key. To create one: https://lamin.ai/settings")
|
|
91
|
+
api_key = getpass("Paste it: ")
|
|
81
92
|
elif api_key is not None:
|
|
82
93
|
raise ValueError("Please provide either 'user' or 'api_key', not both.")
|
|
83
94
|
|
|
@@ -90,6 +101,9 @@ def login(
|
|
|
90
101
|
"the legacy API key is deprecated and will likely be removed in a future version"
|
|
91
102
|
)
|
|
92
103
|
|
|
104
|
+
# do this here because load_user overwrites current_user_settings_file
|
|
105
|
+
previous_user_uid = current_user_uid()
|
|
106
|
+
|
|
93
107
|
if api_key is None:
|
|
94
108
|
if "@" in user: # type: ignore
|
|
95
109
|
email, handle = user, None
|
|
@@ -144,8 +158,15 @@ def login(
|
|
|
144
158
|
user_settings.api_key = api_key
|
|
145
159
|
save_user_settings(user_settings)
|
|
146
160
|
|
|
147
|
-
if settings._instance_exists
|
|
148
|
-
|
|
161
|
+
if settings._instance_exists:
|
|
162
|
+
if (
|
|
163
|
+
isettings := settings.instance
|
|
164
|
+
).is_on_hub and previous_user_uid != user_settings.uid:
|
|
165
|
+
logger.important_hint(
|
|
166
|
+
f"consider re-connecting to update permissions: lamin connect {isettings.slug}"
|
|
167
|
+
)
|
|
168
|
+
if _check_instance_setup():
|
|
169
|
+
register_user(user_settings)
|
|
149
170
|
|
|
150
171
|
settings._user_settings = None
|
|
151
172
|
# aws s3 credentials are scoped to the user
|
|
@@ -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
|
|
|
@@ -60,6 +60,7 @@ class AWSOptionsManager:
|
|
|
60
60
|
from aiobotocore.session import AioSession
|
|
61
61
|
from s3fs import S3FileSystem
|
|
62
62
|
|
|
63
|
+
anon_env = os.getenv("LAMIN_S3_ANON") == "true"
|
|
63
64
|
# this is cached so will be resued with the connection initialized
|
|
64
65
|
# these options are set for paths in _path_inject_options
|
|
65
66
|
# here we set the same options to cache the filesystem
|
|
@@ -68,19 +69,28 @@ class AWSOptionsManager:
|
|
|
68
69
|
use_listings_cache=True,
|
|
69
70
|
version_aware=False,
|
|
70
71
|
config_kwargs={"max_pool_connections": 64},
|
|
72
|
+
anon=anon_env,
|
|
71
73
|
)
|
|
72
74
|
|
|
73
75
|
self._suppress_aiobotocore_traceback_logging()
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
self.anon: bool = fs.session._credentials is None
|
|
78
|
-
except Exception as e:
|
|
77
|
+
if anon_env:
|
|
78
|
+
self.anon: bool = True
|
|
79
79
|
logger.warning(
|
|
80
|
-
|
|
81
|
-
"
|
|
80
|
+
"`anon` mode will be used for all non-managed buckets "
|
|
81
|
+
"because the environment variable LAMIN_S3_ANON was set to 'true'"
|
|
82
82
|
)
|
|
83
|
-
|
|
83
|
+
else:
|
|
84
|
+
try:
|
|
85
|
+
fs.connect()
|
|
86
|
+
self.anon = fs.session._credentials is None
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.warning(
|
|
89
|
+
f"There is a problem with your default AWS Credentials: {e}\n"
|
|
90
|
+
"`anon` mode will be used for all non-managed buckets"
|
|
91
|
+
)
|
|
92
|
+
self.anon = True
|
|
93
|
+
|
|
84
94
|
self.anon_public: bool | None = None
|
|
85
95
|
if not self.anon:
|
|
86
96
|
try:
|
lamindb_setup/core/_clone.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
init_local_sqlite
|
|
7
7
|
connect_local_sqlite
|
|
8
|
+
connect_remote_sqlite
|
|
8
9
|
upload_sqlite_clone
|
|
9
10
|
"""
|
|
10
11
|
|
|
@@ -13,7 +14,6 @@ import os
|
|
|
13
14
|
import shutil
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
|
|
16
|
-
from lamindb_setup.core._settings_instance import InstanceSettings
|
|
17
17
|
from lamindb_setup.core._settings_load import load_instance_settings
|
|
18
18
|
from lamindb_setup.core._settings_store import instance_settings_file
|
|
19
19
|
from lamindb_setup.core.django import reset_django
|
|
@@ -11,8 +11,7 @@ import httpx
|
|
|
11
11
|
from httpx_retries import Retry, RetryTransport
|
|
12
12
|
from lamin_utils import logger
|
|
13
13
|
from pydantic_settings import BaseSettings
|
|
14
|
-
from supabase import Client, create_client
|
|
15
|
-
from supabase.lib.client_options import ClientOptions
|
|
14
|
+
from supabase import Client, ClientOptions, create_client
|
|
16
15
|
|
|
17
16
|
from ._settings_save import save_user_settings
|
|
18
17
|
|
lamindb_setup/core/_hub_core.py
CHANGED
|
@@ -383,7 +383,8 @@ def _init_instance_hub(
|
|
|
383
383
|
) -> None:
|
|
384
384
|
from ._settings import settings
|
|
385
385
|
|
|
386
|
-
|
|
386
|
+
created_by_id = settings.user._uuid.hex if account_id is None else account_id.hex # type: ignore
|
|
387
|
+
owner_account_id = os.getenv("LAMINDB_ACCOUNT_ID_INIT", created_by_id)
|
|
387
388
|
|
|
388
389
|
try:
|
|
389
390
|
lamindb_version = metadata.version("lamindb")
|
|
@@ -391,13 +392,13 @@ def _init_instance_hub(
|
|
|
391
392
|
lamindb_version = None
|
|
392
393
|
fields = {
|
|
393
394
|
"id": isettings._id.hex,
|
|
394
|
-
"account_id":
|
|
395
|
+
"account_id": owner_account_id,
|
|
395
396
|
"name": isettings.name,
|
|
396
397
|
"lnid": isettings.uid,
|
|
397
398
|
"schema_str": isettings._schema_str,
|
|
398
399
|
"lamindb_version": lamindb_version,
|
|
399
400
|
"public": False,
|
|
400
|
-
"created_by_id":
|
|
401
|
+
"created_by_id": created_by_id,
|
|
401
402
|
}
|
|
402
403
|
if isettings.dialect != "sqlite":
|
|
403
404
|
db_dsn = LaminDsnModel(db=isettings.db)
|
|
@@ -407,7 +408,7 @@ def _init_instance_hub(
|
|
|
407
408
|
"db_port": db_dsn.db.port,
|
|
408
409
|
"db_database": db_dsn.db.database,
|
|
409
410
|
}
|
|
410
|
-
fields.update(db_fields)
|
|
411
|
+
fields.update(db_fields) # type: ignore
|
|
411
412
|
slug = isettings.slug
|
|
412
413
|
# I'd like the following to be an upsert, but this seems to violate RLS
|
|
413
414
|
# Similarly, if we don't specify `returning="minimal"`, we'll violate RLS
|
|
@@ -415,7 +416,9 @@ def _init_instance_hub(
|
|
|
415
416
|
# as then init_instance is no longer idempotent
|
|
416
417
|
try:
|
|
417
418
|
client.table("instance").insert(fields, returning="minimal").execute()
|
|
418
|
-
except APIError:
|
|
419
|
+
except APIError as e:
|
|
420
|
+
if "new row violates row-level security policy" in str(e):
|
|
421
|
+
raise e
|
|
419
422
|
logger.warning(f"instance already existed at: https://lamin.ai/{slug}")
|
|
420
423
|
return None
|
|
421
424
|
if isettings.dialect != "sqlite" and isettings.is_remote:
|
|
@@ -713,7 +716,7 @@ def get_lamin_site_base_url():
|
|
|
713
716
|
|
|
714
717
|
|
|
715
718
|
def sign_up_local_hub(email) -> str | tuple[str, str, str]:
|
|
716
|
-
# raises
|
|
719
|
+
# raises AuthApiError: User already registered
|
|
717
720
|
password = base62(40) # generate new password
|
|
718
721
|
sign_up_kwargs = {"email": email, "password": password}
|
|
719
722
|
client = connect_hub()
|