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