lamindb_setup 0.70.0__py2.py3-none-any.whl → 0.71.1__py2.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 (45) hide show
  1. lamindb_setup/__init__.py +15 -15
  2. lamindb_setup/_cache.py +4 -1
  3. lamindb_setup/_check.py +3 -0
  4. lamindb_setup/_check_setup.py +13 -7
  5. lamindb_setup/_close.py +2 -0
  6. lamindb_setup/_connect_instance.py +47 -26
  7. lamindb_setup/_delete.py +72 -40
  8. lamindb_setup/_django.py +4 -1
  9. lamindb_setup/_exportdb.py +4 -2
  10. lamindb_setup/_importdb.py +5 -1
  11. lamindb_setup/_init_instance.py +61 -45
  12. lamindb_setup/_migrate.py +16 -13
  13. lamindb_setup/_register_instance.py +10 -3
  14. lamindb_setup/_schema.py +6 -3
  15. lamindb_setup/_set_managed_storage.py +37 -0
  16. lamindb_setup/_setup_user.py +7 -7
  17. lamindb_setup/_silence_loggers.py +4 -2
  18. lamindb_setup/core/__init__.py +4 -3
  19. lamindb_setup/core/_aws_storage.py +3 -0
  20. lamindb_setup/core/_deprecated.py +2 -7
  21. lamindb_setup/core/_docs.py +2 -0
  22. lamindb_setup/core/_hub_client.py +12 -10
  23. lamindb_setup/core/_hub_core.py +203 -88
  24. lamindb_setup/core/_hub_crud.py +21 -12
  25. lamindb_setup/core/_hub_utils.py +11 -8
  26. lamindb_setup/core/_settings.py +23 -26
  27. lamindb_setup/core/_settings_instance.py +149 -81
  28. lamindb_setup/core/_settings_load.py +13 -7
  29. lamindb_setup/core/_settings_save.py +13 -8
  30. lamindb_setup/core/_settings_storage.py +76 -42
  31. lamindb_setup/core/_settings_store.py +4 -2
  32. lamindb_setup/core/_settings_user.py +10 -6
  33. lamindb_setup/core/_setup_bionty_sources.py +9 -2
  34. lamindb_setup/core/cloud_sqlite_locker.py +13 -10
  35. lamindb_setup/core/django.py +3 -1
  36. lamindb_setup/core/exceptions.py +4 -2
  37. lamindb_setup/core/hashing.py +15 -5
  38. lamindb_setup/core/types.py +5 -2
  39. lamindb_setup/core/upath.py +191 -88
  40. {lamindb_setup-0.70.0.dist-info → lamindb_setup-0.71.1.dist-info}/METADATA +6 -4
  41. lamindb_setup-0.71.1.dist-info/RECORD +43 -0
  42. lamindb_setup/_add_remote_storage.py +0 -50
  43. lamindb_setup-0.70.0.dist-info/RECORD +0 -43
  44. {lamindb_setup-0.70.0.dist-info → lamindb_setup-0.71.1.dist-info}/LICENSE +0 -0
  45. {lamindb_setup-0.70.0.dist-info → lamindb_setup-0.71.1.dist-info}/WHEEL +0 -0
@@ -2,27 +2,25 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import shutil
5
- from .upath import LocalPathClasses, convert_pathlike
6
5
  from pathlib import Path
7
- from typing import Literal, Optional, Set, Tuple
8
- from uuid import UUID
6
+ from typing import TYPE_CHECKING, Literal
7
+
8
+ from lamin_utils import logger
9
+
9
10
  from ._hub_client import call_with_fallback
10
11
  from ._hub_crud import select_account_handle_name_by_lnid
11
- from lamin_utils import logger
12
+ from ._hub_utils import LaminDsn, LaminDsnModel
13
+ from ._settings_save import save_instance_settings
14
+ from ._settings_storage import StorageSettings, init_storage, mark_storage_root
15
+ from ._settings_store import current_instance_settings_file, instance_settings_file
12
16
  from .cloud_sqlite_locker import (
13
- InstanceLockedException,
14
17
  EXPIRATION_TIME,
18
+ InstanceLockedException,
15
19
  )
16
- from ._hub_utils import LaminDsnModel, LaminDsn
17
- from ._settings_save import save_instance_settings
18
- from ._settings_storage import StorageSettings
19
- from ._settings_store import current_instance_settings_file, instance_settings_file
20
- from .upath import UPath
20
+ from .upath import LocalPathClasses, UPath, convert_pathlike
21
21
 
