lamindb_setup 0.77.4__py2.py3-none-any.whl → 0.77.6__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.
Files changed (47) hide show
  1. lamindb_setup/__init__.py +1 -2
  2. lamindb_setup/_cache.py +34 -34
  3. lamindb_setup/_check.py +7 -7
  4. lamindb_setup/_check_setup.py +92 -79
  5. lamindb_setup/_close.py +35 -35
  6. lamindb_setup/_connect_instance.py +425 -444
  7. lamindb_setup/_django.py +41 -41
  8. lamindb_setup/_entry_points.py +22 -22
  9. lamindb_setup/_exportdb.py +68 -68
  10. lamindb_setup/_importdb.py +50 -50
  11. lamindb_setup/_init_instance.py +411 -374
  12. lamindb_setup/_migrate.py +239 -239
  13. lamindb_setup/_register_instance.py +36 -36
  14. lamindb_setup/_schema.py +27 -27
  15. lamindb_setup/_schema_metadata.py +411 -411
  16. lamindb_setup/_set_managed_storage.py +55 -55
  17. lamindb_setup/_setup_user.py +137 -137
  18. lamindb_setup/_silence_loggers.py +44 -44
  19. lamindb_setup/core/__init__.py +21 -21
  20. lamindb_setup/core/_aws_credentials.py +151 -151
  21. lamindb_setup/core/_aws_storage.py +48 -48
  22. lamindb_setup/core/_deprecated.py +55 -55
  23. lamindb_setup/core/_docs.py +14 -14
  24. lamindb_setup/core/_hub_client.py +1 -1
  25. lamindb_setup/core/_hub_core.py +615 -590
  26. lamindb_setup/core/_hub_crud.py +211 -211
  27. lamindb_setup/core/_hub_utils.py +109 -109
  28. lamindb_setup/core/_private_django_api.py +88 -88
  29. lamindb_setup/core/_settings.py +145 -138
  30. lamindb_setup/core/_settings_instance.py +480 -467
  31. lamindb_setup/core/_settings_load.py +105 -105
  32. lamindb_setup/core/_settings_save.py +81 -81
  33. lamindb_setup/core/_settings_storage.py +412 -405
  34. lamindb_setup/core/_settings_store.py +75 -75
  35. lamindb_setup/core/_settings_user.py +55 -53
  36. lamindb_setup/core/_setup_bionty_sources.py +101 -101
  37. lamindb_setup/core/cloud_sqlite_locker.py +237 -232
  38. lamindb_setup/core/django.py +115 -114
  39. lamindb_setup/core/exceptions.py +12 -12
  40. lamindb_setup/core/hashing.py +114 -114
  41. lamindb_setup/core/types.py +19 -19
  42. lamindb_setup/core/upath.py +779 -779
  43. {lamindb_setup-0.77.4.dist-info → lamindb_setup-0.77.6.dist-info}/METADATA +3 -2
  44. lamindb_setup-0.77.6.dist-info/RECORD +47 -0
  45. {lamindb_setup-0.77.4.dist-info → lamindb_setup-0.77.6.dist-info}/WHEEL +1 -1
  46. lamindb_setup-0.77.4.dist-info/RECORD +0 -47
  47. {lamindb_setup-0.77.4.dist-info → lamindb_setup-0.77.6.dist-info}/LICENSE +0 -0
