lamindb_setup 1.9.1__py3-none-any.whl → 1.10.1__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 (41) hide show
  1. lamindb_setup/__init__.py +107 -107
  2. lamindb_setup/_cache.py +87 -87
  3. lamindb_setup/_check_setup.py +192 -166
  4. lamindb_setup/_connect_instance.py +430 -328
  5. lamindb_setup/_delete.py +144 -141
  6. lamindb_setup/_disconnect.py +35 -32
  7. lamindb_setup/_init_instance.py +430 -440
  8. lamindb_setup/_migrate.py +278 -266
  9. lamindb_setup/_register_instance.py +32 -35
  10. lamindb_setup/_schema_metadata.py +441 -441
  11. lamindb_setup/_set_managed_storage.py +69 -70
  12. lamindb_setup/_setup_user.py +172 -133
  13. lamindb_setup/core/__init__.py +21 -21
  14. lamindb_setup/core/_aws_options.py +223 -223
  15. lamindb_setup/core/_aws_storage.py +9 -1
  16. lamindb_setup/core/_deprecated.py +1 -1
  17. lamindb_setup/core/_hub_client.py +248 -248
  18. lamindb_setup/core/_hub_core.py +751 -665
  19. lamindb_setup/core/_hub_crud.py +247 -227
  20. lamindb_setup/core/_private_django_api.py +83 -83
  21. lamindb_setup/core/_settings.py +374 -377
  22. lamindb_setup/core/_settings_instance.py +609 -569
  23. lamindb_setup/core/_settings_load.py +141 -141
  24. lamindb_setup/core/_settings_save.py +95 -95
  25. lamindb_setup/core/_settings_storage.py +427 -429
  26. lamindb_setup/core/_settings_store.py +91 -91
  27. lamindb_setup/core/_settings_user.py +55 -55
  28. lamindb_setup/core/_setup_bionty_sources.py +44 -44
  29. lamindb_setup/core/cloud_sqlite_locker.py +240 -240
  30. lamindb_setup/core/django.py +311 -305
  31. lamindb_setup/core/exceptions.py +1 -1
  32. lamindb_setup/core/hashing.py +134 -134
  33. lamindb_setup/core/types.py +1 -1
  34. lamindb_setup/core/upath.py +1013 -1013
  35. lamindb_setup/errors.py +80 -70
  36. lamindb_setup/types.py +20 -20
  37. {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.1.dist-info}/METADATA +4 -4
  38. lamindb_setup-1.10.1.dist-info/RECORD +50 -0
  39. lamindb_setup-1.9.1.dist-info/RECORD +0 -50
  40. {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.1.dist-info}/LICENSE +0 -0
  41. {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.1.dist-info}/WHEEL +0 -0
