lamindb_setup 1.19.0__py3-none-any.whl → 1.19.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 (49) hide show
  1. lamindb_setup/__init__.py +1 -1
  2. lamindb_setup/_cache.py +87 -87
  3. lamindb_setup/_check.py +7 -7
  4. lamindb_setup/_check_setup.py +131 -131
  5. lamindb_setup/_connect_instance.py +443 -441
  6. lamindb_setup/_delete.py +155 -155
  7. lamindb_setup/_disconnect.py +38 -38
  8. lamindb_setup/_django.py +39 -39
  9. lamindb_setup/_entry_points.py +19 -19
  10. lamindb_setup/_init_instance.py +423 -423
  11. lamindb_setup/_migrate.py +331 -331
  12. lamindb_setup/_register_instance.py +32 -32
  13. lamindb_setup/_schema.py +27 -27
  14. lamindb_setup/_schema_metadata.py +451 -451
  15. lamindb_setup/_set_managed_storage.py +81 -81
  16. lamindb_setup/_setup_user.py +198 -198
  17. lamindb_setup/_silence_loggers.py +46 -46
  18. lamindb_setup/core/__init__.py +25 -34
  19. lamindb_setup/core/_aws_options.py +276 -276
  20. lamindb_setup/core/_aws_storage.py +57 -57
  21. lamindb_setup/core/_clone.py +50 -50
  22. lamindb_setup/core/_deprecated.py +62 -62
  23. lamindb_setup/core/_docs.py +14 -14
  24. lamindb_setup/core/_hub_client.py +288 -288
  25. lamindb_setup/core/_hub_crud.py +247 -247
  26. lamindb_setup/core/_hub_utils.py +100 -100
  27. lamindb_setup/core/_private_django_api.py +80 -80
  28. lamindb_setup/core/_settings.py +440 -434
  29. lamindb_setup/core/_settings_instance.py +22 -1
  30. lamindb_setup/core/_settings_load.py +162 -162
  31. lamindb_setup/core/_settings_save.py +108 -108
  32. lamindb_setup/core/_settings_storage.py +433 -433
  33. lamindb_setup/core/_settings_store.py +162 -162
  34. lamindb_setup/core/_settings_user.py +55 -55
  35. lamindb_setup/core/_setup_bionty_sources.py +44 -44
  36. lamindb_setup/core/cloud_sqlite_locker.py +240 -240
  37. lamindb_setup/core/django.py +414 -413
  38. lamindb_setup/core/exceptions.py +1 -1
  39. lamindb_setup/core/hashing.py +134 -134
  40. lamindb_setup/core/types.py +1 -1
  41. lamindb_setup/core/upath.py +1031 -1028
  42. lamindb_setup/errors.py +72 -72
  43. lamindb_setup/io.py +423 -423
  44. lamindb_setup/types.py +17 -17
  45. {lamindb_setup-1.19.0.dist-info → lamindb_setup-1.19.1.dist-info}/METADATA +3 -2
  46. lamindb_setup-1.19.1.dist-info/RECORD +51 -0
  47. {lamindb_setup-1.19.0.dist-info → lamindb_setup-1.19.1.dist-info}/WHEEL +1 -1
  48. {lamindb_setup-1.19.0.dist-info → lamindb_setup-1.19.1.dist-info/licenses}/LICENSE +201 -201
  49. lamindb_setup-1.19.0.dist-info/RECORD +0 -51