@@ -1,374 +1,411 @@
1
- from __future__ import annotations
2
-
3
- import importlib
4
- import os
5
- import sys
6
- import uuid
7
- from typing import TYPE_CHECKING, Literal
8
- from uuid import UUID
9
-
10
- from django.core.exceptions import FieldError
11
- from django.db.utils import OperationalError, ProgrammingError
12
- from lamin_utils import logger
13
-
14
- from ._close import close as close_instance
15
- from ._silence_loggers import silence_loggers
16
- from .core import InstanceSettings
17
- from .core._settings import settings
18
- from .core._settings_instance import is_local_db_url
19
- from .core._settings_storage import StorageSettings, init_storage
20
- from .core.upath import UPath
21
-
22
- if TYPE_CHECKING:
23
- from pydantic import PostgresDsn
24
-
25
- from .core.types import UPathStr
26
-
27
-
28
- def get_schema_module_name(schema_name) -> str:
29
- import importlib.util
30
-
31
- name_attempts = [f"lnschema_{schema_name.replace('-', '_')}", schema_name]
32
- for name in name_attempts:
33
- module_spec = importlib.util.find_spec(name)
34
- if module_spec is not None:
35
- return name
36
- raise ImportError(
37
- f"Python package for '{schema_name}' is not installed.\nIf your package is on PyPI, run `pip install {schema_name}`"
38
- )
39
-
40
-
41
- def register_storage_in_instance(ssettings: StorageSettings):
42
- from lnschema_core.models import Storage
43
- from lnschema_core.users import current_user_id
44
-
45
- from .core.hashing import hash_and_encode_as_b62
46
-
47
- if ssettings._instance_id is not None:
48
- instance_uid = hash_and_encode_as_b62(ssettings._instance_id.hex)[:12]
49
- else:
50
- instance_uid = None
51
- # how do we ensure that this function is only called passing
52
- # the managing instance?
53
- defaults = {
54
- "root": ssettings.root_as_str,
55
- "type": ssettings.type,
56
- "region": ssettings.region,
57
- "instance_uid": instance_uid,
58
- "created_by_id": current_user_id(),
59
- "run": None,
60
- }
61
- if ssettings._uid is not None:
62
- defaults["uid"] = ssettings._uid
63
- storage, _ = Storage.objects.update_or_create(
64
- root=ssettings.root_as_str,
65
- defaults=defaults,
66
- )
67
- return storage
68
-
69
-
70
- def register_user(usettings):
71
- from lnschema_core.models import User
72
-
73
- try:
74
- # need to have try except because of integer primary key migration
75
- user, created = User.objects.update_or_create(
76
- uid=usettings.uid,
77
- defaults={
78
- "handle": usettings.handle,
79
- "name": usettings.name,
80
- },
81
- )
82
- # for users with only read access, except via ProgrammingError
83
- # ProgrammingError: permission denied for table lnschema_core_user
84
- except (OperationalError, FieldError, ProgrammingError):
85
- pass
86
-
87
-
88
- def register_user_and_storage_in_instance(isettings: InstanceSettings, usettings):
89
- """Register user & storage in DB."""
90
- from django.db.utils import OperationalError
91
-
92
- try:
93
- register_user(usettings)
94
- register_storage_in_instance(isettings.storage)
95
- except OperationalError as error:
96
- logger.warning(f"instance seems not set up ({error})")
97
-
98
-
99
- def reload_schema_modules(isettings: InstanceSettings):
100
- schema_names = ["core"] + list(isettings.schema)
101
- schema_module_names = [get_schema_module_name(n) for n in schema_names]
102
-
103
- for schema_module_name in schema_module_names:
104
- if schema_module_name in sys.modules:
105
- schema_module = importlib.import_module(schema_module_name)
106
- importlib.reload(schema_module)
107
-
108
-
109
- def reload_lamindb_itself(isettings) -> bool:
110
- reloaded = False
111
- if "lamindb" in sys.modules:
112
- import lamindb
113
-
114
- importlib.reload(lamindb)
115
- reloaded = True
116
- if "bionty" in isettings.schema and "bionty" in sys.modules:
117
- schema_module = importlib.import_module("bionty")
118
- importlib.reload(schema_module)
119
- reloaded = True
120
- return reloaded
121
-
122
-
123
- def reload_lamindb(isettings: InstanceSettings):
124
- # only touch lamindb if we're operating from lamindb
125
- reload_schema_modules(isettings)
126
- log_message = settings.auto_connect
127
- if not reload_lamindb_itself(isettings):
128
- log_message = True
129
- if log_message:
130
- logger.important(f"connected lamindb: {isettings.slug}")
131
-
132
-
133
- ERROR_SQLITE_CACHE = """
134
- Your cached local SQLite file exists, while your cloud SQLite file ({}) doesn't.
135
- Either delete your cache ({}) or add it back to the cloud (if delete was accidental).
136
- """
137
-
138
-
139
- def process_connect_response(
140
- response: tuple | str, instance_identifier: str
141
- ) -> tuple[
142
- UUID,
143
- Literal[
144
- "instance-corrupted-or-deleted", "account-not-exists", "instance-not-found"
145
- ],
146
- ]:
147
- # for internal use when creating instances through CICD
148
- if isinstance(response, tuple) and response[0] == "instance-corrupted-or-deleted":
149
- hub_result = response[1]
150
- instance_state = response[0]
151
- instance_id = UUID(hub_result["id"])
152
- else:
153
- instance_id_str = os.getenv("LAMINDB_INSTANCE_ID_INIT")
154
- if instance_id_str is None:
155
- instance_id = uuid.uuid5(uuid.NAMESPACE_URL, instance_identifier)
156
- else:
157
- instance_id = UUID(instance_id_str)
158
- instance_state = response
159
- return instance_id, instance_state
160
-
161
-
162
- def validate_init_args(
163
- *,
164
- storage: UPathStr,
165
- name: str | None = None,
166
- db: PostgresDsn | None = None,
167
- schema: str | None = None,
168
- _test: bool = False,
169
- ) -> tuple[
170
- str,
171
- UUID | None,
172
- Literal[
173
- "connected",
174
- "instance-corrupted-or-deleted",
175
- "account-not-exists",
176
- "instance-not-found",
177
- ],
178
- str,
179
- ]:
180
- from ._connect_instance import connect
181
- from .core._hub_utils import (
182
- validate_schema_arg,
183
- )
184
-
185
- # should be called as the first thing
186
- name_str = infer_instance_name(storage=storage, name=name, db=db)
187
- # test whether instance exists by trying to load it
188
- instance_slug = f"{settings.user.handle}/{name_str}"
189
- response = connect(instance_slug, db=db, _raise_not_found_error=False, _test=_test)
190
- instance_state: Literal[
191
- "connected",
192
- "instance-corrupted-or-deleted",
193
- "account-not-exists",
194
- "instance-not-found",
195
- ] = "connected"
196
- instance_id = None
197
- if response is not None:
198
- instance_id, instance_state = process_connect_response(response, instance_slug)
199
- schema = validate_schema_arg(schema)
200
- return name_str, instance_id, instance_state, instance_slug
201
-
202
-
203
- MESSAGE_NO_MULTIPLE_INSTANCE = """
204
- Currently don't support subsequent connection to different databases in the same
205
- Python session.\n
206
- Try running on the CLI: lamin set auto-connect false
207
- """
208
-
209
-
210
- def init(
211
- *,
212
- storage: UPathStr,
213
- name: str | None = None,
214
- db: PostgresDsn | None = None,
215
- schema: str | None = None,
216
- _test: bool = False,
217
- ) -> None:
218
- """Create and load a LaminDB instance.
219
-
220
- Args:
221
- storage: Either ``"create-s3"``, local or
222
- remote folder (``"s3://..."`` or ``"gs://..."``).
223
- name: Instance name.
224
- db: Database connection url, do not pass for SQLite.
225
- schema: Comma-separated string of schema modules. None if not set.
226
- """
227
- isettings = None
228
- ssettings = None
229
- try:
230
- silence_loggers()
231
- from ._check_setup import _check_instance_setup
232
-
233
- if _check_instance_setup() and not _test:
234
- raise RuntimeError(MESSAGE_NO_MULTIPLE_INSTANCE)
235
- else:
236
- close_instance(mute=True)
237
- from .core._hub_core import init_instance as init_instance_hub
238
-
239
- name_str, instance_id, instance_state, _ = validate_init_args(
240
- storage=storage,
241
- name=name,
242
- db=db,
243
- schema=schema,
244
- _test=_test,
245
- )
246
- if instance_state == "connected":
247
- settings.auto_connect = True # we can also debate this switch here
248
- return None
249
- # the conditions here match `isettings.is_remote`, but I currently don't
250
- # see a way of making this more elegant; should become possible if we
251
- # remove the instance.storage_id FK on the hub
252
- prevent_register_hub = is_local_db_url(db) if db is not None else False
253
- ssettings, _ = init_storage(
254
- storage,
255
- instance_id=instance_id,
256
- init_instance=True,
257
- prevent_register_hub=prevent_register_hub,
258
- )
259
- isettings = InstanceSettings(
260
- id=instance_id, # type: ignore
261
- owner=settings.user.handle,
262
- name=name_str,
263
- storage=ssettings,
264
- db=db,
265
- schema=schema,
266
- uid=ssettings.uid,
267
- )
268
- register_on_hub = (
269
- isettings.is_remote and instance_state != "instance-corrupted-or-deleted"
270
- )
271
- if register_on_hub:
272
- init_instance_hub(isettings)
273
- validate_sqlite_state(isettings)
274
- isettings._persist()
275
- if _test:
276
- return None
277
- isettings._init_db()
278
- load_from_isettings(isettings, init=True)
279
- if isettings._is_cloud_sqlite:
280
- isettings._cloud_sqlite_locker.lock()
281
- logger.warning(
282
- "locked instance (to unlock and push changes to the cloud SQLite file,"
283
- " call: lamin load --unload)"
284
- )
285
- if register_on_hub and isettings.dialect != "sqlite":
286
- from ._schema_metadata import update_schema_in_hub
287
-
288
- update_schema_in_hub()
289
- settings.auto_connect = True
290
- except Exception as e:
291
- from ._delete import delete_by_isettings
292
- from .core._hub_core import delete_instance_record, delete_storage_record
293
-
294
- if isettings is not None:
295
- delete_by_isettings(isettings)
296
- if settings.user.handle != "anonymous" and isettings.is_on_hub:
297
- delete_instance_record(isettings._id)
298
- isettings._get_settings_file().unlink(missing_ok=True) # type: ignore
299
- if (
300
- ssettings is not None
301
- and settings.user.handle != "anonymous"
302
- and ssettings.is_on_hub
303
- ):
304
- delete_storage_record(ssettings._uuid) # type: ignore
305
- raise e
306
- return None
307
-
308
-
309
- def load_from_isettings(
310
- isettings: InstanceSettings,
311
- *,
312
- init: bool = False,
313
- ) -> None:
314
- from .core._setup_bionty_sources import load_bionty_sources, write_bionty_sources
315
-
316
- if init:
317
- # during init both user and storage need to be registered
318
- register_user_and_storage_in_instance(isettings, settings.user)
319
- write_bionty_sources(isettings)
320
- isettings._update_cloud_sqlite_file(unlock_cloud_sqlite=False)
321
- else:
322
- # when loading, django is already set up
323
- #
324
- # only register user if the instance is connected
325
- # for the first time in an environment
326
- # this is our best proxy for that the user might not
327
- # yet be registered
328
- if not isettings._get_settings_file().exists():
329
- register_user(settings.user)
330
- load_bionty_sources(isettings)
331
- isettings._persist()
332
- reload_lamindb(isettings)
333
-
334
-
335
- def validate_sqlite_state(isettings: InstanceSettings) -> None:
336
- if isettings._is_cloud_sqlite:
337
- if (
338
- # it's important to first evaluate the existence check
339
- # for the local sqlite file because it doesn't need a network
340
- # request
341
- isettings._sqlite_file_local.exists()
342
- and not isettings._sqlite_file.exists()
343
- ):
344
- raise RuntimeError(
345
- ERROR_SQLITE_CACHE.format(
346
- isettings._sqlite_file, isettings._sqlite_file_local
347
- )
348
- )
349
-
350
-
351
- def infer_instance_name(
352
- *,
353
- storage: UPathStr,
354
- name: str | None = None,
355
- db: PostgresDsn | None = None,
356
- ) -> str:
357
- if name is not None:
358
- if "/" in name:
359
- raise ValueError("Invalid instance name: '/' delimiter not allowed.")
360
- return name
361
- if db is not None:
362
- logger.warning("using the sql database name for the instance name")
363
- # this isn't a great way to access the db name
364
- # could use LaminDsn instead
365
- return str(db).split("/")[-1]
366
- if storage == "create-s3":
367
- raise ValueError("pass name to init if storage = 'create-s3'")
368
- storage_path = UPath(storage)
369
- if storage_path.name != "":
370
- name = storage_path.name
371
- else:
372
- # dedicated treatment of bucket names
373
- name = storage_path._url.netloc
374
- return name.lower()
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import os
5
+ import sys
6
+ import uuid
7
+ from typing import TYPE_CHECKING, Literal
8
+ from uuid import UUID
9
+
10
+ from django.core.exceptions import FieldError
11
+ from django.db.utils import OperationalError, ProgrammingError
12
+ from lamin_utils import logger
13
+
14
+ from ._close import close as close_instance
15
+ from ._silence_loggers import silence_loggers
16
+ from .core import InstanceSettings
17
+ from .core._settings import settings
18
+ from .core._settings_instance import is_local_db_url
19
+ from .core._settings_storage import StorageSettings, init_storage
20
+ from .core.upath import UPath
21
+
22
+ if TYPE_CHECKING:
23
+ from pydantic import PostgresDsn
24
+
25
+ from .core._settings_user import UserSettings
26
+ from .core.types import UPathStr
27
+
28
+
29
+ def get_schema_module_name(schema_name) -> str:
30
+ import importlib.util
31
+
32
+ name_attempts = [f"lnschema_{schema_name.replace('-', '_')}", schema_name]
33
+ for name in name_attempts:
34
+ module_spec = importlib.util.find_spec(name)
35
+ if module_spec is not None:
36
+ return name
37
+ raise ImportError(
38
+ f"Python package for '{schema_name}' is not installed.\nIf your package is on PyPI, run `pip install {schema_name}`"
39
+ )
40
+
41
+
42
+ def register_storage_in_instance(ssettings: StorageSettings):
43
+ from lnschema_core.models import Storage
44
+ from lnschema_core.users import current_user_id
45
+
46
+ from .core.hashing import hash_and_encode_as_b62
47
+
48
+ if ssettings._instance_id is not None:
49
+ instance_uid = hash_and_encode_as_b62(ssettings._instance_id.hex)[:12]
50
+ else:
51
+ instance_uid = None
52
+ # how do we ensure that this function is only called passing
53
+ # the managing instance?
54
+ defaults = {
55
+ "root": ssettings.root_as_str,
56
+ "type": ssettings.type,
57
+ "region": ssettings.region,
58
+ "instance_uid": instance_uid,
59
+ "created_by_id": current_user_id(),
60
+ "run": None,
61
+ }
62
+ if ssettings._uid is not None:
63
+ defaults["uid"] = ssettings._uid
64
+ storage, _ = Storage.objects.update_or_create(
65
+ root=ssettings.root_as_str,
66
+ defaults=defaults,
67
+ )
68
+ return storage
69
+
70
+
71
+ def register_user(usettings):
72
+ from lnschema_core.models import User
73
+
74
+ try:
75
+ # need to have try except because of integer primary key migration
76
+ user, created = User.objects.update_or_create(
77
+ uid=usettings.uid,
78
+ defaults={
79
+ "handle": usettings.handle,
80
+ "name": usettings.name,
81
+ },
82
+ )
83
+ # for users with only read access, except via ProgrammingError
84
+ # ProgrammingError: permission denied for table lnschema_core_user
85
+ except (OperationalError, FieldError, ProgrammingError):
86
+ pass
87
+
88
+
89
+ def register_user_and_storage_in_instance(isettings: InstanceSettings, usettings):
90
+ """Register user & storage in DB."""
91
+ from django.db.utils import OperationalError
92
+
93
+ try:
94
+ register_user(usettings)
95
+ register_storage_in_instance(isettings.storage)
96
+ except OperationalError as error:
97
+ logger.warning(f"instance seems not set up ({error})")
98
+
99
+
100
+ def reload_schema_modules(isettings: InstanceSettings, include_core: bool = True):
101
+ schema_names = ["core"] if include_core else []
102
+ # schema_names += list(isettings.schema)
103
+ schema_module_names = [get_schema_module_name(n) for n in schema_names]
104
+
105
+ for schema_module_name in schema_module_names:
106
+ if schema_module_name in sys.modules:
107
+ schema_module = importlib.import_module(schema_module_name)
108
+ importlib.reload(schema_module)
109
+
110
+
111
+ def reload_lamindb_itself(isettings) -> bool:
112
+ reloaded = False
113
+ if "lamindb" in sys.modules:
114
+ import lamindb
115
+
116
+ importlib.reload(lamindb)
117
+ reloaded = True
118
+ return reloaded
119
+
120
+
121
+ def reload_lamindb(isettings: InstanceSettings):
122
+ log_message = settings.auto_connect
123
+ if not reload_lamindb_itself(isettings):
124
+ log_message = True
125
+ if log_message:
126
+ logger.important(f"connected lamindb: {isettings.slug}")
127
+
128
+
129
+ ERROR_SQLITE_CACHE = """
130
+ Your cached local SQLite file exists, while your cloud SQLite file ({}) doesn't.
131
+ Either delete your cache ({}) or add it back to the cloud (if delete was accidental).
132
+ """
133
+
134
+
135
+ def process_connect_response(
136
+ response: tuple | str, instance_identifier: str
137
+ ) -> tuple[
138
+ UUID,
139
+ Literal[
140
+ "instance-corrupted-or-deleted", "account-not-exists", "instance-not-found"
141
+ ],
142
+ ]:
143
+ # for internal use when creating instances through CICD
144
+ if isinstance(response, tuple) and response[0] == "instance-corrupted-or-deleted":
145
+ hub_result = response[1]
146
+ instance_state = response[0]
147
+ instance_id = UUID(hub_result["id"])
148
+ else:
149
+ instance_id_str = os.getenv("LAMINDB_INSTANCE_ID_INIT")
150
+ if instance_id_str is None:
151
+ instance_id = uuid.uuid5(uuid.NAMESPACE_URL, instance_identifier)
152
+ else:
153
+ instance_id = UUID(instance_id_str)
154
+ instance_state = response
155
+ return instance_id, instance_state
156
+
157
+
158
+ def validate_init_args(
159
+ *,
160
+ storage: UPathStr,
161
+ name: str | None = None,
162
+ db: PostgresDsn | None = None,
163
+ schema: str | None = None,
164
+ _test: bool = False,
165
+ _write_settings: bool = True,
166
+ _user: UserSettings | None = None,
167
+ ) -> tuple[
168
+ str,
169
+ UUID | None,
170
+ Literal[
171
+ "connected",
172
+ "instance-corrupted-or-deleted",
173
+ "account-not-exists",
174
+ "instance-not-found",
175
+ ],
176
+ str,
177
+ ]:
178
+ from ._connect_instance import connect
179
+ from .core._hub_utils import (
180
+ validate_schema_arg,
181
+ )
182
+
183
+ # should be called as the first thing
184
+ name_str = infer_instance_name(storage=storage, name=name, db=db)
185
+ owner_str = settings.user.handle if _user is None else _user.handle
186
+ # test whether instance exists by trying to load it
187
+ instance_slug = f"{owner_str}/{name_str}"
188
+ response = connect(
189
+ instance_slug,
190
+ _db=db,
191
+ _raise_not_found_error=False,
192
+ _test=_test,
193
+ _write_settings=_write_settings,
194
+ _user=_user,
195
+ )
196
+ instance_state: Literal[
197
+ "connected",
198
+ "instance-corrupted-or-deleted",
199
+ "account-not-exists",
200
+ "instance-not-found",
201
+ ] = "connected"
202
+ instance_id = None
203
+ if response is not None:
204
+ instance_id, instance_state = process_connect_response(response, instance_slug)
205
+ schema = validate_schema_arg(schema)
206
+ return name_str, instance_id, instance_state, instance_slug
207
+
208
+
209
+ MESSAGE_NO_MULTIPLE_INSTANCE = """
210
+ Currently don't support subsequent connection to different databases in the same
211
+ Python session.\n
212
+ Try running on the CLI: lamin settings set auto-connect false
213
+ """
214
+
215
+
216
+ def init(
217
+ *,
218
+ storage: UPathStr,
219
+ name: str | None = None,
220
+ db: PostgresDsn | None = None,
221
+ schema: str | None = None,
222
+ **kwargs,
223
+ ) -> None:
224
+ """Create and load a LaminDB instance.
225
+
226
+ Args:
227
+ storage: Either ``"create-s3"``, local or
228
+ remote folder (``"s3://..."`` or ``"gs://..."``).
229
+ name: Instance name.
230
+ db: Database connection url, do not pass for SQLite.
231
+ schema: Comma-separated string of schema modules. None if not set.
232
+ """
233
+ isettings = None
234
+ ssettings = None
235
+
236
+ _write_settings: bool = kwargs.get("_write_settings", True)
237
+ _test: bool = kwargs.get("_test", False)
238
+
239
+ # use this user instead of settings.user
240
+ # contains access_token
241
+ _user: UserSettings | None = kwargs.get("_user", None)
242
+ user_handle: str = settings.user.handle if _user is None else _user.handle
243
+ user__uuid: UUID = settings.user._uuid if _user is None else _user._uuid # type: ignore
244
+ access_token: str | None = None if _user is None else _user.access_token
245
+
246
+ try:
247
+ silence_loggers()
248
+ from ._check_setup import _check_instance_setup
249
+
250
+ if _check_instance_setup() and not _test:
251
+ raise RuntimeError(MESSAGE_NO_MULTIPLE_INSTANCE)
252
+ elif _write_settings:
253
+ close_instance(mute=True)
254
+ from .core._hub_core import init_instance as init_instance_hub
255
+
256
+ name_str, instance_id, instance_state, _ = validate_init_args(
257
+ storage=storage,
258
+ name=name,
259
+ db=db,
260
+ schema=schema,
261
+ _test=_test,
262
+ _write_settings=_write_settings,
263
+ _user=_user, # will get from settings.user if _user is None
264
+ )
265
+ if instance_state == "connected":
266
+ if _write_settings:
267
+ settings.auto_connect = True # we can also debate this switch here
268
+ return None
269
+ # the conditions here match `isettings.is_remote`, but I currently don't
270
+ # see a way of making this more elegant; should become possible if we
271
+ # remove the instance.storage_id FK on the hub
272
+ prevent_register_hub = is_local_db_url(db) if db is not None else False
273
+ ssettings, _ = init_storage(
274
+ storage,
275
+ instance_id=instance_id,
276
+ init_instance=True,
277
+ prevent_register_hub=prevent_register_hub,
278
+ created_by=user__uuid,
279
+ access_token=access_token,
280
+ )
281
+ isettings = InstanceSettings(
282
+ id=instance_id, # type: ignore
283
+ owner=user_handle,
284
+ name=name_str,
285
+ storage=ssettings,
286
+ db=db,
287
+ schema=schema,
288
+ uid=ssettings.uid,
289
+ # to lock passed user in isettings._cloud_sqlite_locker.lock()
290
+ _locker_user=_user, # only has effect if cloud sqlite
291
+ )
292
+ register_on_hub = (
293
+ isettings.is_remote and instance_state != "instance-corrupted-or-deleted"
294
+ )
295
+ if register_on_hub:
296
+ init_instance_hub(
297
+ isettings, account_id=user__uuid, access_token=access_token
298
+ )
299
+ validate_sqlite_state(isettings)
300
+ # why call it here if it is also called in load_from_isettings?
301
+ isettings._persist(write_to_disk=_write_settings)
302
+ if _test:
303
+ return None
304
+ isettings._init_db()
305
+ load_from_isettings(
306
+ isettings, init=True, user=_user, write_settings=_write_settings
307
+ )
308
+ if _write_settings and isettings._is_cloud_sqlite:
309
+ isettings._cloud_sqlite_locker.lock()
310
+ logger.warning(
311
+ "locked instance (to unlock and push changes to the cloud SQLite file,"
312
+ " call: lamin load --unload)"
313
+ )
314
+ if register_on_hub and isettings.dialect != "sqlite":
315
+ from ._schema_metadata import update_schema_in_hub
316
+
317
+ update_schema_in_hub(access_token=access_token)
318
+ if _write_settings:
319
+ settings.auto_connect = True
320
+ except Exception as e:
321
+ from ._delete import delete_by_isettings
322
+ from .core._hub_core import delete_instance_record, delete_storage_record
323
+
324
+ if isettings is not None:
325
+ if _write_settings:
326
+ delete_by_isettings(isettings)
327
+ else:
328
+ settings._instance_settings = None
329
+ if (
330
+ user_handle != "anonymous" or access_token is not None
331
+ ) and isettings.is_on_hub:
332
+ delete_instance_record(isettings._id, access_token=access_token)
333
+ if (
334
+ ssettings is not None
335
+ and (user_handle != "anonymous" or access_token is not None)
336
+ and ssettings.is_on_hub
337
+ ):
338
+ delete_storage_record(ssettings._uuid, access_token=access_token) # type: ignore
339
+ raise e
340
+ return None
341
+
342
+
343
+ def load_from_isettings(
344
+ isettings: InstanceSettings,
345
+ *,
346
+ init: bool = False,
347
+ user: UserSettings | None = None,
348
+ write_settings: bool = True,
349
+ ) -> None:
350
+ from .core._setup_bionty_sources import write_bionty_sources
351
+
352
+ user = settings.user if user is None else user
353
+
354
+ if init:
355
+ # during init both user and storage need to be registered
356
+ register_user_and_storage_in_instance(isettings, user)
357
+ write_bionty_sources(isettings)
358
+ isettings._update_cloud_sqlite_file(unlock_cloud_sqlite=False)
359
+ else:
360
+ # when loading, django is already set up
361
+ #
362
+ # only register user if the instance is connected
363
+ # for the first time in an environment
364
+ # this is our best proxy for that the user might not
365
+ # yet be registered
366
+ if not isettings._get_settings_file().exists():
367
+ register_user(user)
368
+ isettings._persist(write_to_disk=write_settings)
369
+ reload_lamindb(isettings)
370
+
371
+
372
+ def validate_sqlite_state(isettings: InstanceSettings) -> None:
373
+ if isettings._is_cloud_sqlite:
374
+ if (
375
+ # it's important to first evaluate the existence check
376
+ # for the local sqlite file because it doesn't need a network
377
+ # request
378
+ isettings._sqlite_file_local.exists()
379
+ and not isettings._sqlite_file.exists()
380
+ ):
381
+ raise RuntimeError(
382
+ ERROR_SQLITE_CACHE.format(
383
+ isettings._sqlite_file, isettings._sqlite_file_local
384
+ )
385
+ )
386
+
387
+
388
+ def infer_instance_name(
389
+ *,
390
+ storage: UPathStr,
391
+ name: str | None = None,
392
+ db: PostgresDsn | None = None,
393
+ ) -> str:
394
+ if name is not None:
395
+ if "/" in name:
396
+ raise ValueError("Invalid instance name: '/' delimiter not allowed.")
397
+ return name
398
+ if db is not None:
399
+ logger.warning("using the sql database name for the instance name")
400
+ # this isn't a great way to access the db name
401
+ # could use LaminDsn instead
402
+ return str(db).split("/")[-1]
403
+ if storage == "create-s3":
404
+ raise ValueError("pass name to init if storage = 'create-s3'")
405
+ storage_path = UPath(storage)
406
+ if storage_path.name != "":
407
+ name = storage_path.name
408
+ else:
409
+ # dedicated treatment of bucket names
410
+ name = storage_path._url.netloc
411
+ return name.lower()