@@ -1,328 +1,430 @@
1
- from __future__ import annotations
2
-
3
- import importlib
4
- import os
5
- from typing import TYPE_CHECKING, Any
6
- from uuid import UUID
7
-
8
- from lamin_utils import logger
9
-
10
- from ._check_setup import _check_instance_setup, _get_current_instance_settings
11
- from ._disconnect import disconnect
12
- from ._init_instance import (
13
- MESSAGE_CANNOT_SWITCH_DEFAULT_INSTANCE,
14
- load_from_isettings,
15
- )
16
- from ._silence_loggers import silence_loggers
17
- from .core._hub_core import connect_instance_hub
18
- from .core._hub_utils import LaminDsnModel
19
- from .core._settings import settings
20
- from .core._settings_instance import InstanceSettings
21
- from .core._settings_load import load_instance_settings
22
- from .core._settings_storage import StorageSettings
23
- from .core._settings_store import instance_settings_file, settings_dir
24
- from .core.cloud_sqlite_locker import unlock_cloud_sqlite_upon_exception
25
- from .errors import CannotSwitchDefaultInstance
26
-
27
- if TYPE_CHECKING:
28
- from pathlib import Path
29
-
30
- from .core._settings_user import UserSettings
31
- from .types import UPathStr
32
-
33
- # this is for testing purposes only
34
- # set to True only to test failed load
35
- _TEST_FAILED_LOAD = False
36
-
37
-
38
- INSTANCE_NOT_FOUND_MESSAGE = (
39
- "'{owner}/{name}' not found:"
40
- " '{hub_result}'\nCheck your permissions:"
41
- " https://lamin.ai/{owner}/{name}"
42
- )
43
-
44
-
45
- class InstanceNotFoundError(SystemExit):
46
- pass
47
-
48
-
49
- def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
50
- return (
51
- db_dsn_hub.scheme == db_dsn_local.scheme
52
- and db_dsn_hub.host == db_dsn_local.host
53
- and db_dsn_hub.database == db_dsn_local.database
54
- and db_dsn_hub.port == db_dsn_local.port
55
- )
56
-
57
-
58
- def update_db_using_local(
59
- hub_instance_result: dict[str, str],
60
- settings_file: Path,
61
- db: str | None = None,
62
- raise_permission_error=True,
63
- ) -> str | None:
64
- db_updated = None
65
- # check if postgres
66
- if hub_instance_result["db_scheme"] == "postgresql":
67
- if db is not None:
68
- # use only the provided db if it is set
69
- db_updated = db
70
- elif (db_env := os.getenv("LAMINDB_INSTANCE_DB")) is not None:
71
- logger.important("loading db URL from env variable LAMINDB_INSTANCE_DB")
72
- # read directly from the environment
73
- db_updated = db_env
74
- else:
75
- db_hub = hub_instance_result["db"]
76
- db_dsn_hub = LaminDsnModel(db=db_hub)
77
- # read from a cached settings file in case the hub result is inexistent
78
- if db_dsn_hub.db.user in {None, "none"} and settings_file.exists():
79
- isettings = load_instance_settings(settings_file)
80
- db_updated = isettings.db
81
- else:
82
- # just take the default hub result and ensure there is actually a user
83
- if (
84
- db_dsn_hub.db.user in {None, "none"}
85
- and db_dsn_hub.db.password in {None, "none"}
86
- and raise_permission_error
87
- ):
88
- raise PermissionError(
89
- "No database access, please ask your admin to provide you with"
90
- " a DB URL and pass it via --db <db_url>"
91
- )
92
- db_updated = db_hub
93
- return db_updated
94
-
95
-
96
- def _connect_instance(
97
- owner: str,
98
- name: str,
99
- *,
100
- db: str | None = None,
101
- raise_permission_error: bool = True,
102
- access_token: str | None = None,
103
- ) -> InstanceSettings:
104
- settings_file = instance_settings_file(name, owner)
105
- make_hub_request = True
106
- if settings_file.exists():
107
- isettings = load_instance_settings(settings_file)
108
- # skip hub request for a purely local instance
109
- if isettings.is_remote:
110
- make_hub_request = True
111
- else:
112
- make_hub_request = False
113
- if db is not None and isettings.dialect == "postgresql":
114
- isettings._db = db
115
- if make_hub_request:
116
- # the following will return a string if the instance does not exist
117
- # on the hub
118
- # do not call hub if the user is anonymous
119
- if owner != "anonymous":
120
- hub_result = connect_instance_hub(
121
- owner=owner, name=name, access_token=access_token
122
- )
123
- else:
124
- hub_result = "anonymous-user"
125
- # if hub_result is not a string, it means it made a request
126
- # that successfully returned metadata
127
- if not isinstance(hub_result, str):
128
- instance_result, storage_result = hub_result
129
- db_updated = update_db_using_local(
130
- instance_result,
131
- settings_file,
132
- db=db,
133
- raise_permission_error=raise_permission_error,
134
- )
135
- ssettings = StorageSettings(
136
- root=storage_result["root"],
137
- region=storage_result["region"],
138
- uid=storage_result["lnid"],
139
- uuid=UUID(storage_result["id"]),
140
- instance_id=UUID(instance_result["id"]),
141
- )
142
- isettings = InstanceSettings(
143
- id=UUID(instance_result["id"]),
144
- owner=owner,
145
- name=instance_result["name"],
146
- storage=ssettings,
147
- db=db_updated,
148
- modules=instance_result["schema_str"],
149
- git_repo=instance_result["git_repo"],
150
- keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
151
- is_on_hub=True,
152
- api_url=instance_result["api_url"],
153
- schema_id=None
154
- if (schema_id := instance_result["schema_id"]) is None
155
- else UUID(schema_id),
156
- fine_grained_access=instance_result.get("fine_grained_access", False),
157
- db_permissions=instance_result.get("db_permissions", None),
158
- )
159
- else:
160
- if hub_result != "anonymous-user":
161
- message = INSTANCE_NOT_FOUND_MESSAGE.format(
162
- owner=owner, name=name, hub_result=hub_result
163
- )
164
- else:
165
- message = "It is not possible to load an anonymous-owned instance from the hub"
166
- if settings_file.exists():
167
- isettings = load_instance_settings(settings_file)
168
- if isettings.is_remote:
169
- raise InstanceNotFoundError(message)
170
- else:
171
- raise InstanceNotFoundError(message)
172
- return isettings
173
-
174
-
175
- def _connect_cli(instance: str) -> None:
176
- from lamindb_setup import settings as settings_
177
-
178
- settings_.auto_connect = True
179
- owner, name = get_owner_name_from_identifier(instance)
180
- isettings = _connect_instance(owner, name)
181
- isettings._persist(write_to_disk=True)
182
- if not isettings.is_on_hub or isettings._is_cloud_sqlite:
183
- # there are two reasons to call the full-blown connect
184
- # (1) if the instance is not on the hub, we need to register
185
- # potential users through register_user()
186
- # (2) if the instance is cloud sqlite, we need to lock it
187
- connect(_write_settings=False, _reload_lamindb=False)
188
- else:
189
- logger.important(f"connected lamindb: {isettings.slug}")
190
- return None
191
-
192
-
193
- @unlock_cloud_sqlite_upon_exception(ignore_prev_locker=True)
194
- def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
195
- """Connect to an instance.
196
-
197
- Args:
198
- instance: Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
199
- If `None`, looks for an environment variable `LAMIN_CURRENT_INSTANCE` to get the instance identifier.
200
- If it doesn't find this variable, it connects to the instance that was connected with `lamin connect` through the CLI.
201
- """
202
- # validate kwargs
203
- valid_kwargs = {
204
- "_db",
205
- "_write_settings",
206
- "_raise_not_found_error",
207
- "_reload_lamindb",
208
- "_test",
209
- "_user",
210
- }
211
- for kwarg in kwargs:
212
- if kwarg not in valid_kwargs:
213
- raise TypeError(f"connect() got unexpected keyword argument '{kwarg}'")
214
- isettings: InstanceSettings = None # type: ignore
215
- # _db is still needed because it is called in init
216
- _db: str | None = kwargs.get("_db", None)
217
- _write_settings: bool = kwargs.get("_write_settings", False)
218
- _raise_not_found_error: bool = kwargs.get("_raise_not_found_error", True)
219
- _reload_lamindb: bool = kwargs.get("_reload_lamindb", True)
220
- _test: bool = kwargs.get("_test", False)
221
-
222
- access_token: str | None = None
223
- _user: UserSettings | None = kwargs.get("_user", None)
224
- if _user is not None:
225
- access_token = _user.access_token
226
- if instance is None:
227
- instance = os.environ.get("LAMIN_CURRENT_INSTANCE")
228
-
229
- try:
230
- if instance is None:
231
- isettings_or_none = _get_current_instance_settings()
232
- if isettings_or_none is None:
233
- raise ValueError(
234
- "No instance was connected through the CLI, pass a value to `instance` or connect via the CLI."
235
- )
236
- isettings = isettings_or_none
237
- if _db is not None and isettings.dialect == "postgresql":
238
- isettings._db = _db
239
- else:
240
- owner, name = get_owner_name_from_identifier(instance)
241
- if _check_instance_setup() and not _test:
242
- if (
243
- settings._instance_exists
244
- and f"{owner}/{name}" == settings.instance.slug
245
- ):
246
- logger.important(f"connected lamindb: {settings.instance.slug}")
247
- return None
248
- else:
249
- raise CannotSwitchDefaultInstance(
250
- MESSAGE_CANNOT_SWITCH_DEFAULT_INSTANCE
251
- )
252
- elif (
253
- _write_settings
254
- and settings._instance_exists
255
- and f"{owner}/{name}" != settings.instance.slug
256
- ):
257
- disconnect(mute=True)
258
-
259
- try:
260
- isettings = _connect_instance(
261
- owner, name, db=_db, access_token=access_token
262
- )
263
- except InstanceNotFoundError as e:
264
- if _raise_not_found_error:
265
- raise e
266
- else:
267
- return "instance-not-found"
268
- if isinstance(isettings, str):
269
- return isettings
270
- # at this point we have checked already that isettings is not a string
271
- # _user is passed to lock cloud sqlite for this user in isettings._load_db()
272
- # has no effect if _user is None or if not cloud sqlite instance
273
- isettings._locker_user = _user
274
- isettings._persist(write_to_disk=_write_settings)
275
- if _test:
276
- return None
277
- silence_loggers()
278
- check, msg = isettings._load_db()
279
- if not check:
280
- local_db = (
281
- isettings._is_cloud_sqlite and isettings._sqlite_file_local.exists()
282
- )
283
- if local_db:
284
- logger.warning(
285
- "SQLite file does not exist in the cloud, but exists locally:"
286
- f" {isettings._sqlite_file_local}\nTo push the file to the cloud,"
287
- " call: lamin disconnect"
288
- )
289
- elif _raise_not_found_error:
290
- raise SystemExit(msg)
291
- else:
292
- logger.warning(
293
- f"instance exists with id {isettings._id.hex}, but database is not"
294
- " loadable: re-initializing"
295
- )
296
- return "instance-corrupted-or-deleted"
297
- # this is for testing purposes only
298
- if _TEST_FAILED_LOAD:
299
- raise RuntimeError("Technical testing error.")
300
-
301
- load_from_isettings(isettings, user=_user, write_settings=_write_settings)
302
- if _reload_lamindb:
303
- importlib.reload(importlib.import_module("lamindb"))
304
- logger.important(f"connected lamindb: {isettings.slug}")
305
- except Exception as e:
306
- if isettings is not None:
307
- if _write_settings:
308
- isettings._get_settings_file().unlink(missing_ok=True) # type: ignore
309
- settings._instance_settings = None
310
- raise e
311
- return None
312
-
313
-
314
- def get_owner_name_from_identifier(identifier: str):
315
- if "/" in identifier:
316
- if identifier.startswith("https://lamin.ai/"):
317
- identifier = identifier.replace("https://lamin.ai/", "")
318
- split = identifier.split("/")
319
- if len(split) > 2:
320
- raise ValueError(
321
- "The instance identifier needs to be 'owner/name', the instance name"
322
- " (owner is current user) or the URL: https://lamin.ai/owner/name."
323
- )
324
- owner, name = split
325
- else:
326
- owner = settings.user.handle
327
- name = identifier
328
- return owner, name
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import os
5
+ import sys
6
+ from typing import TYPE_CHECKING, Any
7
+ from uuid import UUID
8
+
9
+ from lamin_utils import logger
10
+
11
+ from ._check_setup import (
12
+ _check_instance_setup,
13
+ _get_current_instance_settings,
14
+ find_module_candidates,
15
+ )
16
+ from ._disconnect import disconnect
17
+ from ._init_instance import load_from_isettings
18
+ from ._silence_loggers import silence_loggers
19
+ from .core._hub_core import connect_instance_hub
20
+ from .core._hub_utils import LaminDsnModel
21
+ from .core._settings import settings
22
+ from .core._settings_instance import InstanceSettings
23
+ from .core._settings_load import load_instance_settings
24
+ from .core._settings_storage import StorageSettings
25
+ from .core._settings_store import instance_settings_file, settings_dir
26
+ from .core.cloud_sqlite_locker import unlock_cloud_sqlite_upon_exception
27
+ from .core.django import reset_django
28
+ from .errors import CannotSwitchDefaultInstance
29
+
30
+ if TYPE_CHECKING:
31
+ from pathlib import Path
32
+
33
+ from .core._settings_user import UserSettings
34
+ from .types import UPathStr
35
+
36
+ # this is for testing purposes only
37
+ # set to True only to test failed load
38
+ _TEST_FAILED_LOAD = False
39
+
40
+
41
+ INSTANCE_NOT_FOUND_MESSAGE = (
42
+ "'{owner}/{name}' not found:"
43
+ " '{hub_result}'\nCheck your permissions:"
44
+ " https://lamin.ai/{owner}/{name}"
45
+ )
46
+
47
+
48
+ class InstanceNotFoundError(SystemExit):
49
+ pass
50
+
51
+
52
+ def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
53
+ return (
54
+ db_dsn_hub.scheme == db_dsn_local.scheme
55
+ and db_dsn_hub.host == db_dsn_local.host
56
+ and db_dsn_hub.database == db_dsn_local.database
57
+ and db_dsn_hub.port == db_dsn_local.port
58
+ )
59
+
60
+
61
+ def update_db_using_local(
62
+ hub_instance_result: dict[str, str],
63
+ settings_file: Path,
64
+ db: str | None = None,
65
+ raise_permission_error=True,
66
+ ) -> str | None:
67
+ db_updated = None
68
+ # check if postgres
69
+ if hub_instance_result["db_scheme"] == "postgresql":
70
+ if db is not None:
71
+ # use only the provided db if it is set
72
+ db_updated = db
73
+ elif (db_env := os.getenv("LAMINDB_INSTANCE_DB")) is not None:
74
+ logger.important("loading db URL from env variable LAMINDB_INSTANCE_DB")
75
+ # read directly from the environment
76
+ db_updated = db_env
77
+ else:
78
+ db_hub = hub_instance_result["db"]
79
+ db_dsn_hub = LaminDsnModel(db=db_hub)
80
+ # read from a cached settings file in case the hub result is inexistent
81
+ if db_dsn_hub.db.user in {None, "none"} and settings_file.exists():
82
+ isettings = load_instance_settings(settings_file)
83
+ db_updated = isettings.db
84
+ else:
85
+ # just take the default hub result and ensure there is actually a user
86
+ if (
87
+ db_dsn_hub.db.user in {None, "none"}
88
+ and db_dsn_hub.db.password in {None, "none"}
89
+ and raise_permission_error
90
+ ):
91
+ raise PermissionError(
92
+ "No database access, please ask your admin to provide you with"
93
+ " a DB URL and pass it via --db <db_url>"
94
+ )
95
+ db_updated = db_hub
96
+ return db_updated
97
+
98
+
99
+ def _connect_instance(
100
+ owner: str,
101
+ name: str,
102
+ *,
103
+ db: str | None = None,
104
+ raise_permission_error: bool = True,
105
+ use_root_db_user: bool = False,
106
+ access_token: str | None = None,
107
+ ) -> InstanceSettings:
108
+ settings_file = instance_settings_file(name, owner)
109
+ make_hub_request = True
110
+ if settings_file.exists():
111
+ isettings = load_instance_settings(settings_file)
112
+ # skip hub request for a purely local instance
113
+ if isettings.is_remote:
114
+ make_hub_request = True
115
+ else:
116
+ make_hub_request = False
117
+ if db is not None and isettings.dialect == "postgresql":
118
+ isettings._db = db
119
+ if make_hub_request:
120
+ # the following will return a string if the instance does not exist
121
+ # on the hub
122
+ # do not call hub if the user is anonymous
123
+ if owner != "anonymous":
124
+ hub_result = connect_instance_hub(
125
+ owner=owner,
126
+ name=name,
127
+ access_token=access_token,
128
+ use_root_db_user=use_root_db_user,
129
+ )
130
+ else:
131
+ hub_result = "anonymous-user"
132
+ # if hub_result is not a string, it means it made a request
133
+ # that successfully returned metadata
134
+ if not isinstance(hub_result, str):
135
+ instance_result, storage_result = hub_result
136
+ db_updated = update_db_using_local(
137
+ instance_result,
138
+ settings_file,
139
+ db=db,
140
+ raise_permission_error=raise_permission_error,
141
+ )
142
+ ssettings = StorageSettings(
143
+ root=storage_result["root"],
144
+ region=storage_result["region"],
145
+ uid=storage_result["lnid"],
146
+ uuid=UUID(storage_result["id"]),
147
+ instance_id=UUID(instance_result["id"]),
148
+ )
149
+ isettings = InstanceSettings(
150
+ id=UUID(instance_result["id"]),
151
+ owner=owner,
152
+ name=instance_result["name"],
153
+ storage=ssettings,
154
+ db=db_updated,
155
+ modules=instance_result["schema_str"],
156
+ git_repo=instance_result["git_repo"],
157
+ keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
158
+ is_on_hub=True,
159
+ api_url=instance_result["api_url"],
160
+ schema_id=None
161
+ if (schema_id := instance_result["schema_id"]) is None
162
+ else UUID(schema_id),
163
+ fine_grained_access=bool(
164
+ instance_result["fine_grained_access"]
165
+ ), # can be None
166
+ db_permissions=instance_result.get("db_permissions", None),
167
+ )
168
+ else:
169
+ if hub_result != "anonymous-user":
170
+ message = INSTANCE_NOT_FOUND_MESSAGE.format(
171
+ owner=owner, name=name, hub_result=hub_result
172
+ )
173
+ else:
174
+ message = "It is not possible to load an anonymous-owned instance from the hub"
175
+ if settings_file.exists():
176
+ isettings = load_instance_settings(settings_file)
177
+ if isettings.is_remote:
178
+ raise InstanceNotFoundError(message)
179
+ else:
180
+ raise InstanceNotFoundError(message)
181
+ return isettings
182
+
183
+
184
+ def reset_django_module_variables():
185
+ # This function updates all module-level references to Django classes
186
+ # But it will fail to update function level references
187
+ # So, if a user has
188
+ # def my_function():
189
+ # import lamindb as ln
190
+ # ...
191
+ #
192
+ # Then it will **not** work and the `ln` variable will become stale and hold a reference
193
+ # to the old classes
194
+ # There doesn't seem to be an easy way to fix this problem
195
+
196
+ logger.important_hint("resetting django module variables")
197
+
198
+ import types
199
+
200
+ from django.apps import apps
201
+
202
+ app_names = {app.name for app in apps.get_app_configs()}
203
+
204
+ for name, module in sys.modules.items():
205
+ if (
206
+ module is not None
207
+ and (not name.startswith("__") or name == "__main__")
208
+ and name not in sys.builtin_module_names
209
+ and not (
210
+ hasattr(module, "__file__")
211
+ and module.__file__
212
+ and any(
213
+ path in module.__file__ for path in ["/lib/python", "\\lib\\python"]
214
+ )
215
+ )
216
+ ):
217
+ try:
218
+ for k, v in vars(module).items():
219
+ if (
220
+ isinstance(v, types.ModuleType)
221
+ and not k.startswith("_")
222
+ and getattr(v, "__name__", None) in app_names
223
+ ):
224
+ if v.__name__ in sys.modules:
225
+ vars(module)[k] = sys.modules[v.__name__]
226
+ # Also reset classes from Django apps - but check if the class module starts with any app name
227
+ elif hasattr(v, "__module__") and getattr(v, "__module__", None):
228
+ class_module = v.__module__
229
+ # Check if the class module starts with any of our app names
230
+ if any(
231
+ class_module.startswith(app_name) for app_name in app_names
232
+ ):
233
+ if class_module in sys.modules:
234
+ fresh_module = sys.modules[class_module]
235
+ attr_name = getattr(v, "__name__", k)
236
+ if hasattr(fresh_module, attr_name):
237
+ vars(module)[k] = getattr(fresh_module, attr_name)
238
+ except (AttributeError, TypeError):
239
+ continue
240
+
241
+
242
+ def _connect_cli(instance: str, use_root_db_user: bool = False) -> None:
243
+ from lamindb_setup import settings as settings_
244
+
245
+ owner, name = get_owner_name_from_identifier(instance)
246
+ isettings = _connect_instance(owner, name, use_root_db_user=use_root_db_user)
247
+ isettings._persist(write_to_disk=True)
248
+ if not isettings.is_on_hub or isettings._is_cloud_sqlite:
249
+ # there are two reasons to call the full-blown connect
250
+ # (1) if the instance is not on the hub, we need to register
251
+ # potential users through register_user()
252
+ # (2) if the instance is cloud sqlite, we need to lock it
253
+ connect(_write_settings=False, _reload_lamindb=False)
254
+ else:
255
+ logger.important(f"connected lamindb: {isettings.slug}")
256
+ return None
257
+
258
+
259
+ @unlock_cloud_sqlite_upon_exception(ignore_prev_locker=True)
260
+ def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
261
+ """Connect to an instance.
262
+
263
+ Args:
264
+ instance: Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
265
+ If `None`, looks for an environment variable `LAMIN_CURRENT_INSTANCE` to get the instance identifier.
266
+ If it doesn't find this variable, it connects to the instance that was connected with `lamin connect` through the CLI.
267
+
268
+ See Also:
269
+ Configure an instance for auto-connect via the CLI, see `here <https://docs.lamin.ai/cli#connect>`__.
270
+ """
271
+ # validate kwargs
272
+ valid_kwargs = {
273
+ "use_root_db_user",
274
+ "_db",
275
+ "_write_settings",
276
+ "_raise_not_found_error",
277
+ "_reload_lamindb",
278
+ "_test",
279
+ "_user",
280
+ }
281
+ for kwarg in kwargs:
282
+ if kwarg not in valid_kwargs:
283
+ raise TypeError(f"connect() got unexpected keyword argument '{kwarg}'")
284
+ isettings: InstanceSettings = None # type: ignore
285
+ # _db is still needed because it is called in init
286
+ use_root_db_user: bool = kwargs.get("use_root_db_user", False)
287
+ _db: str | None = kwargs.get("_db", None)
288
+ _write_settings: bool = kwargs.get("_write_settings", False)
289
+ _raise_not_found_error: bool = kwargs.get("_raise_not_found_error", True)
290
+ _reload_lamindb: bool = kwargs.get("_reload_lamindb", True)
291
+ _test: bool = kwargs.get("_test", False)
292
+
293
+ access_token: str | None = None
294
+ _user: UserSettings | None = kwargs.get("_user", None)
295
+ if _user is not None:
296
+ access_token = _user.access_token
297
+ if instance is None:
298
+ instance = os.environ.get("LAMIN_CURRENT_INSTANCE")
299
+
300
+ try:
301
+ if instance is None:
302
+ if settings._instance_exists:
303
+ isettings = settings.instance
304
+ else:
305
+ isettings_or_none = _get_current_instance_settings()
306
+ if isettings_or_none is None:
307
+ raise ValueError(
308
+ "No instance was connected through the CLI, pass a value to `instance` or connect via the CLI."
309
+ )
310
+ isettings = isettings_or_none
311
+ if use_root_db_user:
312
+ reset_django()
313
+ owner, name = isettings.owner, isettings.name
314
+ if _db is not None and isettings.dialect == "postgresql":
315
+ isettings._db = _db
316
+ else:
317
+ from django.db import connection
318
+
319
+ owner, name = get_owner_name_from_identifier(instance)
320
+ if _check_instance_setup() and not _test:
321
+ if (
322
+ settings._instance_exists
323
+ and f"{owner}/{name}" == settings.instance.slug
324
+ # below is to ensure that if another process interferes
325
+ # we don't use the in-memory mock database
326
+ # could be made more specific by checking whether the django
327
+ # configured database is the same as the one in settings
328
+ and connection.settings_dict["NAME"] != ":memory:"
329
+ and not use_root_db_user # always re-connect for root db user
330
+ ):
331
+ logger.important(
332
+ f"doing nothing, already connected lamindb: {settings.instance.slug}"
333
+ )
334
+ return None
335
+ else:
336
+ if (
337
+ settings._instance_exists
338
+ and settings.instance.slug != "none/none"
339
+ ):
340
+ import lamindb as ln
341
+
342
+ if ln.context.transform is not None:
343
+ raise CannotSwitchDefaultInstance(
344
+ "Cannot switch default instance while `ln.track()` is live: call `ln.finish()`"
345
+ )
346
+ reset_django()
347
+ elif (
348
+ _write_settings
349
+ and settings._instance_exists
350
+ and f"{owner}/{name}" != settings.instance.slug
351
+ ):
352
+ disconnect(mute=True)
353
+
354
+ if instance is not None or use_root_db_user:
355
+ try:
356
+ isettings = _connect_instance(
357
+ owner,
358
+ name,
359
+ db=_db,
360
+ access_token=access_token,
361
+ use_root_db_user=use_root_db_user,
362
+ )
363
+ except InstanceNotFoundError as e:
364
+ if _raise_not_found_error:
365
+ raise e
366
+ else:
367
+ return "instance-not-found"
368
+ if isinstance(isettings, str):
369
+ return isettings
370
+ # at this point we have checked already that isettings is not a string
371
+ # _user is passed to lock cloud sqlite for this user in isettings._load_db()
372
+ # has no effect if _user is None or if not cloud sqlite instance
373
+ isettings._locker_user = _user
374
+ isettings._persist(write_to_disk=_write_settings)
375
+ if _test:
376
+ return None
377
+ silence_loggers()
378
+ check, msg = isettings._load_db()
379
+ if not check:
380
+ local_db = (
381
+ isettings._is_cloud_sqlite and isettings._sqlite_file_local.exists()
382
+ )
383
+ if local_db:
384
+ logger.warning(
385
+ "SQLite file does not exist in the cloud, but exists locally:"
386
+ f" {isettings._sqlite_file_local}\nTo push the file to the cloud,"
387
+ " call: lamin disconnect"
388
+ )
389
+ elif _raise_not_found_error:
390
+ raise SystemExit(msg)
391
+ else:
392
+ logger.warning(
393
+ f"instance exists with id {isettings._id.hex}, but database is not"
394
+ " loadable: re-initializing"
395
+ )
396
+ return "instance-corrupted-or-deleted"
397
+ # this is for testing purposes only
398
+ if _TEST_FAILED_LOAD:
399
+ raise RuntimeError("Technical testing error.")
400
+
401
+ load_from_isettings(isettings, user=_user, write_settings=_write_settings)
402
+ if _reload_lamindb:
403
+ importlib.reload(importlib.import_module("lamindb"))
404
+ reset_django_module_variables()
405
+ if isettings.slug != "none/none":
406
+ logger.important(f"connected lamindb: {isettings.slug}")
407
+ except Exception as e:
408
+ if isettings is not None:
409
+ if _write_settings:
410
+ isettings._get_settings_file().unlink(missing_ok=True) # type: ignore
411
+ settings._instance_settings = None
412
+ raise e
413
+ return None
414
+
415
+
416
+ def get_owner_name_from_identifier(identifier: str):
417
+ if "/" in identifier:
418
+ if identifier.startswith("https://lamin.ai/"):
419
+ identifier = identifier.replace("https://lamin.ai/", "")
420
+ split = identifier.split("/")
421
+ if len(split) > 2:
422
+ raise ValueError(
423
+ "The instance identifier needs to be 'owner/name', the instance name"
424
+ " (owner is current user) or the URL: https://lamin.ai/owner/name."
425
+ )
426
+ owner, name = split
427
+ else:
428
+ owner = settings.user.handle
429
+ name = identifier
430
+ return owner, name