22
- LOCAL_STORAGE_ROOT_WARNING = (
23
- "No local storage root found, set via `ln.setup.settings.instance.local_storage ="
24
- " local_root`"
25
- )
22
+ if TYPE_CHECKING:
23
+ from uuid import UUID
26
24
 
27
25
 
28
26
  def sanitize_git_repo_url(repo_url: str) -> str:
@@ -39,26 +37,28 @@ class InstanceSettings:
39
37
  owner: str, # owner handle
40
38
  name: str, # instance name
41
39
  storage: StorageSettings, # storage location
42
- local_storage: bool = False, # default to local storage
43
- uid: Optional[str] = None, # instance uid/lnid
44
- db: Optional[str] = None, # DB URI
45
- schema: Optional[str] = None, # comma-separated string of schema names
46
- git_repo: Optional[str] = None, # a git repo URL
40
+ keep_artifacts_local: bool = False, # default to local storage
41
+ uid: str | None = None, # instance uid/lnid
42
+ db: str | None = None, # DB URI
43
+ schema: str | None = None, # comma-separated string of schema names
44
+ git_repo: str | None = None, # a git repo URL
45
+ is_on_hub: bool = False, # initialized from hub
47
46
  ):
48
47
  from ._hub_utils import validate_db_arg
49
48
 
50
- self._id: UUID = id
49
+ self._id_: UUID = id
51
50
  self._owner: str = owner
52
51
  self._name: str = name
53
- self._uid: Optional[str] = uid
52
+ self._uid: str | None = uid
54
53
  self._storage: StorageSettings = storage
55
54
  validate_db_arg(db)
56
- self._db: Optional[str] = db
57
- self._schema_str: Optional[str] = schema
55
+ self._db: str | None = db
56
+ self._schema_str: str | None = schema
58
57
  self._git_repo = None if git_repo is None else sanitize_git_repo_url(git_repo)
59
58
  # local storage
60
- self._local_storage_on = local_storage
61
- self._local_storage = None
59
+ self._keep_artifacts_local = keep_artifacts_local
60
+ self._storage_local: StorageSettings | None = None
61
+ self._is_on_hub = is_on_hub
62
62
 
63
63
  def __repr__(self):
64
64
  """Rich string representation."""
@@ -97,60 +97,111 @@ class InstanceSettings:
97
97
  """Instance name."""
98
98
  return self._name
99
99
 
100
- def _search_local_root(self):
100
+ def _search_local_root(
101
+ self, local_root: str | None = None, mute_warning: bool = False
102
+ ) -> StorageSettings | None:
101
103
  from lnschema_core.models import Storage
102
104
 
103
- records = Storage.objects.filter(type="local").all()
104
- for record in records:
105
- if Path(record.root).exists():
106
- self._local_storage = StorageSettings(record.root)
107
- logger.important(f"defaulting to local storage: {record}")
108
- break
109
- if self._local_storage is None:
110
- logger.warning(LOCAL_STORAGE_ROOT_WARNING)
105
+ if local_root is not None:
106
+ local_records = Storage.objects.filter(root=local_root)
107
+ else:
108
+ local_records = Storage.objects.filter(type="local")
109
+ found = False
110
+ for record in local_records.all():
111
+ root_path = Path(record.root)
112
+ if root_path.exists():
113
+ marker_path = root_path / ".lamindb/_is_initialized"
114
+ if marker_path.exists():
115
+ uid = marker_path.read_text()
116
+ if uid == record.uid:
117
+ found = True
118
+ break
119
+ elif uid == "":
120
+ # legacy instance that was not yet marked properly
121
+ mark_storage_root(record.root, record.uid)
122
+ else:
123
+ continue
124
+ else:
125
+ # legacy instance that was not yet marked at all
126
+ mark_storage_root(record.root, record.uid)
127
+ break
128
+ if found:
129
+ return StorageSettings(record.root)
130
+ elif not mute_warning:
131
+ logger.warning(
132
+ f"none of the registered local storage locations were found in your environment: {local_records}"
133
+ "\n❗ please register a new local storage location via `ln.settings.storage_local = local_root_path` "
134
+ "and re-load/connect the instance"
135
+ )
136
+ return None
111
137
 
112
138
  @property
113
- def local_storage(self) -> StorageSettings:
114
- """Default local storage.
139
+ def keep_artifacts_local(self) -> bool:
140
+ """Default to keeping artifacts local.
115
141
 
