lamindb_setup 1.18.1__py3-none-any.whl → 1.19.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.
lamindb_setup/__init__.py CHANGED
@@ -35,7 +35,7 @@ Migration management
35
35
 
36
36
  """
37
37
 
38
- __version__ = "1.18.1" # denote a release candidate for 0.1.0 with 0.1rc1
38
+ __version__ = "1.19.0" # denote a release candidate for 0.1.0 with 0.1rc1
39
39
 
40
40
  import os
41
41
  import warnings
@@ -48,9 +48,9 @@ warnings.filterwarnings(
48
48
  )
49
49
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="postgrest")
50
50
 
51
- from packaging import version as packaging_version
52
-
53
- from . import core, errors, io, types
51
+ # do not import io by default to reduce import time
52
+ # it's not immediately needed in the default user workflows
53
+ from . import core, errors, types
54
54
  from ._check_setup import _check_instance_setup
55
55
  from ._connect_instance import connect
56
56
  from ._delete import delete
@@ -63,20 +63,6 @@ from ._register_instance import register
63
63
  from ._setup_user import login, logout
64
64
  from .core._settings import settings
65
65
 
66
- # check that the version of s3fs is higher than the lower bound
67
- # needed because spatialdata installs old versions of s3fs
68
- try:
69
- from s3fs import __version__ as s3fs_version
70
-
71
- if packaging_version.parse(s3fs_version) < packaging_version.parse("2023.12.2"):
72
- raise RuntimeError(
73
- f"The version of s3fs you have ({s3fs_version}) is impompatible "
74
- "with lamindb, please upgrade it: pip install s3fs>=2023.12.2"
75
- )
76
- except ImportError:
77
- # might be not installed
78
- pass
79
-
80
66
 
81
67
  def _is_CI_environment() -> bool:
82
68
  ci_env_vars = [
@@ -112,5 +98,4 @@ _call_registered_entry_points("lamindb_setup.on_import")
112
98
 
113
99
  settings.__doc__ = """Global :class:`~lamindb.setup.core.SetupSettings`."""
114
100
 
115
-
116
101
  close = disconnect # backward compatibility
@@ -13,8 +13,6 @@ from ._check_setup import _check_instance_setup
13
13
  from ._disconnect import disconnect
14
14
  from ._init_instance import load_from_isettings
15
15
  from ._silence_loggers import silence_loggers
16
- from .core._hub_core import connect_instance_hub
17
- from .core._hub_utils import LaminDsnModel
18
16
  from .core._settings import settings
19
17
  from .core._settings_instance import InstanceSettings
20
18
  from .core._settings_load import load_instance_settings
@@ -66,6 +64,9 @@ def update_db_using_local(
66
64
  # read directly from the environment
67
65
  db_updated = db_env
68
66
  else:
67
+ # dynamic import to avoid importing the heavy LaminDsnModel at root
68
+ from .core._hub_utils import LaminDsnModel
69
+
69
70
  db_hub = hub_instance_result["db"]
70
71
  db_dsn_hub = LaminDsnModel(db=db_hub)
71
72
  # read from a cached settings file in case the hub result is inexistent
@@ -113,6 +114,8 @@ def _connect_instance(
113
114
  # the following will return a string if the instance does not exist on the hub
114
115
  # do not call hub if the user is anonymous
115
116
  if owner != "anonymous":
117
+ from .core._hub_core import connect_instance_hub
118
+
116
119
  hub_result = connect_instance_hub(
117
120
  owner=owner,
118
121
  name=name,
@@ -233,7 +236,8 @@ def reset_django_module_variables():
233
236
 
234
237
 
235
238
  def _connect_cli(
236
- instance: str, use_root_db_user: bool = False, use_proxy_db: bool = False
239
+ instance: str,
240
+ use_root_db_user: bool = False,
237
241
  ) -> None:
238
242
  from lamindb_setup import settings as settings_
239
243
 
@@ -242,7 +246,6 @@ def _connect_cli(
242
246
  owner,
243
247
  name,
244
248
  use_root_db_user=use_root_db_user,
245
- use_proxy_db=use_proxy_db,
246
249
  raise_systemexit=True,
247
250
  )
248
251
  isettings._persist(write_to_disk=True)
lamindb_setup/_delete.py CHANGED
@@ -8,8 +8,6 @@ from lamin_utils import logger
8
8
 
9
9
  from ._connect_instance import _connect_instance, get_owner_name_from_identifier
10
10
  from .core._aws_options import HOSTED_BUCKETS
11
- from .core._hub_core import delete_instance as delete_instance_on_hub
12
- from .core._hub_core import get_storage_records_for_instance
13
11
  from .core._settings import settings
14
12
  from .core._settings_load import load_instance_settings
15
13
  from .core._settings_storage import StorageSettings
@@ -120,6 +118,9 @@ def delete(slug: str, force: bool = False, require_empty: bool = True) -> int |
120
118
  )
121
119
  # now everything that's on the hub
122
120
  if settings.user.handle != "anonymous":
121
+ # dynamic import to avoid importing hub logic at root
122
+ from .core._hub_core import get_storage_records_for_instance
123
+
123
124
  storage_records = get_storage_records_for_instance(isettings._id)
124
125
  for storage_record in storage_records:
125
126
  if storage_record["root"] == isettings.storage.root_as_str:
@@ -139,7 +140,10 @@ def delete(slug: str, force: bool = False, require_empty: bool = True) -> int |
139
140
  if settings.user.handle != "anonymous" and isettings.is_on_hub:
140
141
  # start with deleting things on the hub
141
142
  # this will error if the user doesn't have permission
142
- delete_instance_on_hub(isettings._id, require_empty=False)
143
+ # dynamic import to avoid importing hub logic at root
144
+ from .core._hub_core import delete_instance
145
+
146
+ delete_instance(isettings._id, require_empty=False)
143
147
  delete_by_isettings(isettings)
144
148
  # if lamin.db file was delete, then we might count -1
145
149
  if n_files <= 0 and isettings.storage.type == "local":
@@ -6,7 +6,6 @@ import uuid
6
6
  from typing import TYPE_CHECKING, Literal
7
7
  from uuid import UUID
8
8
 
9
- import click
10
9
  from lamin_utils import logger
11
10
 
12
11
  from ._disconnect import disconnect
@@ -17,7 +16,7 @@ from .core._settings import settings
17
16
  from .core._settings_instance import check_is_instance_remote
18
17
  from .core._settings_storage import StorageSettings, init_storage
19
18
  from .core.upath import UPath
20
- from .errors import CannotSwitchDefaultInstance
19
+ from .errors import CannotSwitchDefaultInstance, InstanceNotCreated
21
20
 
22
21
  if TYPE_CHECKING:
23
22
  from lamindb.models import Storage
@@ -27,11 +26,6 @@ if TYPE_CHECKING:
27
26
  from .types import UPathStr
28
27
 
29
28
 
30
- class InstanceNotCreated(click.ClickException):
31
- def show(self, file=None):
32
- pass
33
-
34
-
35
29
  def get_schema_module_name(module_name, raise_import_error: bool = True) -> str | None:
36
30
  import importlib.util
37
31
 
lamindb_setup/_migrate.py CHANGED
@@ -2,9 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
 
5
- import httpx
6
5
  from django.db import connection
7
- from django.db.migrations.loader import MigrationLoader
8
6
  from lamin_utils import logger
9
7
  from packaging import version
10
8
 
@@ -111,6 +109,9 @@ class migrate:
111
109
  isettings = settings.instance
112
110
 
113
111
  if isettings.is_on_hub and LAMIN_MIGRATE_ON_LAMBDA:
112
+ # dynamic import to avoid importing the heavy httpx at root
113
+ import httpx
114
+
114
115
  response = httpx.post(
115
116
  f"{isettings.api_url}/instances/{isettings._id}/migrate",
116
117
  headers={"Authorization": f"Bearer {settings.user.access_token}"},
@@ -300,6 +301,9 @@ class migrate:
300
301
 
301
302
  return latest_migrations
302
303
  else:
304
+ # import dynamically to avoid importing the heavy django.db.migrations at the root
305
+ from django.db.migrations.loader import MigrationLoader
306
+
303
307
  # Load all migrations using Django's migration loader
304
308
  loader = MigrationLoader(connection)
305
309
  squashed_replacements = set()
@@ -5,7 +5,6 @@ from typing import TYPE_CHECKING
5
5
  from lamin_utils import logger
6
6
 
7
7
  from ._init_instance import register_storage_in_instance
8
- from .core._hub_core import delete_storage_record
9
8
  from .core._settings import settings
10
9
  from .core._settings_storage import StorageSettings, init_storage
11
10
 
@@ -65,6 +64,8 @@ def set_managed_storage(root: UPathStr, host: str | None = None, **fs_kwargs):
65
64
  register_storage_in_instance(ssettings)
66
65
  except Exception as e:
67
66
  if hub_record_status == "hub-record-created" and ssettings._uuid is not None:
67
+ from .core._hub_core import delete_storage_record
68
+
68
69
  delete_storage_record(ssettings)
69
70
  raise e
70
71
 
@@ -58,7 +58,17 @@ class AWSOptionsManager:
58
58
  self._parameters_cache = {} # this is not refreshed
59
59
 
60
60
  from aiobotocore.session import AioSession
61
+ from packaging import version as packaging_version
62
+
63
+ # takes 100ms to import, so keep it here to avoid delaying the import of the main module
61
64
  from s3fs import S3FileSystem
65
+ from s3fs import __version__ as s3fs_version
66
+
67
+ if packaging_version.parse(s3fs_version) < packaging_version.parse("2023.12.2"):
68
+ raise RuntimeError(
69
+ f"The version of s3fs you have ({s3fs_version}) is impompatible "
70
+ "with lamindb, please upgrade it: pip install s3fs>=2023.12.2"
71
+ )
62
72
 
63
73
  anon_env = os.getenv("LAMIN_S3_ANON") == "true"
64
74
  # this is cached so will be resued with the connection initialized
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import httpx
4
3
  from lamin_utils import logger
5
4
 
6
5
 
7
6
  def get_location(ip="ipinfo.io"):
7
+ # dynamic import to avoid importing the heavy httpx at root
8
+ import httpx
9
+
8
10
  response = httpx.get(f"http://{ip}/json").json()
9
11
  loc = response["loc"].split(",")
10
12
  return {"latitude": float(loc[0]), "longitude": float(loc[1])}
@@ -10,26 +10,20 @@ from urllib.request import urlretrieve
10
10
  import httpx
11
11
  from httpx_retries import Retry, RetryTransport
12
12
  from lamin_utils import logger
13
- from pydantic_settings import BaseSettings
14
13
  from supabase import Client, ClientOptions, create_client
15
14
 
16
15
  from ._settings_save import save_user_settings
17
-
18
-
19
- class Connector(BaseSettings):
20
- url: str
21
- key: str
16
+ from ._settings_store import Connector
22
17
 
23
18
 
24
19
  def load_fallback_connector() -> Connector:
25
20
  url = "https://lamin-site-assets.s3.amazonaws.com/connector.env"
26
21
  connector_file, _ = urlretrieve(url)
27
- connector = Connector(_env_file=connector_file)
28
- return connector
22
+ return Connector.from_env_file(connector_file, "")
29
23
 
30
24
 
31
25
  PROD_URL = "https://hub.lamin.ai"
32
- PROD_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxhZXNhdW1tZHlkbGxwcGdmY2h1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTY4NDA1NTEsImV4cCI6MTk3MjQxNjU1MX0.WUeCRiun0ExUxKIv5-CtjF6878H8u26t0JmCWx3_2-c"
26
+ PROD_ANON_KEY = "sb_publishable_YVa4h8hQ-yBhXpfa2cP39w_PhoLW6Nu"
33
27
 
34
28
 
35
29
  class Environment:
@@ -48,13 +42,13 @@ class Environment:
48
42
  key = connector.key
49
43
  elif lamin_env == "staging":
50
44
  url = "https://amvrvdwndlqdzgedrqdv.supabase.co"
51
- key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFtdnJ2ZHduZGxxZHpnZWRycWR2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NzcxNTcxMzMsImV4cCI6MTk5MjczMzEzM30.Gelt3dQEi8tT4j-JA36RbaZuUvxRnczvRr3iyRtzjY0"
45
+ key = "sb_publishable_amVjtilv_Yj4VmGLmxtq6A_sYlLoQx5"
52
46
  elif lamin_env == "staging-test":
53
47
  url = "https://iugyyajllqftbpidapak.supabase.co"
54
- key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Iml1Z3l5YWpsbHFmdGJwaWRhcGFrIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTQyMjYyODMsImV4cCI6MjAwOTgwMjI4M30.s7B0gMogFhUatMSwlfuPJ95kWhdCZMn1ROhZ3t6Og90"
48
+ key = "sb_publishable_XmXroXqTLQw-eeT5kysCww_k8vJv-4L"
55
49
  elif lamin_env == "prod-test":
56
50
  url = "https://xtdacpwiqwpbxsatoyrv.supabase.co"
57
- key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0ZGFjcHdpcXdwYnhzYXRveXJ2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTQyMjYxNDIsImV4cCI6MjAwOTgwMjE0Mn0.Dbi27qujTt8Ei9gfp9KnEWTYptE5KUbZzEK6boL46k4"
51
+ key = "sb_publishable_G-pyO5aW6VFErTzJyVvM5w_NAv1_Mf7"
58
52
  else:
59
53
  url = os.environ["SUPABASE_API_URL"]
60
54
  key = os.environ["SUPABASE_ANON_KEY"]
@@ -627,8 +627,6 @@ def access_aws(storage_root: str, access_token: str | None = None) -> dict[str,
627
627
 
628
628
 
629
629
  def _access_aws(*, storage_root: str, client: Client) -> dict[str, dict]:
630
- import lamindb_setup
631
-
632
630
  storage_root_info: dict[str, dict] = {"credentials": {}, "accessibility": {}}
633
631
  response = client.functions.invoke(
634
632
  "get-cloud-access-v1",
@@ -5,14 +5,9 @@ import shutil
5
5
  from pathlib import Path
6
6
  from typing import TYPE_CHECKING, Literal
7
7
 
8
- from django.db import connection
9
- from django.db.utils import ProgrammingError
10
8
  from lamin_utils import logger
11
9
 
12
10
  from ._deprecated import deprecated
13
- from ._hub_client import call_with_fallback
14
- from ._hub_crud import select_account_handle_name_by_lnid
15
- from ._hub_utils import LaminDsn, LaminDsnModel
16
11
  from ._settings_save import save_instance_settings
17
12
  from ._settings_storage import (
18
13
  LEGACY_STORAGE_UID_FILE_KEY,
@@ -85,6 +80,7 @@ class InstanceSettings:
85
80
  _locker_user: UserSettings | None = None, # user to lock for if cloud sqlite,
86
81
  _is_clone: bool = False,
87
82
  ):
83
+ # dynamic import to avoid importing pydantic at root
88
84
  from ._hub_utils import validate_db_arg
89
85
 
90
86
  self._id_: UUID = id
@@ -135,6 +131,9 @@ class InstanceSettings:
135
131
  )
136
132
  elif attr == "db":
137
133
  if self.dialect != "sqlite":
134
+ # dynamic import to avoid importing pydantic at root
135
+ from ._hub_utils import LaminDsn, LaminDsnModel
136
+
138
137
  model = LaminDsnModel(db=value)
139
138
  db_print = LaminDsn.build(
140
139
  scheme=model.db.scheme,
@@ -166,6 +165,7 @@ class InstanceSettings:
166
165
  def _search_local_root(
167
166
  self, local_root: str | None = None, mute_warning: bool = False
168
167
  ) -> StorageSettings | None:
168
+ from django.db.utils import ProgrammingError
169
169
  from lamindb.models import Storage
170
170
 
171
171
  if local_root is not None:
@@ -375,6 +375,7 @@ class InstanceSettings:
375
375
  if self._db_permissions != "jwt":
376
376
  return None
377
377
 
378
+ from django.db import connection
378
379
  from lamindb.models import Space
379
380
 
380
381
  spaces: dict = {"admin": [], "write": [], "read": []}
@@ -452,6 +453,9 @@ class InstanceSettings:
452
453
  sqlite_file.synchronize_to(cache_file, print_progress=True) # type: ignore
453
454
 
454
455
  def _check_sqlite_lock(self):
456
+ from ._hub_client import call_with_fallback
457
+ from ._hub_crud import select_account_handle_name_by_lnid
458
+
455
459
  if not self._cloud_sqlite_locker.has_lock:
456
460
  locked_by = self._cloud_sqlite_locker._locked_by
457
461
  lock_msg = "Cannot load the instance, it is locked by "
@@ -560,7 +564,7 @@ class InstanceSettings:
560
564
  Will return `False` in case the user token can't find the instance.
561
565
  """
