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