116
- Warning: Only enable if you're sure you want to use the more complicated
117
- storage mode across local & cloud locations.
142
+ Enable this optional setting for cloud instances on lamin.ai.
118
143
 
119
- As an admin, enable via: `ln.setup.settings.instance.local_storage =
120
- local_root`.
144
+ Guide: :doc:`faq/keep-artifacts-local`
145
+ """
146
+ return self._keep_artifacts_local
121
147
 
122
- If enabled, you'll save artifacts to a default local storage
123
- location at :attr:`~lamindb.setup.settings.InstanceSettings.local_storage`.
148
+ @property
149
+ def storage(self) -> StorageSettings:
150
+ """Default storage.
124
151
 
125
- Upon passing `upload=True` in `artifact.save(upload=True)`, you upload the
126
- artifact to the default cloud storage location:
127
- :attr:`~lamindb.setup.core.InstanceSettings.storage`.
152
+ For a cloud instance, this is cloud storage. For a local instance, this
153
+ is a local directory.
128
154
  """
129
- if not self._local_storage_on:
130
- raise ValueError("Local storage is not enabled for this instance.")
131
- if self._local_storage is None:
132
- self._search_local_root()
133
- if self._local_storage is None:
134
- raise ValueError(LOCAL_STORAGE_ROOT_WARNING)
135
- return self._local_storage
136
-
137
- @local_storage.setter
138
- def local_storage(self, local_root: Path):
139
- from ._hub_core import update_instance_record
140
- from .._init_instance import register_storage
141
-
142
- self._search_local_root()
143
- if self._local_storage is not None:
144
- raise ValueError(
145
- "You already configured a local storage root for this instance in this"
146
- f" environment: {self.local_storage.root}"
147
- )
155
+ return self._storage
156
+
157
+ @property
158
+ def storage_local(self) -> StorageSettings:
159
+ """An additional local default storage.
160
+
161
+ Is only available if :attr:`keep_artifacts_local` is enabled.
162
+
163
+ Guide: :doc:`faq/keep-artifacts-local`
164
+ """
165
+ if not self._keep_artifacts_local:
166
+ raise ValueError("`keep_artifacts_local` is not enabled for this instance.")
167
+ if self._storage_local is None:
168
+ self._storage_local = self._search_local_root()
169
+ if self._storage_local is None:
170
+ # raise an error, there was a warning just before in search_local_root
171
+ raise ValueError()
172
+ return self._storage_local
173
+
174
+ @storage_local.setter
175
+ def storage_local(self, local_root: Path | str):
176
+ from lamindb_setup._init_instance import register_storage_in_instance
177
+
178
+ local_root = Path(local_root)
179
+ if not self._keep_artifacts_local:
180
+ raise ValueError("`keep_artifacts_local` is not enabled for this instance.")
181
+ storage_local = self._search_local_root(
182
+ local_root=StorageSettings(local_root).root_as_str, mute_warning=True
183
+ )
184
+ if storage_local is not None:
185
+ # great, we're merely switching storage location
186
+ self._storage_local = storage_local
187
+ logger.important(f"defaulting to local storage: {storage_local.root}")
188
+ return None
189
+ storage_local = self._search_local_root(mute_warning=True)
190
+ if storage_local is not None:
191
+ if os.getenv("LAMIN_TESTING") == "true":
192
+ response = "y"
193
+ else:
194
+ response = input(
195
+ "You already configured a local storage root for this instance in this"
196
+ f" environment: {self.storage_local.root}\nDo you want to register another one? (y/n)"
197
+ )
198
+ if response != "y":
199
+ return None
148
200
  local_root = convert_pathlike(local_root)
149
201
  assert isinstance(local_root, LocalPathClasses)
150
- self._local_storage = StorageSettings(local_root) # type: ignore
151
- register_storage(self._local_storage) # type: ignore
152
- self._local_storage_on = True
153
- update_instance_record(self.id, {"storage_mode": "hybrid"})
202
+ self._storage_local = init_storage(local_root, self._id, register_hub=True) # type: ignore
203
+ register_storage_in_instance(self._storage_local) # type: ignore
204
+ logger.important(f"defaulting to local storage: {self._storage_local.root}")
154
205
 
155
206
  @property
156
207
  def slug(self) -> str:
@@ -158,7 +209,7 @@ class InstanceSettings:
158
209
  return f"{self.owner}/{self.name}"
159
210
 
160
211
  @property
161
- def git_repo(self) -> Optional[str]:
212
+ def git_repo(self) -> str | None:
162
213
  """Sync transforms with scripts in git repository.