@@ -1,434 +1,440 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import sys
5
- from pathlib import Path
6
- from typing import TYPE_CHECKING
7
-
8
- import jwt
9
- from lamin_utils import logger
10
- from platformdirs import user_cache_dir
11
-
12
- from lamindb_setup.errors import CurrentInstanceNotConfigured
13
-
14
- from ._settings_load import (
15
- load_cache_path_from_settings,
16
- load_instance_settings,
17
- load_or_create_user_settings,
18
- )
19
- from ._settings_store import (
20
- current_instance_settings_file,
21
- settings_dir,
22
- system_settings_dir,
23
- )
24
- from .upath import LocalPathClasses, UPath
25
-
26
- if TYPE_CHECKING:
27
- from lamindb.models import Branch, Space
28
-
29
- from lamindb_setup.core import InstanceSettings, StorageSettings, UserSettings
30
- from lamindb_setup.types import UPathStr
31
-
32
-
33
- DEFAULT_CACHE_DIR = UPath(user_cache_dir(appname="lamindb", appauthor="laminlabs"))
34
-
35
-
36
- def _process_cache_path(cache_path: UPathStr | None) -> UPath | None:
37
- if cache_path is None or cache_path == "null":
38
- return None
39
- cache_dir = UPath(cache_path)
40
- if not isinstance(cache_dir, LocalPathClasses):
41
- raise ValueError("cache dir should be a local path.")
42
- if cache_dir.exists() and not cache_dir.is_dir():
43
- raise ValueError("cache dir should be a directory.")
44
- if not cache_dir.is_absolute():
45
- raise ValueError("A path to the cache dir should be absolute.")
46
- return cache_dir
47
-
48
-
49
- # returned by settings.branch for none/none instance
50
- class MainBranchMock:
51
- id = 1
52
- name = "main"
53
-
54
-
55
- class SetupSettings:
56
- """Setup settings."""
57
-
58
- _using_key: str | None = None # set through lamindb.settings
59
-
60
- _user_settings: UserSettings | None = None
61
- _instance_settings: InstanceSettings | None = None
62
-
63
- _user_settings_env: str | None = None
64
- _instance_settings_env: str | None = None
65
-
66
- _auto_connect_path: Path = settings_dir / "auto_connect"
67
- _private_django_api_path: Path = settings_dir / "private_django_api"
68
-
69
- _cache_dir: Path | None = None
70
-
71
- _branch = None # do not have types here
72
- _space = None # do not have types here
73
-
74
- @property
75
- def _instance_settings_path(self) -> Path:
76
- return current_instance_settings_file()
77
-
78
- @property
79
- def settings_dir(self) -> Path:
80
- """The directory that holds locally persisted settings."""
81
- return settings_dir
82
-
83
- @property
84
- def auto_connect(self) -> bool:
85
- """Auto-connect to current instance upon `import lamindb`.
86
-
87
- This setting is always `True` and will be removed in a future version.
88
- """
89
- return True
90
-
91
- @auto_connect.setter
92
- def auto_connect(self, value: bool) -> None:
93
- logger.warning(
94
- "setting auto_connect to `False` no longer has an effect and the setting will likely be removed in the future",
95
- )
96
- if value:
97
- self._auto_connect_path.touch()
98
- else:
99
- self._auto_connect_path.unlink(missing_ok=True)
100
-
101
- @property
102
- def _dev_dir_path(self) -> Path:
103
- return (
104
- settings_dir / f"dev-dir--{self.instance.owner}--{self.instance.name}.txt"
105
- )
106
-
107
- @property
108
- def dev_dir(self) -> Path | None:
109
- """Get or set the local development directory for the current instance.
110
-
111
- If setting it to `None`, the working development directory is unset.
112
- """
113
- if not self._dev_dir_path.exists():
114
- return None
115
- return Path(self._dev_dir_path.read_text())
116
-
117
- @dev_dir.setter
118
- def dev_dir(self, value: str | Path | None) -> None:
119
- if value is None:
120
- if self._dev_dir_path.exists():
121
- self._dev_dir_path.unlink()
122
- else:
123
- value_str = Path(value).expanduser().resolve().as_posix()
124
- self._dev_dir_path.write_text(value_str)
125
-
126
- @property
127
- def _branch_path(self) -> Path:
128
- return (
129
- settings_dir
130
- / f"current-branch--{self.instance.owner}--{self.instance.name}.txt"
131
- )
132
-
133
- def _read_branch_idlike_name(self) -> tuple[int | str, str]:
134
- idlike: str | int = 1
135
- name: str = "main"
136
- try:
137
- branch_path = self._branch_path
138
- except SystemExit: # in case no instance setup
139
- return idlike, name
140
- if branch_path.exists():
141
- idlike, name = branch_path.read_text().split("\n")
142
- return idlike, name
143
-
144
- @property
145
- # TODO: refactor so that it returns a BranchMock object
146
- # and we never need a DB request
147
- def branch(self) -> Branch:
148
- """Default branch."""
149
- # this is needed for .filter() with non-default connections
150
- if not self._instance_exists:
151
- return MainBranchMock()
152
-
153
- if self._branch is None:
154
- from lamindb import Branch
155
-
156
- idlike, _ = self._read_branch_idlike_name()
157
- self._branch = Branch.get(idlike)
158
- return self._branch
159
-
160
- @branch.setter
161
- def branch(self, value: str | Branch) -> None:
162
- from lamindb import Branch, Q
163
- from lamindb.errors import DoesNotExist
164
-
165
- if isinstance(value, Branch):
166
- assert value._state.adding is False, "Branch must be saved"
167
- branch_record = value
168
- else:
169
- branch_record = Branch.filter(Q(name=value) | Q(uid=value)).one_or_none()
170
- if branch_record is None:
171
- raise DoesNotExist(
172
- f"Branch '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
173
- )
174
- # we are sure that the current instance is setup because
175
- # it will error on lamindb import otherwise
176
- self._branch_path.write_text(f"{branch_record.uid}\n{branch_record.name}")
177
- self._branch = branch_record
178
-
179
- @property
180
- def _space_path(self) -> Path:
181
- return (
182
- settings_dir
183
- / f"current-space--{self.instance.owner}--{self.instance.name}.txt"
184
- )
185
-
186
- def _read_space_idlike_name(self) -> tuple[int | str, str]:
187
- idlike: str | int = 1
188
- name: str = "all"
189
- try:
190
- space_path = self._space_path
191
- except SystemExit: # in case no instance setup
192
- return idlike, name
193
- if space_path.exists():
194
- idlike, name = space_path.read_text().split("\n")
195
- return idlike, name
196
-
197
- @property
198
- # TODO: refactor so that it returns a BranchMock object
199
- # and we never need a DB request
200
- def space(self) -> Space:
201
- """Default space."""
202
- if self._space is None:
203
- from lamindb import Space
204
-
205
- idlike, _ = self._read_space_idlike_name()
206
- self._space = Space.get(idlike)
207
- return self._space
208
-
209
- @space.setter
210
- def space(self, value: str | Space) -> None:
211
- from lamindb import Q, Space
212
- from lamindb.errors import DoesNotExist
213
-
214
- if isinstance(value, Space):
215
- assert value._state.adding is False, "Space must be saved"
216
- space_record = value
217
- else:
218
- space_record = Space.filter(Q(name=value) | Q(uid=value)).one_or_none()
219
- if space_record is None:
220
- raise DoesNotExist(
221
- f"Space '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
222
- )
223
- # we are sure that the current instance is setup because
224
- # it will error on lamindb import otherwise
225
- self._space_path.write_text(f"{space_record.uid}\n{space_record.name}")
226
- self._space = space_record
227
-
228
- @property
229
- def is_connected(self) -> bool:
230
- """Determine whether the current instance is fully connected and ready to use.
231
-
232
- If `True`, the current instance is connected, meaning that the db and other settings
233
- are properly configured for use.
234
- """
235
- from . import django
236
-
237
- return self._instance_exists and django.IS_SETUP
238
-
239
- @property
240
- def private_django_api(self) -> bool:
241
- """Turn internal Django API private to clean up the API (default `False`).
242
-
243
- This patches your local pip-installed django installation.
244
- You can undo the patch by setting this back to `False`.
245
- """
246
- return self._private_django_api_path.exists()
247
-
248
- @private_django_api.setter
249
- def private_django_api(self, value: bool) -> None:
250
- from ._private_django_api import private_django_api
251
-
252
- # we don't want to call private_django_api() twice
253
- if value and not self.private_django_api:
254
- private_django_api()
255
- self._private_django_api_path.touch()
256
- elif not value and self.private_django_api:
257
- private_django_api(reverse=True)
258
- self._private_django_api_path.unlink(missing_ok=True)
259
-
260
- @property
261
- def user(self) -> UserSettings:
262
- """Settings of current user."""
263
- env_changed = (
264
- self._user_settings_env is not None
265
- and self._user_settings_env != get_env_name()
266
- )
267
- if self._user_settings is None or env_changed:
268
- # only uses LAMIN_API_KEY if there is no current_user.env
269
- self._user_settings = load_or_create_user_settings(
270
- api_key=os.environ.get("LAMIN_API_KEY")
271
- )
272
- self._user_settings_env = get_env_name()
273
- if self._user_settings and self._user_settings.uid is None:
274
- raise RuntimeError("Need to login, first: lamin login")
275
- return self._user_settings # type: ignore
276
-
277
- @property
278
- def instance(self) -> InstanceSettings:
279
- """Settings of current LaminDB instance."""
280
- env_changed = (
281
- self._instance_settings_env is not None
282
- and self._instance_settings_env != get_env_name()
283
- )
284
- if self._instance_settings is None or env_changed:
285
- self._instance_settings = load_instance_settings()
286
- self._instance_settings_env = get_env_name()
287
- return self._instance_settings # type: ignore
288
-
289
- @property
290
- def storage(self) -> StorageSettings:
291
- """Settings of default storage."""
292
- return self.instance.storage
293
-
294
- @property
295
- def _instance_exists(self):
296
- return self.instance.slug != "none/none"
297
-
298
- @property
299
- def cache_dir(self) -> UPath:
300
- """Cache root, a local directory to cache cloud files."""
301
- if "LAMIN_CACHE_DIR" in os.environ:
302
- cache_dir = UPath(os.environ["LAMIN_CACHE_DIR"])
303
- elif self._cache_dir is None:
304
- cache_path = load_cache_path_from_settings()
305
- cache_dir = _process_cache_path(cache_path)
306
- if cache_dir is None:
307
- cache_dir = DEFAULT_CACHE_DIR
308
- self._cache_dir = cache_dir
309
- else:
310
- cache_dir = self._cache_dir
311
- try:
312
- cache_dir.mkdir(parents=True, exist_ok=True)
313
- # we don not want this to error
314
- # beause no actual writing happens on just getting the cache dir
315
- # in cloud_to_local_no_update for example
316
- # so it should not fail on read-only systems
317
- except Exception as e:
318
- logger.warning(
319
- f"Failed to create lamin cache directory at {cache_dir}: {e}"
320
- )
321
- return cache_dir
322
-
323
- @property
324
- def paths(self) -> type[SetupPaths]:
325
- """Convert cloud paths to lamindb local paths.
326
-
327
- Use `settings.paths.cloud_to_local_no_update` or `settings.paths.cloud_to_local`.
328
- """
329
- return SetupPaths
330
-
331
- def _debug_db_access(self):
332
- """Debug database access problems."""
333
- instance = self.instance
334
- db_permissions = instance._db_permissions
335
- print("db connection: ", instance.db)
336
- print("db permissions: ", db_permissions)
337
- if db_permissions != "jwt":
338
- return
339
- # sets the token if not present yet
340
- print("available spaces: ", instance.available_spaces)
341
-
342
- from lamindb_setup.core.django import db_token_manager
343
-
344
- tokens = db_token_manager.tokens
345
- if tokens:
346
- for conn, token in tokens.items():
347
- token_encoded = token._token
348
- if token_encoded is None:
349
- token._refresh_token()
350
- token_encoded = token._token
351
- token_decoded = jwt.decode(
352
- token_encoded, options={"verify_signature": False}
353
- )
354
- print(
355
- f"db token for the connection '{conn}' is '{token_encoded}': {token_decoded}"
356
- )
357
- else:
358
- print("no db tokens are present")
359
-
360
- def __repr__(self) -> str:
361
- """Rich string representation."""
362
- from lamin_utils import colors
363
-
364
- # do not show current setting representation when building docs
365
- if "sphinx" in sys.modules:
366
- return object.__repr__(self)
367
-
368
- repr = ""
369
- if self._instance_exists:
370
- instance_rep = self.instance.__repr__().split("\n")
371
- repr += f"{colors.cyan('Instance:')} {instance_rep[0].replace('Instance: ', '')}\n"
372
- repr += f" - branch: {self._read_branch_idlike_name()[1]}\n"
373
- repr += f" - space: {self._read_space_idlike_name()[1]}\n"
374
- repr += f" - dev-dir: {self.dev_dir}"
375
- repr += f"\n{colors.yellow('Details:')}\n"
376
- repr += "\n".join(instance_rep[1:])
377
- else:
378
- repr += f"{colors.cyan('Instance:')} None"
379
- repr += f"\n{colors.blue('Cache & settings:')}\n"
380
- repr += f" - cache: {self.cache_dir.as_posix()}\n"
381
- repr += f" - user settings: {settings_dir.as_posix()}\n"
382
- repr += f" - system settings: {system_settings_dir.as_posix()}"
383
- repr += f"\n{colors.green('User:')} {self.user.handle}"
384
- return repr
385
-
386
-
387
- class SetupPaths:
388
- """A static class for conversion of cloud paths to lamindb local paths."""
389
-
390
- @staticmethod
391
- def cloud_to_local_no_update(
392
- filepath: UPathStr, cache_key: str | None = None
393
- ) -> UPath:
394
- """Local (or local cache) filepath from filepath without synchronization."""
395
- if not isinstance(filepath, UPath):
396
- filepath = UPath(filepath)
397
- # cache_key is ignored if filepath is a local path
398
- if not isinstance(filepath, LocalPathClasses):
399
- # settings is defined further in this file
400
- if cache_key is None:
401
- local_key = filepath.path # type: ignore
402
- protocol = filepath.protocol # type: ignore
403
- if protocol in {"http", "https"}:
404
- local_key = local_key.removeprefix(protocol + "://")
405
- else:
406
- local_key = cache_key
407
- local_filepath = settings.cache_dir / local_key
408
- else:
409
- local_filepath = filepath
410
- return local_filepath
411
-
412
- @staticmethod
413
- def cloud_to_local(
414
- filepath: UPathStr, cache_key: str | None = None, **kwargs
415
- ) -> UPath:
416
- """Local (or local cache) filepath from filepath."""
417
- if not isinstance(filepath, UPath):
418
- filepath = UPath(filepath)
419
- # cache_key is ignored in cloud_to_local_no_update if filepath is local
420
- local_filepath = SetupPaths.cloud_to_local_no_update(filepath, cache_key)
421
- if not isinstance(filepath, LocalPathClasses):
422
- local_filepath.parent.mkdir(parents=True, exist_ok=True)
423
- filepath.synchronize_to(local_filepath, **kwargs) # type: ignore
424
- return local_filepath
425
-
426
-
427
- def get_env_name():
428
- if "LAMIN_ENV" in os.environ:
429
- return os.environ["LAMIN_ENV"]
430
- else:
431
- return "prod"
432
-
433
-
434
- settings = SetupSettings()
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ import jwt
9
+ from lamin_utils import logger
10
+ from platformdirs import user_cache_dir
11
+
12
+ from ._settings_load import (
13
+ load_cache_path_from_settings,
14
+ load_instance_settings,
15
+ load_or_create_user_settings,
16
+ )
17
+ from ._settings_store import (
18
+ current_instance_settings_file,
19
+ settings_dir,
20
+ system_settings_dir,
21
+ )
22
+ from .upath import LocalPathClasses, UPath
23
+
24
+ if TYPE_CHECKING:
25
+ from lamindb.models import Branch, Space
26
+
27
+ from lamindb_setup.core import InstanceSettings, StorageSettings, UserSettings
28
+ from lamindb_setup.core.django import DBToken, DBTokenManager
29
+ from lamindb_setup.types import UPathStr
30
+
31
+
32
+ DEFAULT_CACHE_DIR = UPath(user_cache_dir(appname="lamindb", appauthor="laminlabs"))
33
+
34
+
35
+ def _process_cache_path(cache_path: UPathStr | None) -> UPath | None:
36
+ if cache_path is None or cache_path == "null":
37
+ return None
38
+ cache_dir = UPath(cache_path)
39
+ if not isinstance(cache_dir, LocalPathClasses):
40
+ raise ValueError("cache dir should be a local path.")
41
+ if cache_dir.exists() and not cache_dir.is_dir():
42
+ raise ValueError("cache dir should be a directory.")
43
+ if not cache_dir.is_absolute():
44
+ raise ValueError("A path to the cache dir should be absolute.")
45
+ return cache_dir
46
+
47
+
48
+ # returned by settings.branch for none/none instance
49
+ class MainBranchMock:
50
+ id = 1
51
+ name = "main"
52
+
53
+
54
+ class SetupSettings:
55
+ """Setup settings."""
56
+
57
+ _using_key: str | None = None # set through lamindb.settings
58
+
59
+ _user_settings: UserSettings | None = None
60
+ _instance_settings: InstanceSettings | None = None
61
+
62
+ _user_settings_env: str | None = None
63
+ _instance_settings_env: str | None = None
64
+
65
+ _auto_connect_path: Path = settings_dir / "auto_connect"
66
+ _private_django_api_path: Path = settings_dir / "private_django_api"
67
+
68
+ _cache_dir: Path | None = None
69
+
70
+ _branch = None # do not have types here
71
+ _space = None # do not have types here
72
+
73
+ @property
74
+ def _instance_settings_path(self) -> Path:
75
+ return current_instance_settings_file()
76
+
77
+ @property
78
+ def settings_dir(self) -> Path:
79
+ """The directory that holds locally persisted settings."""
80
+ return settings_dir
81
+
82
+ @property
83
+ def auto_connect(self) -> bool:
84
+ """Auto-connect to current instance upon `import lamindb`.
85
+
86
+ This setting is always `True` and will be removed in a future version.
87
+ """
88
+ return True
89
+
90
+ @auto_connect.setter
91
+ def auto_connect(self, value: bool) -> None:
92
+ logger.warning(
93
+ "setting auto_connect to `False` no longer has an effect and the setting will likely be removed in the future",
94
+ )
95
+ if value:
96
+ self._auto_connect_path.touch()
97
+ else:
98
+ self._auto_connect_path.unlink(missing_ok=True)
99
+
100
+ @property
101
+ def _dev_dir_path(self) -> Path:
102
+ return (
103
+ settings_dir / f"dev-dir--{self.instance.owner}--{self.instance.name}.txt"
104
+ )
105
+
106
+ @property
107
+ def dev_dir(self) -> Path | None:
108
+ """Get or set the local development directory for the current instance.
109
+
110
+ If setting it to `None`, the working development directory is unset.
111
+ """
112
+ if not self._dev_dir_path.exists():
113
+ return None
114
+ return Path(self._dev_dir_path.read_text())
115
+
116
+ @dev_dir.setter
117
+ def dev_dir(self, value: str | Path | None) -> None:
118
+ if value is None:
119
+ if self._dev_dir_path.exists():
120
+ self._dev_dir_path.unlink()
121
+ else:
122
+ value_str = Path(value).expanduser().resolve().as_posix()
123
+ self._dev_dir_path.write_text(value_str)
124
+
125
+ @property
126
+ def _branch_path(self) -> Path:
127
+ return (
128
+ settings_dir
129
+ / f"current-branch--{self.instance.owner}--{self.instance.name}.txt"
130
+ )
131
+
132
+ def _read_branch_idlike_name(self) -> tuple[int | str, str]:
133
+ idlike: str | int = 1
134
+ name: str = "main"
135
+ try:
136
+ branch_path = self._branch_path
137
+ except SystemExit: # in case no instance setup
138
+ return idlike, name
139
+ if branch_path.exists():
140
+ idlike, name = branch_path.read_text().split("\n")
141
+ return idlike, name
142
+
143
+ @property
144
+ # TODO: refactor so that it returns a BranchMock object
145
+ # and we never need a DB request
146
+ def branch(self) -> Branch:
147
+ """Default branch."""
148
+ # this is needed for .filter() with non-default connections
149
+ if not self._instance_exists:
150
+ return MainBranchMock()
151
+
152
+ if self._branch is None:
153
+ from lamindb import Branch
154
+
155
+ idlike, _ = self._read_branch_idlike_name()
156
+ self._branch = Branch.get(idlike)
157
+ return self._branch
158
+
159
+ @branch.setter
160
+ def branch(self, value: str | Branch) -> None:
161
+ from lamindb import Branch, Q
162
+ from lamindb.errors import DoesNotExist
163
+
164
+ if isinstance(value, Branch):
165
+ assert value._state.adding is False, "Branch must be saved"
166
+ branch_record = value
167
+ else:
168
+ branch_record = Branch.filter(Q(name=value) | Q(uid=value)).one_or_none()
169
+ if branch_record is None:
170
+ raise DoesNotExist(
171
+ f"Branch '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
172
+ )
173
+ # we are sure that the current instance is setup because
174
+ # it will error on lamindb import otherwise
175
+ self._branch_path.write_text(f"{branch_record.uid}\n{branch_record.name}")
176
+ self._branch = branch_record
177
+
178
+ @property
179
+ def _space_path(self) -> Path:
180
+ return (
181
+ settings_dir
182
+ / f"current-space--{self.instance.owner}--{self.instance.name}.txt"
183
+ )
184
+
185
+ def _read_space_idlike_name(self) -> tuple[int | str, str]:
186
+ idlike: str | int = 1
187
+ name: str = "all"
188
+ try:
189
+ space_path = self._space_path
190
+ except SystemExit: # in case no instance setup
191
+ return idlike, name
192
+ if space_path.exists():
193
+ idlike, name = space_path.read_text().split("\n")
194
+ return idlike, name
195
+
196
+ @property
197
+ # TODO: refactor so that it returns a BranchMock object
198
+ # and we never need a DB request
199
+ def space(self) -> Space:
200
+ """Default space."""
201
+ if self._space is None:
202
+ from lamindb import Space
203
+
204
+ idlike, _ = self._read_space_idlike_name()
205
+ self._space = Space.get(idlike)
206
+ return self._space
207
+
208
+ @space.setter
209
+ def space(self, value: str | Space) -> None:
210
+ from lamindb import Q, Space
211
+ from lamindb.errors import DoesNotExist
212
+
213
+ if isinstance(value, Space):
214
+ assert value._state.adding is False, "Space must be saved"
215
+ space_record = value
216
+ else:
217
+ space_record = Space.filter(Q(name=value) | Q(uid=value)).one_or_none()
218
+ if space_record is None:
219
+ raise DoesNotExist(
220
+ f"Space '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
221
+ )
222
+ # we are sure that the current instance is setup because
223
+ # it will error on lamindb import otherwise
224
+ self._space_path.write_text(f"{space_record.uid}\n{space_record.name}")
225
+ self._space = space_record
226
+
227
+ @property
228
+ def is_connected(self) -> bool:
229
+ """Determine whether the current instance is fully connected and ready to use.
230
+
231
+ If `True`, the current instance is connected, meaning that the db and other settings
232
+ are properly configured for use.
233
+ """
234
+ from . import django
235
+
236
+ return self._instance_exists and django.IS_SETUP
237
+
238
+ @property
239
+ def private_django_api(self) -> bool:
240
+ """Turn internal Django API private to clean up the API (default `False`).
241
+
242
+ This patches your local pip-installed django installation.
243
+ You can undo the patch by setting this back to `False`.
244
+ """
245
+ return self._private_django_api_path.exists()
246
+
247
+ @private_django_api.setter
248
+ def private_django_api(self, value: bool) -> None:
249
+ from ._private_django_api import private_django_api
250
+
251
+ # we don't want to call private_django_api() twice
252
+ if value and not self.private_django_api:
253
+ private_django_api()
254
+ self._private_django_api_path.touch()
255
+ elif not value and self.private_django_api:
256
+ private_django_api(reverse=True)
257
+ self._private_django_api_path.unlink(missing_ok=True)
258
+
259
+ @property
260
+ def user(self) -> UserSettings:
261
+ """Settings of current user."""
262
+ env_changed = (
263
+ self._user_settings_env is not None
264
+ and self._user_settings_env != get_env_name()
265
+ )
266
+ if self._user_settings is None or env_changed:
267
+ # only uses LAMIN_API_KEY if there is no current_user.env
268
+ self._user_settings = load_or_create_user_settings(
269
+ api_key=os.environ.get("LAMIN_API_KEY")
270
+ )
271
+ self._user_settings_env = get_env_name()
272
+ if self._user_settings and self._user_settings.uid is None:
273
+ raise RuntimeError("Need to login, first: lamin login")
274
+ return self._user_settings # type: ignore
275
+
276
+ @property
277
+ def instance(self) -> InstanceSettings:
278
+ """Settings of current LaminDB instance."""
279
+ env_changed = (
280
+ self._instance_settings_env is not None
281
+ and self._instance_settings_env != get_env_name()
282
+ )
283
+ if self._instance_settings is None or env_changed:
284
+ self._instance_settings = load_instance_settings()
285
+ self._instance_settings_env = get_env_name()
286
+ return self._instance_settings # type: ignore
287
+
288
+ @property
289
+ def storage(self) -> StorageSettings:
290
+ """Settings of default storage."""
291
+ return self.instance.storage
292
+
293
+ @property
294
+ def _instance_exists(self):
295
+ return self.instance.slug != "none/none"
296
+
297
+ @property
298
+ def cache_dir(self) -> UPath:
299
+ """Cache root, a local directory to cache cloud files."""
300
+ if "LAMIN_CACHE_DIR" in os.environ:
301
+ cache_dir = UPath(os.environ["LAMIN_CACHE_DIR"])
302
+ elif self._cache_dir is None:
303
+ cache_path = load_cache_path_from_settings()
304
+ cache_dir = _process_cache_path(cache_path)
305
+ if cache_dir is None:
306
+ cache_dir = DEFAULT_CACHE_DIR
307
+ self._cache_dir = cache_dir
308
+ else:
309
+ cache_dir = self._cache_dir
310
+ try:
311
+ cache_dir.mkdir(parents=True, exist_ok=True)
312
+ # we don not want this to error
313
+ # beause no actual writing happens on just getting the cache dir
314
+ # in cloud_to_local_no_update for example
315
+ # so it should not fail on read-only systems
316
+ except Exception as e:
317
+ logger.warning(
318
+ f"Failed to create lamin cache directory at {cache_dir}: {e}"
319
+ )
320
+ return cache_dir
321
+
322
+ @property
323
+ def paths(self) -> type[SetupPaths]:
324
+ """Convert cloud paths to lamindb local paths.
325
+
326
+ Use `settings.paths.cloud_to_local_no_update` or `settings.paths.cloud_to_local`.
327
+ """
328
+ return SetupPaths
329
+
330
+ @property
331
+ def _db_token_manager(self) -> DBTokenManager:
332
+ from lamindb_setup.core.django import db_token_manager
333
+
334
+ return db_token_manager
335
+
336
+ def _get_db_token(self, connection_name: str = "default") -> DBToken | None:
337
+ return self._db_token_manager.tokens.get(connection_name, None)
338
+
339
+ def _debug_db_access(self):
340
+ """Debug database access problems."""
341
+ instance = self.instance
342
+ db_permissions = instance._db_permissions
343
+ print("db connection: ", instance.db)
344
+ print("db permissions: ", db_permissions)
345
+ if db_permissions != "jwt":
346
+ return
347
+ # sets the token if not present yet
348
+ print("available spaces: ", instance.available_spaces)
349
+
350
+ tokens = self._db_token_manager.tokens
351
+ if tokens:
352
+ for conn, token in tokens.items():
353
+ token_encoded = token._token
354
+ if token_encoded is None:
355
+ token._refresh_token()
356
+ token_encoded = token._token
357
+ token_decoded = jwt.decode(
358
+ token_encoded, options={"verify_signature": False}
359
+ )
360
+ print(
361
+ f"db token for the connection '{conn}' is '{token_encoded}': {token_decoded}"
362
+ )
363
+ else:
364
+ print("no db tokens are present")
365
+
366
+ def __repr__(self) -> str:
367
+ """Rich string representation."""
368
+ from lamin_utils import colors
369
+
370
+ # do not show current setting representation when building docs
371
+ if "sphinx" in sys.modules:
372
+ return object.__repr__(self)
373
+
374
+ repr = ""
375
+ if self._instance_exists:
376
+ instance_rep = self.instance.__repr__().split("\n")
377
+ repr += f"{colors.cyan('Instance:')} {instance_rep[0].replace('Instance: ', '')}\n"
378
+ repr += f" - branch: {self._read_branch_idlike_name()[1]}\n"
379
+ repr += f" - space: {self._read_space_idlike_name()[1]}\n"
380
+ repr += f" - dev-dir: {self.dev_dir}"
381
+ repr += f"\n{colors.yellow('Details:')}\n"
382
+ repr += "\n".join(instance_rep[1:])
383
+ else:
384
+ repr += f"{colors.cyan('Instance:')} None"
385
+ repr += f"\n{colors.blue('Cache & settings:')}\n"
386
+ repr += f" - cache: {self.cache_dir.as_posix()}\n"
387
+ repr += f" - user settings: {settings_dir.as_posix()}\n"
388
+ repr += f" - system settings: {system_settings_dir.as_posix()}"
389
+ repr += f"\n{colors.green('User:')} {self.user.handle}"
390
+ return repr
391
+
392
+
393
+ class SetupPaths:
394
+ """A static class for conversion of cloud paths to lamindb local paths."""
395
+
396
+ @staticmethod
397
+ def cloud_to_local_no_update(
398
+ filepath: UPathStr, cache_key: str | None = None
399
+ ) -> UPath:
400
+ """Local (or local cache) filepath from filepath without synchronization."""
401
+ if not isinstance(filepath, UPath):
402
+ filepath = UPath(filepath)
403
+ # cache_key is ignored if filepath is a local path
404
+ if not isinstance(filepath, LocalPathClasses):
405
+ # settings is defined further in this file
406
+ if cache_key is None:
407
+ local_key = filepath.path # type: ignore
408
+ protocol = filepath.protocol # type: ignore
409
+ if protocol in {"http", "https"}:
410
+ local_key = local_key.removeprefix(protocol + "://")
411
+ else:
412
+ local_key = cache_key
413
+ local_filepath = settings.cache_dir / local_key
414
+ else:
415
+ local_filepath = filepath
416
+ return local_filepath
417
+
418
+ @staticmethod
419
+ def cloud_to_local(
420
+ filepath: UPathStr, cache_key: str | None = None, **kwargs
421
+ ) -> UPath:
422
+ """Local (or local cache) filepath from filepath."""
423
+ if not isinstance(filepath, UPath):
424
+ filepath = UPath(filepath)
425
+ # cache_key is ignored in cloud_to_local_no_update if filepath is local
426
+ local_filepath = SetupPaths.cloud_to_local_no_update(filepath, cache_key)
427
+ if not isinstance(filepath, LocalPathClasses):
428
+ local_filepath.parent.mkdir(parents=True, exist_ok=True)
429
+ filepath.synchronize_to(local_filepath, **kwargs) # type: ignore
430
+ return local_filepath
431
+
432
+
433
+ def get_env_name():
434
+ if "LAMIN_ENV" in os.environ:
435
+ return os.environ["LAMIN_ENV"]
436
+ else:
437
+ return "prod"
438
+
439
+
440
+ settings = SetupSettings()