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,377 +1,374 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import sys
5
- from typing import TYPE_CHECKING
6
-
7
- from lamin_utils import logger
8
- from platformdirs import user_cache_dir
9
-
10
- from ._settings_load import (
11
- load_cache_path_from_settings,
12
- load_instance_settings,
13
- load_or_create_user_settings,
14
- )
15
- from ._settings_store import (
16
- current_instance_settings_file,
17
- settings_dir,
18
- system_settings_dir,
19
- )
20
- from .upath import LocalPathClasses, UPath
21
-
22
- if TYPE_CHECKING:
23
- from pathlib import Path
24
-
25
- from lamindb.models import Branch, Space
26
-
27
- from lamindb_setup.core import InstanceSettings, StorageSettings, UserSettings
28
- from lamindb_setup.types import UPathStr
29
-
30
-
31
- DEFAULT_CACHE_DIR = UPath(user_cache_dir(appname="lamindb", appauthor="laminlabs"))
32
-
33
-
34
- def _process_cache_path(cache_path: UPathStr | None) -> UPath | None:
35
- if cache_path is None or cache_path == "null":
36
- return None
37
- cache_dir = UPath(cache_path)
38
- if not isinstance(cache_dir, LocalPathClasses):
39
- raise ValueError("cache dir should be a local path.")
40
- if cache_dir.exists() and not cache_dir.is_dir():
41
- raise ValueError("cache dir should be a directory.")
42
- if not cache_dir.is_absolute():
43
- raise ValueError("A path to the cache dir should be absolute.")
44
- return cache_dir
45
-
46
-
47
- class SetupSettings:
48
- """Setup settings."""
49
-
50
- _using_key: str | None = None # set through lamindb.settings
51
-
52
- _user_settings: UserSettings | None = None
53
- _instance_settings: InstanceSettings | None = None
54
-
55
- _user_settings_env: str | None = None
56
- _instance_settings_env: str | None = None
57
-
58
- _auto_connect_path: Path = settings_dir / "auto_connect"
59
- _private_django_api_path: Path = settings_dir / "private_django_api"
60
-
61
- _cache_dir: Path | None = None
62
-
63
- _branch = None # do not have types here
64
- _space = None # do not have types here
65
-
66
- @property
67
- def _instance_settings_path(self) -> Path:
68
- return current_instance_settings_file()
69
-
70
- @property
71
- def settings_dir(self) -> Path:
72
- """The directory that holds locally persisted settings."""
73
- return settings_dir
74
-
75
- @property
76
- def auto_connect(self) -> bool:
77
- """Auto-connect to current instance upon `import lamindb`.
78
-
79
- Upon installing `lamindb`, this setting is `False`.
80
-
81
- Upon calling `lamin init` or `lamin connect` on the CLI, this setting is switched to `True`.
82
-
83
- `ln.connect()` doesn't change the value of this setting.
84
-
85
- You can manually change this setting
86
-
87
- - in Python: `ln.setup.settings.auto_connect = True/False`
88
- - via the CLI: `lamin settings set auto-connect true/false`
89
- """
90
- return self._auto_connect_path.exists()
91
-
92
- @auto_connect.setter
93
- def auto_connect(self, value: bool) -> None:
94
- if value:
95
- self._auto_connect_path.touch()
96
- else:
97
- self._auto_connect_path.unlink(missing_ok=True)
98
-
99
- @property
100
- def _branch_path(self) -> Path:
101
- return (
102
- settings_dir
103
- / f"current-branch--{self.instance.owner}--{self.instance.name}.txt"
104
- )
105
-
106
- def _read_branch_idlike_name(self) -> tuple[int | str, str]:
107
- idlike: str | int = 1
108
- name: str = "main"
109
- try:
110
- branch_path = self._branch_path
111
- except SystemExit: # in case no instance setup
112
- return idlike, name
113
- if branch_path.exists():
114
- idlike, name = branch_path.read_text().split("\n")
115
- return idlike, name
116
-
117
- @property
118
- # TODO: refactor so that it returns a BranchMock object
119
- # and we never need a DB request
120
- def branch(self) -> Branch:
121
- """Default branch."""
122
- if self._branch is None:
123
- from lamindb import Branch
124
-
125
- idlike, _ = self._read_branch_idlike_name()
126
- self._branch = Branch.get(idlike)
127
- return self._branch
128
-
129
- @branch.setter
130
- def branch(self, value: str | Branch) -> None:
131
- from lamindb import Branch, Q
132
- from lamindb.errors import DoesNotExist
133
-
134
- if isinstance(value, Branch):
135
- assert value._state.adding is False, "Branch must be saved"
136
- branch_record = value
137
- else:
138
- branch_record = Branch.filter(Q(name=value) | Q(uid=value)).one_or_none()
139
- if branch_record is None:
140
- raise DoesNotExist(
141
- f"Branch '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
142
- )
143
- # we are sure that the current instance is setup because
144
- # it will error on lamindb import otherwise
145
- self._branch_path.write_text(f"{branch_record.uid}\n{branch_record.name}")
146
- self._branch = branch_record
147
-
148
- @property
149
- def _space_path(self) -> Path:
150
- return (
151
- settings_dir
152
- / f"current-space--{self.instance.owner}--{self.instance.name}.txt"
153
- )
154
-
155
- def _read_space_idlike_name(self) -> tuple[int | str, str]:
156
- idlike: str | int = 1
157
- name: str = "all"
158
- try:
159
- space_path = self._space_path
160
- except SystemExit: # in case no instance setup
161
- return idlike, name
162
- if space_path.exists():
163
- idlike, name = space_path.read_text().split("\n")
164
- return idlike, name
165
-
166
- @property
167
- # TODO: refactor so that it returns a BranchMock object
168
- # and we never need a DB request
169
- def space(self) -> Space:
170
- """Default space."""
171
- if self._space is None:
172
- from lamindb import Space
173
-
174
- idlike, _ = self._read_space_idlike_name()
175
- self._space = Space.get(idlike)
176
- return self._space
177
-
178
- @space.setter
179
- def space(self, value: str | Space) -> None:
180
- from lamindb import Q, Space
181
- from lamindb.errors import DoesNotExist
182
-
183
- if isinstance(value, Space):
184
- assert value._state.adding is False, "Space must be saved"
185
- space_record = value
186
- else:
187
- space_record = Space.filter(Q(name=value) | Q(uid=value)).one_or_none()
188
- if space_record is None:
189
- raise DoesNotExist(
190
- f"Space '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
191
- )
192
- # we are sure that the current instance is setup because
193
- # it will error on lamindb import otherwise
194
- self._space_path.write_text(f"{space_record.uid}\n{space_record.name}")
195
- self._space = space_record
196
-
197
- @property
198
- def is_connected(self) -> bool:
199
- """Determine whether the current instance is fully connected and ready to use.
200
-
201
- If `True`, the current instance is connected, meaning that the db and other settings
202
- are properly configured for use.
203
- """
204
- from .django import IS_SETUP # always import to protect from assignment
205
-
206
- return IS_SETUP
207
-
208
- @property
209
- def private_django_api(self) -> bool:
210
- """Turn internal Django API private to clean up the API (default `False`).
211
-
212
- This patches your local pip-installed django installation. You can undo
213
- the patch by setting this back to `False`.
214
- """
215
- return self._private_django_api_path.exists()
216
-
217
- @private_django_api.setter
218
- def private_django_api(self, value: bool) -> None:
219
- from ._private_django_api import private_django_api
220
-
221
- # we don't want to call private_django_api() twice
222
- if value and not self.private_django_api:
223
- private_django_api()
224
- self._private_django_api_path.touch()
225
- elif not value and self.private_django_api:
226
- private_django_api(reverse=True)
227
- self._private_django_api_path.unlink(missing_ok=True)
228
-
229
- @property
230
- def user(self) -> UserSettings:
231
- """Settings of current user."""
232
- env_changed = (
233
- self._user_settings_env is not None
234
- and self._user_settings_env != get_env_name()
235
- )
236
- if self._user_settings is None or env_changed:
237
- # only uses LAMIN_API_KEY if there is no current_user.env
238
- self._user_settings = load_or_create_user_settings(
239
- api_key=os.environ.get("LAMIN_API_KEY")
240
- )
241
- self._user_settings_env = get_env_name()
242
- if self._user_settings and self._user_settings.uid is None:
243
- raise RuntimeError("Need to login, first: lamin login")
244
- return self._user_settings # type: ignore
245
-
246
- @property
247
- def instance(self) -> InstanceSettings:
248
- """Settings of current LaminDB instance."""
249
- env_changed = (
250
- self._instance_settings_env is not None
251
- and self._instance_settings_env != get_env_name()
252
- )
253
- if self._instance_settings is None or env_changed:
254
- self._instance_settings = load_instance_settings()
255
- self._instance_settings_env = get_env_name()
256
- return self._instance_settings # type: ignore
257
-
258
- @property
259
- def storage(self) -> StorageSettings:
260
- """Settings of default storage."""
261
- return self.instance.storage
262
-
263
- @property
264
- def _instance_exists(self):
265
- try:
266
- self.instance # noqa
267
- return True
268
- # this is implicit logic that catches if no instance is loaded
269
- except SystemExit:
270
- return False
271
-
272
- @property
273
- def cache_dir(self) -> UPath:
274
- """Cache root, a local directory to cache cloud files."""
275
- if "LAMIN_CACHE_DIR" in os.environ:
276
- cache_dir = UPath(os.environ["LAMIN_CACHE_DIR"])
277
- elif self._cache_dir is None:
278
- cache_path = load_cache_path_from_settings()
279
- cache_dir = _process_cache_path(cache_path)
280
- if cache_dir is None:
281
- cache_dir = DEFAULT_CACHE_DIR
282
- self._cache_dir = cache_dir
283
- else:
284
- cache_dir = self._cache_dir
285
- try:
286
- cache_dir.mkdir(parents=True, exist_ok=True)
287
- # we don not want this to error
288
- # beause no actual writing happens on just getting the cache dir
289
- # in cloud_to_local_no_update for example
290
- # so it should not fail on read-only systems
291
- except Exception as e:
292
- logger.warning(
293
- f"Failed to create lamin cache directory at {cache_dir}: {e}"
294
- )
295
- return cache_dir
296
-
297
- @property
298
- def paths(self) -> type[SetupPaths]:
299
- """Convert cloud paths to lamindb local paths.
300
-
301
- Use `settings.paths.cloud_to_local_no_update`
302
- or `settings.paths.cloud_to_local`.
303
- """
304
- return SetupPaths
305
-
306
- def __repr__(self) -> str:
307
- """Rich string representation."""
308
- # do not show current setting representation when building docs
309
- if "sphinx" in sys.modules:
310
- return object.__repr__(self)
311
- repr = ""
312
- if self._instance_exists:
313
- repr += "Current branch & space:\n"
314
- repr += f" - branch: {self._read_branch_idlike_name()[1]}\n"
315
- repr += f" - space: {self._read_space_idlike_name()[1]}\n"
316
- repr += self.instance.__repr__()
317
- else:
318
- repr += "Current instance: None"
319
- repr += "\nConfig:\n"
320
- repr += f" - auto-connect in Python: {self.auto_connect}\n"
321
- repr += f" - private Django API: {self.private_django_api}\n"
322
- repr += "Local directories:\n"
323
- repr += f" - cache: {self.cache_dir.as_posix()}\n"
324
- repr += f" - user settings: {settings_dir.as_posix()}\n"
325
- repr += f" - system settings: {system_settings_dir.as_posix()}\n"
326
- repr += self.user.__repr__()
327
- return repr
328
-
329
-
330
- class SetupPaths:
331
- """A static class for conversion of cloud paths to lamindb local paths."""
332
-
333
- @staticmethod
334
- def cloud_to_local_no_update(
335
- filepath: UPathStr, cache_key: str | None = None
336
- ) -> UPath:
337
- """Local (or local cache) filepath from filepath without synchronization."""
338
- if not isinstance(filepath, UPath):
339
- filepath = UPath(filepath)
340
- # cache_key is ignored if filepath is a local path
341
- if not isinstance(filepath, LocalPathClasses):
342
- # settings is defined further in this file
343
- if cache_key is None:
344
- local_key = filepath.path # type: ignore
345
- protocol = filepath.protocol # type: ignore
346
- if protocol in {"http", "https"}:
347
- local_key = local_key.removeprefix(protocol + "://")
348
- else:
349
- local_key = cache_key
350
- local_filepath = settings.cache_dir / local_key
351
- else:
352
- local_filepath = filepath
353
- return local_filepath
354
-
355
- @staticmethod
356
- def cloud_to_local(
357
- filepath: UPathStr, cache_key: str | None = None, **kwargs
358
- ) -> UPath:
359
- """Local (or local cache) filepath from filepath."""
360
- if not isinstance(filepath, UPath):
361
- filepath = UPath(filepath)
362
- # cache_key is ignored in cloud_to_local_no_update if filepath is local
363
- local_filepath = SetupPaths.cloud_to_local_no_update(filepath, cache_key)
364
- if not isinstance(filepath, LocalPathClasses):
365
- local_filepath.parent.mkdir(parents=True, exist_ok=True)
366
- filepath.synchronize_to(local_filepath, **kwargs) # type: ignore
367
- return local_filepath
368
-
369
-
370
- def get_env_name():
371
- if "LAMIN_ENV" in os.environ:
372
- return os.environ["LAMIN_ENV"]
373
- else:
374
- return "prod"
375
-
376
-
377
- settings = SetupSettings()
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ import warnings
6
+ from typing import TYPE_CHECKING
7
+
8
+ from lamin_utils import logger
9
+ from platformdirs import user_cache_dir
10
+
11
+ from lamindb_setup.errors import CurrentInstanceNotConfigured
12
+
13
+ from ._settings_load import (
14
+ load_cache_path_from_settings,
15
+ load_instance_settings,
16
+ load_or_create_user_settings,
17
+ )
18
+ from ._settings_store import (
19
+ current_instance_settings_file,
20
+ settings_dir,
21
+ system_settings_dir,
22
+ )
23
+ from .upath import LocalPathClasses, UPath
24
+
25
+ if TYPE_CHECKING:
26
+ from pathlib import Path
27
+
28
+ from lamindb.models import Branch, Space
29
+
30
+ from lamindb_setup.core import InstanceSettings, StorageSettings, UserSettings
31
+ from lamindb_setup.types import UPathStr
32
+
33
+
34
+ DEFAULT_CACHE_DIR = UPath(user_cache_dir(appname="lamindb", appauthor="laminlabs"))
35
+
36
+
37
+ def _process_cache_path(cache_path: UPathStr | None) -> UPath | None:
38
+ if cache_path is None or cache_path == "null":
39
+ return None
40
+ cache_dir = UPath(cache_path)
41
+ if not isinstance(cache_dir, LocalPathClasses):
42
+ raise ValueError("cache dir should be a local path.")
43
+ if cache_dir.exists() and not cache_dir.is_dir():
44
+ raise ValueError("cache dir should be a directory.")
45
+ if not cache_dir.is_absolute():
46
+ raise ValueError("A path to the cache dir should be absolute.")
47
+ return cache_dir
48
+
49
+
50
+ class SetupSettings:
51
+ """Setup settings."""
52
+
53
+ _using_key: str | None = None # set through lamindb.settings
54
+
55
+ _user_settings: UserSettings | None = None
56
+ _instance_settings: InstanceSettings | None = None
57
+
58
+ _user_settings_env: str | None = None
59
+ _instance_settings_env: str | None = None
60
+
61
+ _auto_connect_path: Path = settings_dir / "auto_connect"
62
+ _private_django_api_path: Path = settings_dir / "private_django_api"
63
+
64
+ _cache_dir: Path | None = None
65
+
66
+ _branch = None # do not have types here
67
+ _space = None # do not have types here
68
+
69
+ @property
70
+ def _instance_settings_path(self) -> Path:
71
+ return current_instance_settings_file()
72
+
73
+ @property
74
+ def settings_dir(self) -> Path:
75
+ """The directory that holds locally persisted settings."""
76
+ return settings_dir
77
+
78
+ @property
79
+ def auto_connect(self) -> bool:
80
+ """Auto-connect to current instance upon `import lamindb`.
81
+
82
+ This setting is always `True` and will be removed in a future version.
83
+ """
84
+ return True
85
+
86
+ @auto_connect.setter
87
+ def auto_connect(self, value: bool) -> None:
88
+ logger.warning(
89
+ "setting auto_connect to `False` no longer has an effect and the setting will likely be removed in the future",
90
+ )
91
+ if value:
92
+ self._auto_connect_path.touch()
93
+ else:
94
+ self._auto_connect_path.unlink(missing_ok=True)
95
+
96
+ @property
97
+ def _branch_path(self) -> Path:
98
+ return (
99
+ settings_dir
100
+ / f"current-branch--{self.instance.owner}--{self.instance.name}.txt"
101
+ )
102
+
103
+ def _read_branch_idlike_name(self) -> tuple[int | str, str]:
104
+ idlike: str | int = 1
105
+ name: str = "main"
106
+ try:
107
+ branch_path = self._branch_path
108
+ except SystemExit: # in case no instance setup
109
+ return idlike, name
110
+ if branch_path.exists():
111
+ idlike, name = branch_path.read_text().split("\n")
112
+ return idlike, name
113
+
114
+ @property
115
+ # TODO: refactor so that it returns a BranchMock object
116
+ # and we never need a DB request
117
+ def branch(self) -> Branch:
118
+ """Default branch."""
119
+ if self._branch is None:
120
+ from lamindb import Branch
121
+
122
+ idlike, _ = self._read_branch_idlike_name()
123
+ self._branch = Branch.get(idlike)
124
+ return self._branch
125
+
126
+ @branch.setter
127
+ def branch(self, value: str | Branch) -> None:
128
+ from lamindb import Branch, Q
129
+ from lamindb.errors import DoesNotExist
130
+
131
+ if isinstance(value, Branch):
132
+ assert value._state.adding is False, "Branch must be saved"
133
+ branch_record = value
134
+ else:
135
+ branch_record = Branch.filter(Q(name=value) | Q(uid=value)).one_or_none()
136
+ if branch_record is None:
137
+ raise DoesNotExist(
138
+ f"Branch '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
139
+ )
140
+ # we are sure that the current instance is setup because
141
+ # it will error on lamindb import otherwise
142
+ self._branch_path.write_text(f"{branch_record.uid}\n{branch_record.name}")
143
+ self._branch = branch_record
144
+
145
+ @property
146
+ def _space_path(self) -> Path:
147
+ return (
148
+ settings_dir
149
+ / f"current-space--{self.instance.owner}--{self.instance.name}.txt"
150
+ )
151
+
152
+ def _read_space_idlike_name(self) -> tuple[int | str, str]:
153
+ idlike: str | int = 1
154
+ name: str = "all"
155
+ try:
156
+ space_path = self._space_path
157
+ except SystemExit: # in case no instance setup
158
+ return idlike, name
159
+ if space_path.exists():
160
+ idlike, name = space_path.read_text().split("\n")
161
+ return idlike, name
162
+
163
+ @property
164
+ # TODO: refactor so that it returns a BranchMock object
165
+ # and we never need a DB request
166
+ def space(self) -> Space:
167
+ """Default space."""
168
+ if self._space is None:
169
+ from lamindb import Space
170
+
171
+ idlike, _ = self._read_space_idlike_name()
172
+ self._space = Space.get(idlike)
173
+ return self._space
174
+
175
+ @space.setter
176
+ def space(self, value: str | Space) -> None:
177
+ from lamindb import Q, Space
178
+ from lamindb.errors import DoesNotExist
179
+
180
+ if isinstance(value, Space):
181
+ assert value._state.adding is False, "Space must be saved"
182
+ space_record = value
183
+ else:
184
+ space_record = Space.filter(Q(name=value) | Q(uid=value)).one_or_none()
185
+ if space_record is None:
186
+ raise DoesNotExist(
187
+ f"Space '{value}', please check on the hub UI whether you have the correct `uid` or `name`."
188
+ )
189
+ # we are sure that the current instance is setup because
190
+ # it will error on lamindb import otherwise
191
+ self._space_path.write_text(f"{space_record.uid}\n{space_record.name}")
192
+ self._space = space_record
193
+
194
+ @property
195
+ def is_connected(self) -> bool:
196
+ """Determine whether the current instance is fully connected and ready to use.
197
+
198
+ If `True`, the current instance is connected, meaning that the db and other settings
199
+ are properly configured for use.
200
+ """
201
+ if self._instance_exists:
202
+ return self.instance.slug != "none/none"
203
+ else:
204
+ return False
205
+
206
+ @property
207
+ def private_django_api(self) -> bool:
208
+ """Turn internal Django API private to clean up the API (default `False`).
209
+
210
+ This patches your local pip-installed django installation. You can undo
211
+ the patch by setting this back to `False`.
212
+ """
213
+ return self._private_django_api_path.exists()
214
+
215
+ @private_django_api.setter
216
+ def private_django_api(self, value: bool) -> None:
217
+ from ._private_django_api import private_django_api
218
+
219
+ # we don't want to call private_django_api() twice
220
+ if value and not self.private_django_api:
221
+ private_django_api()
222
+ self._private_django_api_path.touch()
223
+ elif not value and self.private_django_api:
224
+ private_django_api(reverse=True)
225
+ self._private_django_api_path.unlink(missing_ok=True)
226
+
227
+ @property
228
+ def user(self) -> UserSettings:
229
+ """Settings of current user."""
230
+ env_changed = (
231
+ self._user_settings_env is not None
232
+ and self._user_settings_env != get_env_name()
233
+ )
234
+ if self._user_settings is None or env_changed:
235
+ # only uses LAMIN_API_KEY if there is no current_user.env
236
+ self._user_settings = load_or_create_user_settings(
237
+ api_key=os.environ.get("LAMIN_API_KEY")
238
+ )
239
+ self._user_settings_env = get_env_name()
240
+ if self._user_settings and self._user_settings.uid is None:
241
+ raise RuntimeError("Need to login, first: lamin login")
242
+ return self._user_settings # type: ignore
243
+
244
+ @property
245
+ def instance(self) -> InstanceSettings:
246
+ """Settings of current LaminDB instance."""
247
+ env_changed = (
248
+ self._instance_settings_env is not None
249
+ and self._instance_settings_env != get_env_name()
250
+ )
251
+ if self._instance_settings is None or env_changed:
252
+ self._instance_settings = load_instance_settings()
253
+ self._instance_settings_env = get_env_name()
254
+ return self._instance_settings # type: ignore
255
+
256
+ @property
257
+ def storage(self) -> StorageSettings:
258
+ """Settings of default storage."""
259
+ return self.instance.storage
260
+
261
+ @property
262
+ def _instance_exists(self):
263
+ try:
264
+ self.instance # noqa
265
+ return True
266
+ # this is implicit logic that catches if no instance is loaded
267
+ except CurrentInstanceNotConfigured:
268
+ return False
269
+
270
+ @property
271
+ def cache_dir(self) -> UPath:
272
+ """Cache root, a local directory to cache cloud files."""
273
+ if "LAMIN_CACHE_DIR" in os.environ:
274
+ cache_dir = UPath(os.environ["LAMIN_CACHE_DIR"])
275
+ elif self._cache_dir is None:
276
+ cache_path = load_cache_path_from_settings()
277
+ cache_dir = _process_cache_path(cache_path)
278
+ if cache_dir is None:
279
+ cache_dir = DEFAULT_CACHE_DIR
280
+ self._cache_dir = cache_dir
281
+ else:
282
+ cache_dir = self._cache_dir
283
+ try:
284
+ cache_dir.mkdir(parents=True, exist_ok=True)
285
+ # we don not want this to error
286
+ # beause no actual writing happens on just getting the cache dir
287
+ # in cloud_to_local_no_update for example
288
+ # so it should not fail on read-only systems
289
+ except Exception as e:
290
+ logger.warning(
291
+ f"Failed to create lamin cache directory at {cache_dir}: {e}"
292
+ )
293
+ return cache_dir
294
+
295
+ @property
296
+ def paths(self) -> type[SetupPaths]:
297
+ """Convert cloud paths to lamindb local paths.
298
+
299
+ Use `settings.paths.cloud_to_local_no_update`
300
+ or `settings.paths.cloud_to_local`.
301
+ """
302
+ return SetupPaths
303
+
304
+ def __repr__(self) -> str:
305
+ """Rich string representation."""
306
+ # do not show current setting representation when building docs
307
+ if "sphinx" in sys.modules:
308
+ return object.__repr__(self)
309
+ repr = ""
310
+ if self._instance_exists:
311
+ repr += "Current branch & space:\n"
312
+ repr += f" - branch: {self._read_branch_idlike_name()[1]}\n"
313
+ repr += f" - space: {self._read_space_idlike_name()[1]}\n"
314
+ repr += self.instance.__repr__()
315
+ else:
316
+ repr += "Current instance: None"
317
+ repr += "\nConfig:\n"
318
+ repr += f" - private Django API: {self.private_django_api}\n"
319
+ repr += "Local directories:\n"
320
+ repr += f" - cache: {self.cache_dir.as_posix()}\n"
321
+ repr += f" - user settings: {settings_dir.as_posix()}\n"
322
+ repr += f" - system settings: {system_settings_dir.as_posix()}\n"
323
+ repr += self.user.__repr__()
324
+ return repr
325
+
326
+
327
+ class SetupPaths:
328
+ """A static class for conversion of cloud paths to lamindb local paths."""
329
+
330
+ @staticmethod
331
+ def cloud_to_local_no_update(
332
+ filepath: UPathStr, cache_key: str | None = None
333
+ ) -> UPath:
334
+ """Local (or local cache) filepath from filepath without synchronization."""
335
+ if not isinstance(filepath, UPath):
336
+ filepath = UPath(filepath)
337
+ # cache_key is ignored if filepath is a local path
338
+ if not isinstance(filepath, LocalPathClasses):
339
+ # settings is defined further in this file
340
+ if cache_key is None:
341
+ local_key = filepath.path # type: ignore
342
+ protocol = filepath.protocol # type: ignore
343
+ if protocol in {"http", "https"}:
344
+ local_key = local_key.removeprefix(protocol + "://")
345
+ else:
346
+ local_key = cache_key
347
+ local_filepath = settings.cache_dir / local_key
348
+ else:
349
+ local_filepath = filepath
350
+ return local_filepath
351
+
352
+ @staticmethod
353
+ def cloud_to_local(
354
+ filepath: UPathStr, cache_key: str | None = None, **kwargs
355
+ ) -> UPath:
356
+ """Local (or local cache) filepath from filepath."""
357
+ if not isinstance(filepath, UPath):
358
+ filepath = UPath(filepath)
359
+ # cache_key is ignored in cloud_to_local_no_update if filepath is local
360
+ local_filepath = SetupPaths.cloud_to_local_no_update(filepath, cache_key)
361
+ if not isinstance(filepath, LocalPathClasses):
362
+ local_filepath.parent.mkdir(parents=True, exist_ok=True)
363
+ filepath.synchronize_to(local_filepath, **kwargs) # type: ignore
364
+ return local_filepath
365
+
366
+
367
+ def get_env_name():
368
+ if "LAMIN_ENV" in os.environ:
369
+ return os.environ["LAMIN_ENV"]
370
+ else:
371
+ return "prod"
372
+
373
+
374
+ settings = SetupSettings()