163
214
 
164
215
  Provide the full git repo URL.
@@ -166,17 +217,19 @@ class InstanceSettings:
166
217
  return self._git_repo
167
218
 
168
219
  @property
169
- def id(self) -> UUID:
220
+ def _id(self) -> UUID:
170
221
  """The internal instance id."""
171
- return self._id
222
+ return self._id_
172
223
 
173
224
  @property
174
- def uid(self) -> Optional[str]:
225
+ def uid(self) -> str:
175
226
  """The user-facing instance id."""
176
- return self._uid
227
+ from .hashing import hash_and_encode_as_b62
228
+
229
+ return hash_and_encode_as_b62(self._id.hex)[:12]
177
230
 
178
231
  @property
179
- def schema(self) -> Set[str]:
232
+ def schema(self) -> set[str]:
180
233
  """Schema modules in addition to core schema."""
181
234
  if self._schema_str is None:
182
235
  return {} # type: ignore
@@ -186,7 +239,7 @@ class InstanceSettings:
186
239
  @property
187
240
  def _sqlite_file(self) -> UPath:
188
241
  """SQLite file."""
189
- return self.storage.key_to_filepath(f"{self.id.hex}.lndb")
242
+ return self.storage.key_to_filepath(f"{self._id.hex}.lndb")
190
243
 
191
244
  @property
192
245
  def _sqlite_file_local(self) -> Path:
@@ -290,11 +343,6 @@ class InstanceSettings:
290
343
  else:
291
344
  return empty_locker
292
345
 
293
- @property
294
- def storage(self) -> StorageSettings:
295
- """Low-level access to storage location."""
296
- return self._storage
297
-
298
346
  @property
299
347
  def is_remote(self) -> bool:
300
348
  """Boolean indicating if an instance has no local component."""
@@ -317,6 +365,25 @@ class InstanceSettings:
317
365
  # and remote postgres
318
366
  return True
319
367
 
368
+ @property
369
+ def is_on_hub(self) -> bool:
370
+ """Is this instance on the hub.
371
+
372
+ Only works if user has access to the instance.
373
+ """
374
+ if self._is_on_hub is None:
375
+ from ._hub_client import call_with_fallback_auth
376
+ from ._hub_crud import select_instance_by_id
377
+
378
+ response = call_with_fallback_auth(
379
+ select_instance_by_id, instance_id=self._id.hex
380
+ )
381
+ if response is None:
382
+ self._is_on_hub = False
383
+ else:
384
+ self._is_on_hub = True
385
+ return self._is_on_hub
386
+
320
387
  def _get_settings_file(self) -> Path:
321
388
  return instance_settings_file(self.name, self.owner)
322
389
 
@@ -341,7 +408,7 @@ class InstanceSettings:
341
408
 
342
409
  def _load_db(
343
410
  self, do_not_lock_for_laminapp_admin: bool = False
344
- ) -> Tuple[bool, str]:
411
+ ) -> tuple[bool, str]:
345
412
  # Is the database available and initialized as LaminDB?
346
413
  # returns a tuple of status code and message
347
414
  if self.dialect == "sqlite" and not self._sqlite_file.exists():
@@ -353,6 +420,7 @@ class InstanceSettings:
353
420
  )
354
421
  return False, f"SQLite file {self._sqlite_file} does not exist"
355
422
  from lamindb_setup import settings # to check user
423
+
356
424
  from .django import setup_django
357
425
 
358
426
  # lock in all cases except if do_not_lock_for_laminapp_admin is True and
@@ -1,6 +1,8 @@
1
- from pathlib import Path
2
- from typing import Optional
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Optional
3
4
  from uuid import UUID, uuid4
5
+
4
6
  from lamin_utils import logger
5
7
  from pydantic.error_wrappers import ValidationError
6
8
 
@@ -14,12 +16,15 @@ from ._settings_store import (
14
16
  )
15
17
  from ._settings_user import UserSettings
16
18
 
19
+ if TYPE_CHECKING:
20
+ from pathlib import Path
21
+
17
22
 
18
23
  class SettingsEnvFileOutdated(Exception):
19
24
  pass
20
25
 
21
26
 
22
- def load_instance_settings(instance_settings_file: Optional[Path] = None):
27
+ def load_instance_settings(instance_settings_file: Path | None = None):
23
28
  if instance_settings_file is None:
