lamindb_setup 1.9.0__py3-none-any.whl → 1.9.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 (39) hide show
  1. lamindb_setup/__init__.py +107 -107
  2. lamindb_setup/_cache.py +87 -87
  3. lamindb_setup/_check_setup.py +166 -166
  4. lamindb_setup/_connect_instance.py +328 -342
  5. lamindb_setup/_delete.py +141 -141
  6. lamindb_setup/_disconnect.py +32 -32
  7. lamindb_setup/_init_instance.py +440 -440
  8. lamindb_setup/_migrate.py +266 -266
  9. lamindb_setup/_register_instance.py +35 -35
  10. lamindb_setup/_schema_metadata.py +441 -441
  11. lamindb_setup/_set_managed_storage.py +70 -70
  12. lamindb_setup/_setup_user.py +133 -133
  13. lamindb_setup/core/__init__.py +21 -21
  14. lamindb_setup/core/_aws_options.py +223 -223
  15. lamindb_setup/core/_hub_client.py +248 -248
  16. lamindb_setup/core/_hub_core.py +665 -665
  17. lamindb_setup/core/_hub_crud.py +227 -227
  18. lamindb_setup/core/_private_django_api.py +83 -83
  19. lamindb_setup/core/_settings.py +377 -377
  20. lamindb_setup/core/_settings_instance.py +569 -569
  21. lamindb_setup/core/_settings_load.py +141 -141
  22. lamindb_setup/core/_settings_save.py +95 -95
  23. lamindb_setup/core/_settings_storage.py +429 -429
  24. lamindb_setup/core/_settings_store.py +91 -91
  25. lamindb_setup/core/_settings_user.py +55 -55
  26. lamindb_setup/core/_setup_bionty_sources.py +44 -44
  27. lamindb_setup/core/cloud_sqlite_locker.py +240 -240
  28. lamindb_setup/core/django.py +305 -296
  29. lamindb_setup/core/exceptions.py +1 -1
  30. lamindb_setup/core/hashing.py +134 -134
  31. lamindb_setup/core/types.py +1 -1
  32. lamindb_setup/core/upath.py +1013 -1013
  33. lamindb_setup/errors.py +70 -70
  34. lamindb_setup/types.py +20 -20
  35. {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.9.1.dist-info}/METADATA +1 -1
  36. lamindb_setup-1.9.1.dist-info/RECORD +50 -0
  37. lamindb_setup-1.9.0.dist-info/RECORD +0 -50
  38. {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.9.1.dist-info}/LICENSE +0 -0
  39. {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.9.1.dist-info}/WHEEL +0 -0
@@ -1,377 +1,377 @@
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
+ 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()