562
566
  if self._is_on_hub is None:
563
- from ._hub_client import call_with_fallback_auth
567
+ from ._hub_client import call_with_fallback, call_with_fallback_auth
564
568
  from ._hub_crud import select_instance_by_id
565
569
  from ._settings import settings
566
570
 
@@ -8,7 +8,6 @@ from uuid import UUID, uuid4
8
8
 
9
9
  from dotenv import dotenv_values
10
10
  from lamin_utils import logger
11
- from pydantic import ValidationError
12
11
 
13
12
  from lamindb_setup.errors import CurrentInstanceNotConfigured, SettingsEnvFileOutdated
14
13
 
@@ -49,7 +48,7 @@ def load_cache_path_from_settings(storage_settings: Path | None = None) -> Path
49
48
 
50
49
  def find_module_candidates():
51
50
  """Find all local packages that depend on lamindb."""
52
- candidates = ["bionty", "wetlab"]
51
+ candidates = ["bionty", "pertdb"]
53
52
  return [c for c in candidates if find_spec(c) is not None]
54
53
 
55
54
 
@@ -72,8 +71,10 @@ def load_instance_settings(instance_settings_file: Path | None = None):
72
71
  # this errors only if the file was explicitly provided
73
72
  raise CurrentInstanceNotConfigured