24
29
  instance_settings_file = current_instance_settings_file()
25
30
  if not instance_settings_file.exists():
@@ -33,10 +38,10 @@ def load_instance_settings(instance_settings_file: Optional[Path] = None):
33
38
  f"\n\n{error}\n\nYour instance settings file with\n\n{content}\nis invalid"
34
39
  f" (likely outdated), please delete {instance_settings_file} &"
35
40
  " re-initialize (local) or re-connect to the instance (remote)"
36
- )
41
+ ) from error
37
42
  if settings_store.id == "null":
38
43
  raise ValueError(
39
- "Your instance.id is undefined, please either load your instance from the"
44
+ "Your instance._id is undefined, please either load your instance from the"
40
45
  f" hub or update {instance_settings_file} with a new id: {uuid4().hex}"
41
46
  )
42
47
  isettings = setup_instance_from_store(settings_store)
@@ -60,13 +65,13 @@ def load_or_create_user_settings() -> UserSettings:
60
65
  def load_user_settings(user_settings_file: Path):
61
66
  try:
62
67
  settings_store = UserSettingsStore(_env_file=user_settings_file)
63
- except (ValidationError, TypeError):
68
+ except (ValidationError, TypeError) as error:
64
69
  msg = (
65
70
  "Your user settings file is invalid, please delete"
66
71
  f" {user_settings_file} and log in again."
67
72
  )
68
73
  print(msg)
69
- raise SettingsEnvFileOutdated(msg)
74
+ raise SettingsEnvFileOutdated(msg) from error
70
75
  settings = setup_user_from_store(settings_store)
71
76
  return settings
72
77
 
@@ -84,6 +89,7 @@ def setup_instance_from_store(store: InstanceSettingsStore) -> InstanceSettings:
84
89
  db=store.db if store.db != "null" else None, # type: ignore
85
90
  schema=store.schema_str if store.schema_str != "null" else None,
86
91
  git_repo=store.git_repo if store.git_repo != "null" else None,
92
+ keep_artifacts_local=store.keep_artifacts_local, # type: ignore
87
93
  )
88
94
 
89
95
 
@@ -1,17 +1,20 @@
1
- from pathlib import Path
1
+ from __future__ import annotations
2
2
 
3
- from typing import Any, Dict, Union, get_type_hints, Optional
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any, Optional, get_type_hints
4
5
  from uuid import UUID
5
6
 
6
7
  from ._settings_store import (
7
- UserSettingsStore,
8
8
  InstanceSettingsStore,
9
+ UserSettingsStore,
9
10
  current_user_settings_file,
10
11
  user_settings_file_email,
11
12
  user_settings_file_handle,
12
13
  )
13
- from ._settings_user import UserSettings
14
- from .upath import UPath
14
+
15
+ if TYPE_CHECKING:
16
+ from ._settings_user import UserSettings
17
+ from .upath import UPath
15
18
 
16
19
 
17
20
  def save_user_settings(settings: UserSettings):
@@ -31,20 +34,22 @@ def save_user_settings(settings: UserSettings):
31
34
  def save_settings(
32
35
  settings: Any,
33
36
  settings_file: Path,
34
- type_hints: Dict[str, Any],
37
+ type_hints: dict[str, Any],
35
38
  prefix: str,
36
39
  ):
37
40
  with open(settings_file, "w") as f:
38
41
  for store_key, type in type_hints.items():
39
42
  if type == Optional[str]:
40
43
  type = str
44
+ if type == Optional[bool]:
45
+ type = bool
41
46
  if "__" not in store_key:
42
47
  if store_key == "storage_root":
43
48
  value = settings.storage.root_as_str
44
49
  elif store_key == "storage_region":
45
50
  value = settings.storage.region
46
51
  else:
47
- if store_key in {"db", "schema_str", "name_", "uuid"}:
52
+ if store_key in {"db", "schema_str", "name_", "uuid", "id"}:
48
53
  settings_key = f"_{store_key.rstrip('_')}"
49
54
  else:
50
55
  settings_key = store_key
@@ -65,7 +70,7 @@ def save_instance_settings(settings: Any, settings_file: Path):
65
70
 
66
71
 
67
72
  def save_system_storage_settings(
68
- cache_path: Union[str, Path, UPath, None], settings_file: Path
73
+ cache_path: str | Path | UPath | None, settings_file: Path
69
74
  ):
70
75
  cache_path = "null" if cache_path is None else cache_path
71
76
  if isinstance(cache_path, Path): # also True for UPath