74
73
  try:
75
- settings_store = InstanceSettingsStore(_env_file=isettings_file)
76
- except (ValidationError, TypeError) as error:
74
+ settings_store = InstanceSettingsStore.from_env_file(
75
+ isettings_file, "lamindb_instance_"
76
+ )
77
+ except (ValueError, KeyError, TypeError) as error:
77
78
  raise SettingsEnvFileOutdated(
78
79
  f"\n\n{error}\n\nYour instance settings file with\n\n{isettings_file.read_text()}\nis invalid"
79
80
  f" (likely outdated), see validation error. Please delete {isettings_file} &"
@@ -108,8 +109,10 @@ def load_or_create_user_settings(api_key: str | None = None) -> UserSettings:
108
109
 
109
110
  def load_user_settings(user_settings_file: Path):
110
111
  try:
111
- settings_store = UserSettingsStore(_env_file=user_settings_file)
112
- except (ValidationError, TypeError) as error:
112
+ settings_store = UserSettingsStore.from_env_file(
113
+ user_settings_file, "lamin_user_"
114
+ )
115
+ except (ValueError, KeyError, TypeError) as error:
113
116
  msg = (
114
117
  "Your user settings file is invalid, please delete"
115
118
  f" {user_settings_file} and log in again."
@@ -121,7 +124,7 @@ def load_user_settings(user_settings_file: Path):
121
124
 
122
125
 
123
126
  def _null_to_value(field, value=None):
124
- return field if field != "null" else value
127
+ return value if field in (None, "null") else field
125
128
 
126
129
 
127
130
  def setup_instance_from_store(store: InstanceSettingsStore) -> InstanceSettings:
@@ -155,5 +158,5 @@ def setup_user_from_store(store: UserSettingsStore) -> UserSettings:
155
158
  settings.uid = store.uid
156
159
  settings.handle = _null_to_value(store.handle, value="anonymous")
157
160
  settings.name = _null_to_value(store.name)
158
- settings._uuid = UUID(store.uuid) if store.uuid != "null" else None
161
+ settings._uuid = UUID(store.uuid) if store.uuid not in (None, "null") else None
159
162
  return settings
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
- from typing import TYPE_CHECKING, Any, Optional, get_type_hints
4
+ from typing import TYPE_CHECKING, Any, Optional, get_args, get_type_hints
5
5
  from uuid import UUID
6
6
 
7
7
  from ._settings_store import (
@@ -33,6 +33,21 @@ def save_user_settings(settings: UserSettings):
33
33
  )
34
34
 
35
35
 
36
+ def _coerce_type_for_write(type_: Any) -> Any:
37
+ """Resolve union types to the non-None part for coercion when value is not None."""
38
+ if type_ in (str, bool):
39
+ return type_
40
+ if type_ == Optional[str]: # noqa: UP045
41
+ return str
42
+ if type_ == Optional[bool]: # noqa: UP045
43
+ return bool
44
+ args = get_args(type_) or ()
45
+ if type(None) in args:
46
+ non_none = next((a for a in args if a is not type(None)), type_)
47
+ return non_none if non_none in (str, bool) else type_
48
+ return type_
49
+
50
+
36
51
  def save_settings(
37
52
  settings: Any,
38
53
  settings_file: Path,
@@ -41,10 +56,7 @@ def save_settings(
41
56
  ):
42
57
  with open(settings_file, "w") as f:
43
58
  for store_key, type_ in type_hints.items():
44
- if type_ == Optional[str]: # noqa: UP045
45
- type_ = str
46
- if type_ == Optional[bool]: # noqa: UP045
47
- type_ = bool
59
+ type_ = _coerce_type_for_write(type_)
48
60
  if "__" not in store_key:
49
61
  if store_key == "model_config":
50
62
  continue
@@ -1,26 +1,25 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
4
+ from dataclasses import MISSING, dataclass, fields
2
5
  from pathlib import Path
3
- from typing import Optional
6
+ from typing import Any, get_args, get_type_hints
4
7
 
8
+ from dotenv import dotenv_values
5
9
  from lamin_utils import logger
6
10
  from platformdirs import site_config_dir
7
- from pydantic_settings import BaseSettings, SettingsConfigDict
8
11
 
9
12
  if "LAMIN_SETTINGS_DIR" in os.environ:
10
- # Needed when running with AWS Lambda, as only tmp/ directory has a write access
13
+ # Needed for AWS Lambda, as only tmp/ has write access
11
14
  settings_dir = Path(f"{os.environ['LAMIN_SETTINGS_DIR']}/.lamin")
12
15
  else:
13
- # user_config_dir is weird on MacOS!
14
- # hence, let's take home/.lamin
15
16
  settings_dir = Path.home() / ".lamin"
16
17
 
17
-
18
18
  try:
19
19
  settings_dir.mkdir(parents=True, exist_ok=True)
20
20
  except Exception as e:
21
21
  logger.warning(f"Failed to create lamin settings directory at {settings_dir}: {e}")
22
22
 
23
-
24
23
  system_settings_dir = Path(site_config_dir(appname="lamindb", appauthor="laminlabs"))
25
24
 
26
25
 
@@ -53,7 +52,6 @@ def user_settings_file_handle(handle: str):
53
52
  return settings_dir / f"{get_settings_file_name_prefix()}user--{handle}.env"
54
53
 
55
54
 
56
- # here user means the user directory on os, not a lamindb user
57
55
  def platform_user_storage_settings_file():
58
56
  return settings_dir / "storage.env"
59
57
 
@@ -62,31 +60,103 @@ def system_settings_file():
62
60
  return system_settings_dir / "system.env"
63
61
 
64
62
 
65
- class InstanceSettingsStore(BaseSettings):
66
- api_url: str | None = None
63
+ def _load_env_to_kwargs(
64
+ cls: type,
65
+ path: Path | str,
66
+ prefix: str,
67
+ ) -> dict[str, Any]:
68
+ path = Path(path) if isinstance(path, str) else path
69
+ raw = dotenv_values(path)
70
+ type_hints = get_type_hints(cls)
71
+ flds = {f.name: f for f in fields(cls)}
72
+ optional = {
73
+ n
74
+ for n, f in flds.items()
75
+ if f.default is not MISSING or f.default_factory is not MISSING
76
+ }
77
+
78
+ kwargs: dict[str, Any] = {}
79
+ for store_key in type_hints:
80
+ if store_key.startswith("__"):
81
+ continue
82
+ env_key = f"{prefix}{store_key}"
83
+ has_key = env_key in raw
84
+ raw_val = raw.get(env_key) if has_key else None
85
+ is_opt = store_key in optional
86
+
87
+ if not has_key:
88
+ if not is_opt:
89
+ raise ValueError(f"Missing required key {env_key!r} in env file {path}")
90
+ kwargs[store_key] = None
91
+ continue
92
+ if raw_val is None or raw_val == "" or raw_val == "null":
93
+ kwargs[store_key] = None
94
+ continue
95
+ type_ = type_hints[store_key]
96
+ args = get_args(type_) or ()
97
+ if type_ is bool:
98
+ kwargs[store_key] = raw_val.lower() in ("true", "1", "yes")
99
+ elif type(None) in args:
100
+ non_none = next((a for a in args if a is not type(None)), type_)
101
+ if non_none is bool:
102
+ kwargs[store_key] = raw_val.lower() in ("true", "1", "yes")
103
+ else:
104
+ kwargs[store_key] = raw_val
105
+ else:
106
+ kwargs[store_key] = raw_val
107
+ return kwargs
108
+
109
+
110
+ @dataclass
111
+ class InstanceSettingsStore:
112
+ # Required (no default) — must come first
67
113
  owner: str
68
114
  name: str
69
115
  storage_root: str
70
116
  storage_region: str | None
71
117
  db: str | None
72
118
  schema_str: str | None
73
- schema_id: str | None = None
74
- fine_grained_access: bool = False
75
- db_permissions: str | None = None
76
119
  id: str
77
120
  git_repo: str | None
78
121
  keep_artifacts_local: bool | None
122
+ # Optional
123
+ api_url: str | None = None
124
+ schema_id: str | None = None
125
+ fine_grained_access: bool = False
126
+ db_permissions: str | None = None
79
127
  is_clone: bool = False
80
- model_config = SettingsConfigDict(env_prefix="lamindb_instance_", env_file=".env")
128
+
129
+ @classmethod
130
+ def from_env_file(cls, path: Path | str, prefix: str) -> InstanceSettingsStore:
131
+ kwargs = _load_env_to_kwargs(cls, path, prefix)
132
+ return cls(**kwargs)
81
133
 
82
134
 
83
- class UserSettingsStore(BaseSettings):
135
+ @dataclass
136
+ class UserSettingsStore:
137
+ # Required (no default)
84
138
  email: str
85
139
  password: str
86
140
  access_token: str
87
- api_key: str | None = None
88
141
  uid: str
89
142
  uuid: str
90
143
  handle: str
91
144
  name: str
92
- model_config = SettingsConfigDict(env_prefix="lamin_user_", env_file=".env")
145
+ # Optional
146
+ api_key: str | None = None
147
+
148
+ @classmethod
149
+ def from_env_file(cls, path: Path | str, prefix: str) -> UserSettingsStore:
150
+ kwargs = _load_env_to_kwargs(cls, path, prefix)
151
+ return cls(**kwargs)
152
+
153
+
154
+ @dataclass
155
+ class Connector:
156
+ url: str
157
+ key: str
158
+
159
+ @classmethod
160
+ def from_env_file(cls, path: Path | str, prefix: str) -> Connector:
161
+ kwargs = _load_env_to_kwargs(cls, path, prefix)
162
+ return cls(**kwargs)
@@ -276,6 +276,27 @@ def setup_django(
276
276
  ],
277
277
  STATIC_URL="static/",
278
278
  )
279
+ if logger._verbosity == 5: # debug-level verbosity
280
+ kwargs.update(
281
+ {
282
+ "DEBUG": True,
283
+ "LOGGING": {
284
+ "version": 1,
285
+ "handlers": {
286
+ "console": {
287
+ "level": "DEBUG",
288
+ "class": "logging.StreamHandler",
289
+ }
290
+ },
291
+ "loggers": {
292
+ "django.db.backends": {
293
+ "level": "DEBUG",
294
+ "handlers": ["console"],
295
+ }
296
+ },
297
+ },
298
+ }
299
+ )
279
300
  settings.configure(**kwargs)
280
301
  # this isn't needed the first time django.setup() is called, but for unknown reason it's needed the second time
281
302
  # the first time, it already defaults to true
lamindb_setup/errors.py CHANGED
@@ -8,13 +8,12 @@
8
8
  .. autoexception:: SettingsEnvFileOutdated
9
9
  .. autoexception:: CannotSwitchDefaultInstance
10
10
  .. autoexception:: InstanceNotFoundError
11
+ .. autoexception:: InstanceNotCreated
11
12
 
12
13
  """
13
14
 
14
15
  from __future__ import annotations
15
16
 
16
- import click
17
-
18
17
 
19
18
  class DefaultMessageException(Exception):
20
19
  default_message: str | None = None
@@ -47,15 +46,18 @@ class StorageAlreadyManaged(Exception):
47
46
  pass
48
47
 
49
48
 
50
- class StorageNotEmpty(click.ClickException):
51
- def show(self, file=None):
52
- pass
49
+ class StorageNotEmpty(Exception):
50
+ pass
53
51
 
54
52
 
55
53
  class InstanceNotFoundError(Exception):
56
54
  pass
57
55
 
58
56
 
57
+ class InstanceNotCreated(Exception):
58
+ pass
59
+
60
+
59
61
  # raise if a cloud SQLite instance is already locked
60
62
  # ignored by unlock_cloud_sqlite_upon_exception
61
63
  class InstanceLockedException(Exception):
lamindb_setup/io.py CHANGED
@@ -8,10 +8,7 @@ from importlib import import_module
8
8
  from pathlib import Path
9
9
  from typing import TYPE_CHECKING
10
10
 
11
- import numpy as np
12
- import pandas as pd
13
11
  from django.db import models, transaction
14
- from rich.progress import Progress
15
12
 
16
13
  if TYPE_CHECKING:
17
14
  from collections.abc import Iterable
@@ -59,6 +56,7 @@ def _export_full_table(
59
56
  Returns:
60
57
  String identifier for single-file exports, or list of (table_name, chunk_path) tuples for chunked exports that need merging.
61
58
  """
59
+ import pandas as pd
62
60
  from django.db import connection
63
61
 
64
62
  import lamindb_setup as ln_setup
@@ -156,12 +154,15 @@ def export_db(
156
154
  Ensure that you connect to postgres instances using `use_root_db_user=True`.
157
155
 
158
156
  Args:
159
- module_names: Module names to export (e.g., ["lamindb", "bionty", "wetlab"]).
157
+ module_names: Module names to export (e.g., ["lamindb", "bionty", "pertdb"]).
160
158
  Defaults to "lamindb" if not provided.
161
159
  output_dir: Directory path for exported parquet files.
162
160
  max_workers: Number of parallel processes.
163
161
  chunk_size: Number of rows per chunk for large tables.
164
162
  """
163
+ import pandas as pd
164
+ from rich.progress import Progress
165
+
165
166
  import lamindb_setup as ln_setup
166
167
 
167
168
  if output_dir is None:
@@ -212,6 +213,9 @@ def export_db(
212
213
 
213
214
  def _serialize_value(val):
214
215
  """Convert value to JSON string if it's a dict, list, or numpy array, otherwise return as-is."""
216
+ # keep dynamic import to minimize import time
217
+ import numpy as np
218
+
215
219
  if isinstance(val, (dict, list, np.ndarray)):
216
220
  return json.dumps(
217
221
  val, default=lambda o: o.tolist() if isinstance(o, np.ndarray) else None
@@ -232,6 +236,8 @@ def _import_registry(
232
236
  For SQLite, uses multi-row INSERTs with dynamic chunking to stay under the 999
233
237
  variable limit (2-5x faster than single-row INSERTs).
234
238
  """
239
+ import numpy as np
240
+ import pandas as pd
235
241
  from django.db import connection
236
242
 
237
243
  table_name = registry._meta.db_table
@@ -345,13 +351,14 @@ def import_db(
345
351
 
346
352
  Args:
347
353
  input_dir: Directory containing parquet files to import.
348
- module_names: Module names to import (e.g., ["lamindb", "bionty", "wetlab"]).
354
+ module_names: Module names to import (e.g., ["lamindb", "bionty", "pertdb"]).
349
355
  if_exists: How to behave if table exists: 'fail', 'replace', or 'append'.
350
356
  If set to 'replace', existing data is deleted and new data is imported. All PKs and indices are not guaranteed to be preserved which can lead to write errors.
351
357
  If set to 'append', new data is added to existing data without clearing the table. All PKs and indices are preserved allowing write operations but database size will greatly increase.
352
358
  If set to 'fail', raises an error if the table contains any data.
353
359
  """
354
360
  from django.db import connection
361
+ from rich.progress import Progress
355
362
 
356
363
  import lamindb_setup as ln_setup
357
364
 
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb_setup
3
- Version: 1.18.1
3
+ Version: 1.19.0
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: lamin_utils>=0.3.3
9
+ Requires-Dist: python-dotenv
9
10
  Requires-Dist: django>=5.2,<5.3
10
11
  Requires-Dist: dj_database_url>=1.3.0,<3.0.0
11
12
  Requires-Dist: django-pgtrigger
@@ -1,51 +1,51 @@
1
- lamindb_setup/__init__.py,sha256=qNJ5BlP4kXqvMH3dKRQkWpaxb0mhyK4vS2xSa2Y3XLo,3270
1
+ lamindb_setup/__init__.py,sha256=uog08tfBbwYslApetPEHt0FKE80mE6WEZNM-RU3ETec,2812
2
2
  lamindb_setup/_cache.py,sha256=pGvDNVHGx4HWr_6w5ajqEJOdysmaGc6F221qFnXkT-k,2747
3
3
  lamindb_setup/_check.py,sha256=28PcG8Kp6OpjSLSi1r2boL2Ryeh6xkaCL87HFbjs6GA,129
4
4
  lamindb_setup/_check_setup.py,sha256=zPQho12dctJYjnZxpumq2r7XRvXwrNyWi5-UMRPSeuE,4297
5
- lamindb_setup/_connect_instance.py,sha256=2sPW9KF7wJvGlnUdi_eshzahfT319RwE3mhkZs1H2Fs,17339
6
- lamindb_setup/_delete.py,sha256=ZMAik35NNcj4kDfOISDfzpicCi4j8gwmWAor0hEB-Jk,6123
5
+ lamindb_setup/_connect_instance.py,sha256=mUR8Ue95esJQl6inEvxgaS81R6-Q--arYuznEa9VD78,17387
6
+ lamindb_setup/_delete.py,sha256=sYB1cRDA4fIekwrB6HBi1LtB5g8m7vkrEtInbujjGYo,6232
7
7
  lamindb_setup/_disconnect.py,sha256=B0K0yTIIyhwc3MzDFL_-cKutm-_DHsiJSTrkp1MMyXA,1470
8
8
  lamindb_setup/_django.py,sha256=uIQflpkp8l3axyPaKURlk3kacgpElVP5KOKmFxYSMGk,1454
9
9
  lamindb_setup/_entry_points.py,sha256=sKwXPX9xjOotoAjvgkU5LBwjjHLWVkh0ZGdiSsrch9k,522
10
- lamindb_setup/_init_instance.py,sha256=EBdv0ga_nnqHU-m9fN7EkVg7YEuKOLxR8N87Ww2qEDg,14877
11
- lamindb_setup/_migrate.py,sha256=mqJxm1EnB_1wJOhw_3djESyoc53bXJOXgBPndSRd5OU,12077
10
+ lamindb_setup/_init_instance.py,sha256=3kRq5SJw7vvJi4DaCrLGm8fcGh7D3OJOoOcSkUk0Fbo,14790
11
+ lamindb_setup/_migrate.py,sha256=8_eS21sGIoJAdLD09LBCSn8kjhyymafkuDSjW67aBjE,12270
12
12
  lamindb_setup/_register_instance.py,sha256=RdUZxZWHLdbvdNZWpF8e0UWROb_T0cStWbzc5yUw34I,1047
13
13
  lamindb_setup/_schema.py,sha256=b3uzhhWpV5mQtDwhMINc2MabGCnGLESy51ito3yl6Wc,679
14
14
  lamindb_setup/_schema_metadata.py,sha256=Whs-e4ZMnA1niZ2l5Eu8il-33IxI4Hr5ylGEgPxx8wk,15628
15
- lamindb_setup/_set_managed_storage.py,sha256=xik6Amz8yS4R7ZIK8BVdNyvOo_ShBMjsDMAadfIwQZM,3274
15
+ lamindb_setup/_set_managed_storage.py,sha256=vqxQ_993Db1ZyJ5j9SGU99Wpj5KUAIBveUyci9Z4aRk,3287
16
16
  lamindb_setup/_setup_user.py,sha256=9CwKUru2jza1vXu4A5V-q-oXnbIXwR_YwjKbVnETK7Q,6931
17
17
  lamindb_setup/_silence_loggers.py,sha256=oQnvnvieqxARQQMwQkR82EJVk1TaM-Bo9o0UCOsCcXY,1697
18
- lamindb_setup/errors.py,sha256=3u77okYz2NHXbX-TZkhHf5mJAXaaIIPBFPD21iSyRPg,1620
19
- lamindb_setup/io.py,sha256=stTE6VHCIpLK1ueRuWEJGh35wo9Qd1NcNN1TnLEbK-U,17156
18
+ lamindb_setup/errors.py,sha256=SDAnXvXYCsns54zTrHPfT2sKkEc1am_rCapdliqZiOQ,1646
19
+ lamindb_setup/io.py,sha256=WHdsfdL9yShBPsSu-Fzp5s2bJUzE8clU5h8nAQh1K8w,17330
20
20
  lamindb_setup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  lamindb_setup/types.py,sha256=fuQxZJnrGYe7a_Ju9n1RqO-HhkOAr1l1xjpAg9dmBu8,605
22
22
  lamindb_setup/core/__init__.py,sha256=oSTXxNCx6yfoqaHYTZM9qHuZKdMUIUF2DdXw4gBMBU4,592
23
- lamindb_setup/core/_aws_options.py,sha256=fHc7ndBl7URMYeVliuw1GAoodOuxvYMadTna8m0PORo,10059
24
- lamindb_setup/core/_aws_storage.py,sha256=QEtV-riQrwfivcwqHnXBbkJ-9YyNEXL4fLoCmOHZ1BI,2003
23
+ lamindb_setup/core/_aws_options.py,sha256=rV8CRRlDLoBk8YHNx5d665TeSJ6eBtCVjJz9dV_1b_w,10566
24
+ lamindb_setup/core/_aws_storage.py,sha256=sz1esr5R_xvLd9_U9UMzJR7Pp4M75OHem4Kmg_2fljQ,2072
25
25
  lamindb_setup/core/_clone.py,sha256=IE7d9n4qc8dvUgYbFavIkdUZ_BhsEHfoQzmcPDFuF4A,1537
26
26
  lamindb_setup/core/_deprecated.py,sha256=M3vpM4fZPOncxY2qsXQAPeaEph28xWdv7tYaueaUyAA,2554
27
27
  lamindb_setup/core/_docs.py,sha256=3k-YY-oVaJd_9UIY-LfBg_u8raKOCNfkZQPA73KsUhs,276
28
- lamindb_setup/core/_hub_client.py,sha256=113SKT3Ki4KxyKqpYOwIB8mEG0ouy9oebLJoh_xGJZY,10426
29
- lamindb_setup/core/_hub_core.py,sha256=29iPGnmV1hzYIMrErgwJoHwTQ5RHNJ6-icDM81aC4ac,29255
28
+ lamindb_setup/core/_hub_client.py,sha256=mhCuIWQ8fK0BZrMUXoXftbJv9BbvB9Een17_wuvZfcg,9697
29
+ lamindb_setup/core/_hub_core.py,sha256=mu_DuPvqEGeOgnl7d2KZblDkNOf6FN3c62HOcjiLzlA,29229
30
30
  lamindb_setup/core/_hub_crud.py,sha256=j6516H82kLjFUNPqFGUINbDw9YbofMgjxadGzYb0OS4,6362
31
31
  lamindb_setup/core/_hub_utils.py,sha256=6dyDGyzYFgVfR_lE3VN3CP1jGp98gxPtr-T91PAP05U,2687
32
32
  lamindb_setup/core/_private_django_api.py,sha256=e-9WiGkI7g683nOdTcScS_fPG-_neNvrcWuAI9NEeQs,2529
33
33
  lamindb_setup/core/_settings.py,sha256=LTeh2jsM01GHW2ZTrcc6rXNz6qIUT0VPEzpE9I8tg3Q,15497
34
- lamindb_setup/core/_settings_instance.py,sha256=KoKwvSqOGEieGC-I5Dd0kIr_Q9OknRlno-nj3ZaX8R8,24903
35
- lamindb_setup/core/_settings_load.py,sha256=D6r0fiqJznHhTN_apGlrKfYzl0g21shIaV4z4zEr7Z8,5780
36
- lamindb_setup/core/_settings_save.py,sha256=96mWdYLyfvbnG_ok_vK4x7jm-rtqcWCD1OHEt2QSAms,3328
34
+ lamindb_setup/core/_settings_instance.py,sha256=PrjzAlbajCHPBR664379is4ci3Hq13mtTWwQgJoavdg,25111
35
+ lamindb_setup/core/_settings_load.py,sha256=W8zgrTZ1YWYn7AlRoy8jRWLAYa9hzS19QB4EKUyf-c0,5861
36
+ lamindb_setup/core/_settings_save.py,sha256=W1VvAWgeXNSf6p_fgCUU90JRAVbqDgL63A9yuZ45kII,3753
37
37
  lamindb_setup/core/_settings_storage.py,sha256=e1FQ2uh13Cuw9Z5k6d71C825f6ISUnc1LtkDUO0QUIA,15590
38
- lamindb_setup/core/_settings_store.py,sha256=auZssUBb6qE5oSqdGiHhqI2B46qSpegX89VwObPQksk,2601
38
+ lamindb_setup/core/_settings_store.py,sha256=kFz-bgDRlpxosaIre3YIHJmOYmFNo4a2q8egFUD5hm0,4596
39
39
  lamindb_setup/core/_settings_user.py,sha256=gFfyMf-738onbh1Mf4wsmLlenQJPtjQfpUgKnOlqc2o,1453
40
40
  lamindb_setup/core/_setup_bionty_sources.py,sha256=ox3X-SHiHa2lNPSWjwZhINypbLacX6kGwH6hVVrSFZc,1505
41
41
  lamindb_setup/core/cloud_sqlite_locker.py,sha256=H_CTUCjURFXwD1cCtV_Jn0_60iztZTkaesLLXIBgIxc,7204
42
- lamindb_setup/core/django.py,sha256=X_KDP6c9EgqG0JoHg-igv-rvpdZ9mF1LeeoTtZjVICc,13649
42
+ lamindb_setup/core/django.py,sha256=hy8SofeSKaOvsOiyrge1qgAp1fmcFiWQV8pIFhFIoeo,14420
43
43
  lamindb_setup/core/exceptions.py,sha256=qjMzqy_uzPA7mCOdnoWnS_fdA6OWbdZGftz-YYplrY0,84
44
44
  lamindb_setup/core/hashing.py,sha256=4EDiMkdn96ct3oVkB64_4ujj6_EY5Vt8WMOMT5qnewI,3695
45
45
  lamindb_setup/core/lamin.db.gz,sha256=sFh_Qgg4Krui0lW4g2CrGfKpL59-UjOMgAJ7n0xP4XE,78228
46
46
  lamindb_setup/core/types.py,sha256=T7NwspfRHgIIpYsXDcApks8jkOlGeGRW-YbVLB7jNIo,67
47
47
  lamindb_setup/core/upath.py,sha256=zlPZcDRHDYT7OcoOiX_w9koAzfBr9q8hB66OPwhTpBo,36504
48
- lamindb_setup-1.18.1.dist-info/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
49
- lamindb_setup-1.18.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
50
- lamindb_setup-1.18.1.dist-info/METADATA,sha256=vP_uPB1gKCchtRUEGIPU-9m-rBcwr3j82VvGw6kvNOA,1778
51
- lamindb_setup-1.18.1.dist-info/RECORD,,
48
+ lamindb_setup-1.19.0.dist-info/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
49
+ lamindb_setup-1.19.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
50
+ lamindb_setup-1.19.0.dist-info/METADATA,sha256=EXnq7Rdvz2NKSKdf5hbvANX9NZHxoZZfYwQIoetiv_w,1807
51
+ lamindb_setup-1.19.0.dist-info/RECORD,,