lamindb_setup 1.9.0__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.
@@ -43,20 +43,51 @@ def load_user(email: str | None = None, handle: str | None = None) -> UserSettin
43
43
 
44
44
 
45
45
  def login(
46
- user: str | None = None, *, api_key: str | None = None, key: str | None = None
46
+ user: str | None = None, *, api_key: str | None = None, **kwargs
47
47
  ) -> UserSettings:
48
- """Log in user.
48
+ # note that the docstring needs to be synced with lamin login
49
+ """Log into LaminHub.
50
+
51
+ `login()` prompts for your API key unless you set it via the `LAMIN_API_KEY` environment variable or pass it as an argument.
52
+
53
+ You can create your API key in your account settings on LaminHub (top right corner).
49
54
 
50
55
  Args:
51
- user: handle or email
52
- api_key: API key
53
- key: legacy API key
56
+ user: User handle.
57
+ api_key: API key.
58
+
59
+ See Also:
60
+ Login via the CLI command `lamin login`, see `here <https://docs.lamin.ai/cli#login>`__.
61
+
62
+ Examples:
63
+
64
+ Logging in the first time::
65
+
66
+ import lamindb as ln
67
+
68
+ ln.setup.login() # prompts for API key
69
+
70
+ After authenticating multiple users, you can switch between user profiles::
71
+
72
+ ln.setup.login("myhandle") # pass your user handle
54
73
  """
55
- if user is None and api_key is None:
56
- if "LAMIN_API_KEY" in os.environ:
57
- api_key = os.environ["LAMIN_API_KEY"]
58
- else:
59
- raise ValueError("Both `user` and `api_key` should not be `None`.")
74
+ if user is None:
75
+ if api_key is None:
76
+ if "LAMIN_API_KEY" in os.environ:
77
+ api_key = os.environ["LAMIN_API_KEY"]
78
+ else:
79
+ api_key = input("Your API key: ")
80
+ elif api_key is not None:
81
+ raise ValueError("Please provide either 'user' or 'api_key', not both.")
82
+
83
+ for kwarg in kwargs:
84
+ if kwarg != "key":
85
+ raise TypeError(f"login() got unexpected keyword argument '{kwarg}'")
86
+ key: str | None = kwargs.get("key", None) # legacy API key aka password
87
+ if key is not None:
88
+ logger.warning(
89
+ "the legacy API key is deprecated and will likely be removed in a future version"
90
+ )
60
91
 
61
92
  if api_key is None:
62
93
  if "@" in user: # type: ignore
@@ -66,7 +97,6 @@ def login(
66
97
  user_settings = load_user(email, handle)
67
98
 
68
99
  if key is not None:
69
- # within UserSettings, we still call it "password" for a while
70
100
  user_settings.password = key
71
101
 
72
102
  if user_settings.password is None:
@@ -101,9 +131,9 @@ def login(
101
131
  user_uuid, user_id, user_handle, user_name, access_token = response
102
132
 
103
133
  if api_key is not None:
104
- logger.success(f"logged in {user_handle} (uid: {user_id})")
134
+ logger.success(f"logged in {user_handle}")
105
135
  else: # legacy flow
106
- logger.success(f"logged in with email {user_settings.email} (uid: {user_id})")
136
+ logger.success(f"logged in with email {user_settings.email}")
107
137
 
108
138
  user_settings.uid = user_id
109
139
  user_settings.handle = user_handle
@@ -121,6 +151,15 @@ def login(
121
151
 
122
152
 
123
153
  def logout():
154
+ """Log out the current user.
155
+
156
+ This deletes the `~/.lamin/current_user.env` so that you'll no longer be automatically authenticated in the environment.
157
+
158
+ It keeps a copy as `~/.lamin/user--<user_handle>.env` so you can easily switch between user profiles.
159
+
160
+ See Also:
161
+ Logout via the CLI command `lamin logout`, see `here <https://docs.lamin.ai/cli#logout>`__.
162
+ """
124
163
  if current_user_settings_file().exists():
125
164
  current_user_settings_file().unlink()
126
165
  settings._user_settings = None
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from lamin_utils import logger
4
+
3
5
 
4
6
  def get_location(ip="ipinfo.io"):
5
7
  import requests # type: ignore
@@ -32,7 +34,13 @@ def find_closest_aws_region() -> str:
32
34
  "eu-central-1": {"latitude": 50.11, "longitude": 8.68}, # Frankfurt
33
35
  "eu-west-2": {"latitude": 51.51, "longitude": -0.13}, # London, UK
34
36
  }
35
- your_location = get_location()
37
+ # sometimes get_location fails to obtain coordinates
38
+ try:
39
+ your_location = get_location()
40
+ except Exception as e:
41
+ logger.warning(f"failed to infer location, using us-east-1: {e}")
42
+ return "us-east-1"
43
+
36
44
  closest_region = ""
37
45
  min_distance = float("inf")
38
46
  for region in aws_region_locations:
@@ -93,7 +93,75 @@ def _get_storage_records_for_instance(
93
93
  return response.data
94
94
 
95
95
 
96
- def _select_storage(
96
+ def select_space(lnid: str) -> dict | None:
97
+ return call_with_fallback_auth(
98
+ _select_space,
99
+ lnid=lnid,
100
+ )
101
+
102
+
103
+ def _select_space(lnid: str, client: Client) -> dict | None:
104
+ response = client.table("space").select("*").eq("lnid", lnid).execute()
105
+ return response.data[0] if response.data else None
106
+
107
+
108
+ def update_storage_with_space(storage_lnid: str, space_lnid: str) -> dict | None:
109
+ return call_with_fallback_auth(
110
+ _update_storage_with_space,
111
+ storage_lnid=storage_lnid,
112
+ space_lnid=space_lnid,
113
+ )
114
+
115
+
116
+ def _update_storage_with_space(
117
+ storage_lnid: str, space_lnid: str, client: Client
118
+ ) -> dict | None:
119
+ # unfortunately these are two network requests
120
+ # but postgrest doesn't allow doing it in one
121
+ try:
122
+ space_response = (
123
+ client.table("space").select("id").eq("lnid", space_lnid).execute()
124
+ )
125
+
126
+ if not space_response.data:
127
+ raise ValueError(
128
+ f"Try again! Space with lnid '{space_lnid}' not found on hub"
129
+ )
130
+
131
+ space_id = space_response.data[0]["id"]
132
+
133
+ update_response = (
134
+ client.table("storage")
135
+ .update({"space_id": space_id})
136
+ .eq("lnid", storage_lnid)
137
+ .execute()
138
+ )
139
+
140
+ if not update_response.data:
141
+ raise ValueError(
142
+ f"Try again! Storage with lnid '{storage_lnid}' not found or update failed on hub"
143
+ )
144
+
145
+ return update_response.data[0]
146
+
147
+ except Exception as e:
148
+ print(f"Try again! Error updating storage in hub: {e}")
149
+ return None
150
+
151
+
152
+ def select_storage(lnid: str) -> dict | None:
153
+ return call_with_fallback_auth(
154
+ _select_storage,
155
+ lnid=lnid,
156
+ )
157
+
158
+
159
+ def _select_storage(lnid: str, client: Client) -> dict | None:
160
+ response = client.table("storage").select("*").eq("lnid", lnid).execute()
161
+ return response.data[0] if response.data else None
162
+
163
+
164
+ def _select_storage_by_settings(
97
165
  ssettings: StorageSettings, update_uid: bool, client: Client
98
166
  ) -> bool:
99
167
  root = ssettings.root_as_str
@@ -147,24 +215,26 @@ def select_storage_or_parent(path: str, access_token: str | None = None) -> dict
147
215
 
148
216
  def init_storage_hub(
149
217
  ssettings: StorageSettings,
150
- auto_populate_instance: bool = True,
151
218
  created_by: UUID | None = None,
152
219
  access_token: str | None = None,
153
220
  prevent_creation: bool = False,
221
+ is_default: bool = False,
222
+ space_id: UUID | None = None,
154
223
  ) -> Literal["hub-record-retrieved", "hub-record-created", "hub-record-not-created"]:
155
224
  """Creates or retrieves an existing storage record from the hub."""
156
225
  if settings.user.handle != "anonymous" or access_token is not None:
157
226
  return call_with_fallback_auth(
158
227
  _init_storage_hub,
159
228
  ssettings=ssettings,
160
- auto_populate_instance=auto_populate_instance,
161
229
  created_by=created_by,
162
230
  access_token=access_token,
163
231
  prevent_creation=prevent_creation,
232
+ is_default=is_default,
233
+ space_id=space_id,
164
234
  )
165
235
  else:
166
236
  storage_exists = call_with_fallback(
167
- _select_storage, ssettings=ssettings, update_uid=True
237
+ _select_storage_by_settings, ssettings=ssettings, update_uid=True
168
238
  )
169
239
  if storage_exists:
170
240
  return "hub-record-retrieved"
@@ -175,9 +245,10 @@ def init_storage_hub(
175
245
  def _init_storage_hub(
176
246
  client: Client,
177
247
  ssettings: StorageSettings,
178
- auto_populate_instance: bool,
179
248
  created_by: UUID | None = None,
180
249
  prevent_creation: bool = False,
250
+ is_default: bool = False,
251
+ space_id: UUID | None = None,
181
252
  ) -> Literal["hub-record-retrieved", "hub-record-created", "hub-record-not-created"]:
182
253
  from lamindb_setup import settings
183
254
 
@@ -185,28 +256,23 @@ def _init_storage_hub(
185
256
  # storage roots are always stored without the trailing slash in the SQL
186
257
  # database
187
258
  root = ssettings.root_as_str
188
- if _select_storage(ssettings, update_uid=True, client=client):
259
+ if _select_storage_by_settings(ssettings, update_uid=True, client=client):
189
260
  return "hub-record-retrieved"
190
261
  if prevent_creation:
191
262
  return "hub-record-not-created"
192
263
  if ssettings.type_is_cloud:
193
- id = uuid.uuid5(uuid.NAMESPACE_URL, root)
264
+ hash_string = (
265
+ root if ssettings.type_is_cloud else f"{ssettings.region}://{root}"
266
+ )
267
+ id = uuid.uuid5(uuid.NAMESPACE_URL, hash_string)
194
268
  else:
195
269
  id = uuid.uuid4()
196
- if (
197
- ssettings._instance_id is None
198
- and settings._instance_exists
199
- and auto_populate_instance
200
- ):
270
+ if ssettings._instance_id is None and settings._instance_exists:
201
271
  logger.warning(
202
272
  f"will manage storage location {ssettings.root_as_str} with instance {settings.instance.slug}"
203
273
  )
204
274
  ssettings._instance_id = settings.instance._id
205
- instance_id_hex = (
206
- ssettings._instance_id.hex
207
- if (ssettings._instance_id is not None and auto_populate_instance)
208
- else None
209
- )
275
+ assert ssettings._instance_id is not None, "connect to an instance"
210
276
  fields = {
211
277
  "id": id.hex,
212
278
  "lnid": ssettings.uid,
@@ -214,11 +280,10 @@ def _init_storage_hub(
214
280
  "root": root,
215
281
  "region": ssettings.region,
216
282
  "type": ssettings.type,
217
- "instance_id": instance_id_hex,
218
- # the empty string is important as we want the user flow to be through LaminHub
219
- # if this errors with unique constraint error, the user has to update
220
- # the description in LaminHub
283
+ "instance_id": ssettings._instance_id.hex,
221
284
  "description": "",
285
+ "is_default": is_default,
286
+ "space_id": space_id.hex if space_id is not None else None,
222
287
  }
223
288
  # TODO: add error message for violated unique constraint
224
289
  # on root & description
@@ -331,6 +396,7 @@ def _init_instance_hub(
331
396
  "schema_str": isettings._schema_str,
332
397
  "lamindb_version": lamindb_version,
333
398
  "public": False,
399
+ "created_by_id": account_id.hex, # type: ignore
334
400
  }
335
401
  if isettings.dialect != "sqlite":
336
402
  db_dsn = LaminDsnModel(db=isettings.db)
@@ -351,9 +417,6 @@ def _init_instance_hub(
351
417
  except APIError:
352
418
  logger.warning(f"instance already existed at: https://lamin.ai/{slug}")
353
419
  return None
354
- client.table("storage").update(
355
- {"instance_id": isettings._id.hex, "is_default": True}
356
- ).eq("id", isettings.storage._uuid.hex).execute() # type: ignore
357
420
  if isettings.dialect != "sqlite" and isettings.is_remote:
358
421
  logger.important(f"go to: https://lamin.ai/{slug}")
359
422
 
@@ -2,11 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import sys
5
+ import warnings
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  from lamin_utils import logger
8
9
  from platformdirs import user_cache_dir
9
10
 
11
+ from lamindb_setup.errors import CurrentInstanceNotConfigured
12
+
10
13
  from ._settings_load import (
11
14
  load_cache_path_from_settings,
12
15
  load_instance_settings,
@@ -87,10 +90,13 @@ class SetupSettings:
87
90
  - in Python: `ln.setup.settings.auto_connect = True/False`
88
91
  - via the CLI: `lamin settings set auto-connect true/false`
89
92
  """
90
- return self._auto_connect_path.exists()
93
+ return True
91
94
 
92
95
  @auto_connect.setter
93
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
+ # )
94
100
  if value:
95
101
  self._auto_connect_path.touch()
96
102
  else:
@@ -201,9 +207,10 @@ class SetupSettings:
201
207
  If `True`, the current instance is connected, meaning that the db and other settings
202
208
  are properly configured for use.
203
209
  """
204
- from .django import IS_SETUP # always import to protect from assignment
205
-
206
- return IS_SETUP
210
+ if self._instance_exists:
211
+ return self.instance.slug != "none/none"
212
+ else:
213
+ return False
207
214
 
208
215
  @property
209
216
  def private_django_api(self) -> bool:
@@ -266,7 +273,7 @@ class SetupSettings:
266
273
  self.instance # noqa
267
274
  return True
268
275
  # this is implicit logic that catches if no instance is loaded
269
- except SystemExit:
276
+ except CurrentInstanceNotConfigured:
270
277
  return False
271
278
 
272
279
  @property
@@ -17,6 +17,7 @@ from ._settings_storage import (
17
17
  LEGACY_STORAGE_UID_FILE_KEY,
18
18
  STORAGE_UID_FILE_KEY,
19
19
  StorageSettings,
20
+ get_storage_type,
20
21
  init_storage,
21
22
  instance_uid_from_uuid,
22
23
  )
@@ -31,6 +32,7 @@ if TYPE_CHECKING:
31
32
  from uuid import UUID
32
33
 
33
34
  from ._settings_user import UserSettings
35
+ from .types import UPathStr
34
36
 
35
37
  LOCAL_STORAGE_MESSAGE = "No local storage location found in current environment: defaulting to cloud storage"
36
38
 
@@ -50,6 +52,18 @@ def is_local_db_url(db_url: str) -> bool:
50
52
  return False
51
53
 
52
54
 
55
+ def check_is_instance_remote(root: UPathStr, db: str | None) -> bool:
56
+ # returns True for cloud SQLite
57
+ # and remote postgres
58
+ root_str = str(root)
59
+ if not root_str.startswith("create-s3") and get_storage_type(root_str) == "local":
60
+ return False
61
+
62
+ if db is not None and is_local_db_url(db):
63
+ return False
64
+ return True
65
+
66
+
53
67
  class InstanceSettings:
54
68
  """Instance settings."""
55
69
 
@@ -58,9 +72,8 @@ class InstanceSettings:
58
72
  id: UUID, # instance id/uuid
59
73
  owner: str, # owner handle
60
74
  name: str, # instance name
61
- storage: StorageSettings, # storage location
75
+ storage: StorageSettings | None = None, # storage location
62
76
  keep_artifacts_local: bool = False, # default to local storage
63
- uid: str | None = None, # instance uid/lnid
64
77
  db: str | None = None, # DB URI
65
78
  modules: str | None = None, # comma-separated string of module names
66
79
  git_repo: str | None = None, # a git repo URL
@@ -76,8 +89,7 @@ class InstanceSettings:
76
89
  self._id_: UUID = id
77
90
  self._owner: str = owner
78
91
  self._name: str = name
79
- self._uid: str | None = uid
80
- self._storage: StorageSettings = storage
92
+ self._storage: StorageSettings | None = storage
81
93
  validate_db_arg(db)
82
94
  self._db: str | None = db
83
95
  self._schema_str: str | None = modules
@@ -232,7 +244,7 @@ class InstanceSettings:
232
244
  For a cloud instance, this is cloud storage. For a local instance, this
233
245
  is a local directory.
234
246
  """
235
- return self._storage
247
+ return self._storage # type: ignore
236
248
 
237
249
  @property
238
250
  def local_storage(self) -> StorageSettings:
@@ -431,6 +443,10 @@ class InstanceSettings:
431
443
  "It overwrites all db connections and is used instead of `instance.db`."
432
444
  )
433
445
  if self._db is None:
446
+ from .django import IS_SETUP
447
+
448
+ if self._storage is None and self.slug == "none/none":
449
+ return "sqlite:///:memory:"
434
450
  # here, we want the updated sqlite file
435
451
  # hence, we don't use self._sqlite_file_local()
436
452
  # error_no_origin=False because on instance init
@@ -476,15 +492,7 @@ class InstanceSettings:
476
492
  @property
477
493
  def is_remote(self) -> bool:
478
494
  """Boolean indicating if an instance has no local component."""
479
- if not self.storage.type_is_cloud:
480
- return False
481
-
482
- if self.dialect == "postgresql":
483
- if is_local_db_url(self.db):
484
- return False
485
- # returns True for cloud SQLite
486
- # and remote postgres
487
- return True
495
+ return check_is_instance_remote(self.storage.root_as_str, self.db)
488
496
 
489
497
  @property
490
498
  def is_on_hub(self) -> bool:
@@ -523,7 +531,7 @@ class InstanceSettings:
523
531
  write_to_disk: Save these instance settings to disk and
524
532
  overwrite the current instance settings file.
525
533
  """
526
- if write_to_disk:
534
+ if write_to_disk and self.slug != "none/none":
527
535
  assert self.name is not None
528
536
  filepath = self._get_settings_file()
529
537
  # persist under filepath for later reference
@@ -9,7 +9,7 @@ from dotenv import dotenv_values
9
9
  from lamin_utils import logger
10
10
  from pydantic import ValidationError
11
11
 
12
- from lamindb_setup.errors import SettingsEnvFileOutdated
12
+ from lamindb_setup.errors import CurrentInstanceNotConfigured, SettingsEnvFileOutdated
13
13
 
14
14
  from ._settings_instance import InstanceSettings
15
15
  from ._settings_storage import StorageSettings
@@ -50,7 +50,7 @@ def load_instance_settings(instance_settings_file: Path | None = None):
50
50
  if instance_settings_file is None:
51
51
  instance_settings_file = current_instance_settings_file()
52
52
  if not instance_settings_file.exists():
53
- raise SystemExit("No instance connected! Call `lamin connect` or `lamin init`")
53
+ raise CurrentInstanceNotConfigured
54
54
  try:
55
55
  settings_store = InstanceSettingsStore(_env_file=instance_settings_file)
56
56
  except (ValidationError, TypeError) as error:
@@ -92,11 +92,11 @@ def init_storage(
92
92
  instance_id: UUID,
93
93
  instance_slug: str,
94
94
  register_hub: bool | None = None,
95
- prevent_register_hub: bool = False,
96
95
  init_instance: bool = False,
97
96
  created_by: UUID | None = None,
98
97
  access_token: str | None = None,
99
98
  region: str | None = None,
99
+ space_uuid: UUID | None = None,
100
100
  ) -> tuple[
101
101
  StorageSettings,
102
102
  Literal["hub-record-not-created", "hub-record-retrieved", "hub-record-created"],
@@ -145,19 +145,17 @@ def init_storage(
145
145
  )
146
146
  # this retrieves the storage record if it exists already in the hub
147
147
  # and updates uid and instance_id in ssettings
148
- register_hub = (
149
- register_hub or ssettings.type_is_cloud
150
- ) # default to registering cloud storage
151
148
  if register_hub and not ssettings.type_is_cloud and ssettings.host is None:
152
149
  raise ValueError(
153
150
  "`host` must be set for local storage locations that are registered on the hub"
154
151
  )
155
152
  hub_record_status = init_storage_hub(
156
153
  ssettings,
157
- auto_populate_instance=not init_instance,
158
154
  created_by=created_by,
159
155
  access_token=access_token,
160
- prevent_creation=prevent_register_hub or not register_hub,
156
+ prevent_creation=not register_hub,
157
+ is_default=init_instance,
158
+ space_id=space_uuid,
161
159
  )
162
160
  # we check the write access here if the storage record has not been retrieved from the hub
163
161
  if hub_record_status != "hub-record-retrieved":
@@ -9,7 +9,7 @@ import jwt
9
9
  import time
10
10
  from pathlib import Path
11
11
  import time
12
- from ._settings_instance import InstanceSettings
12
+ from ._settings_instance import InstanceSettings, is_local_db_url
13
13
 
14
14
  from lamin_utils import logger
15
15
 
@@ -156,18 +156,33 @@ def setup_django(
156
156
 
157
157
  import dj_database_url
158
158
  import django
159
+ from django.apps import apps
159
160
  from django.conf import settings
160
161
  from django.core.management import call_command
161
162
 
162
163
  # configuration
163
164
  if not settings.configured:
165
+ instance_db = isettings.db
166
+ if isettings.dialect == "postgresql":
167
+ if os.getenv("LAMIN_DB_SSL_REQUIRE") == "false":
168
+ ssl_require = False
169
+ else:
170
+ ssl_require = not is_local_db_url(instance_db)
171
+ options = {"connect_timeout": os.getenv("PGCONNECT_TIMEOUT", 20)}
172
+ else:
173
+ ssl_require = False
174
+ options = {}
164
175
  default_db = dj_database_url.config(
165
176
  env="LAMINDB_DJANGO_DATABASE_URL",
166
- default=isettings.db,
177
+ default=instance_db,
167
178
  # see comment next to patching BaseDatabaseWrapper below
168
179
  conn_max_age=CONN_MAX_AGE,
169
180
  conn_health_checks=True,
181
+ ssl_require=ssl_require,
170
182
  )
183
+ if options:
184
+ # do not overwrite keys in options if set
185
+ default_db["OPTIONS"] = {**options, **default_db.get("OPTIONS", {})}
171
186
  DATABASES = {
172
187
  "default": default_db,
173
188
  }
@@ -214,6 +229,9 @@ def setup_django(
214
229
  STATIC_URL="static/",
215
230
  )
216
231
  settings.configure(**kwargs)
232
+ # this isn't needed the first time django.setup() is called, but for unknown reason it's needed the second time
233
+ # the first time, it already defaults to true
234
+ apps.apps_ready = True
217
235
  django.setup(set_prefix=False)
218
236
  # https://laminlabs.slack.com/archives/C04FPE8V01W/p1698239551460289
219
237
  from django.db.backends.base.base import BaseDatabaseWrapper
@@ -278,6 +296,7 @@ def reset_django():
278
296
  app_names = {"django"} | {app.name for app in apps.get_app_configs()}
279
297
 
280
298
  apps.app_configs.clear()
299
+ apps.all_models.clear()
281
300
  apps.apps_ready = apps.models_ready = apps.ready = apps.loading = False
282
301
  apps.clear_cache()
283
302
 
@@ -994,6 +994,9 @@ def check_storage_is_empty(
994
994
  root_string += "/"
995
995
  directory_string = root_string + ".lamindb"
996
996
  objects = root_upath.fs.find(directory_string)
997
+ if account_for_sqlite_file:
998
+ # ignore exclusion dir for cloud sqlite
999
+ objects = [o for o in objects if "/.lamindb/_exclusion/" not in o]
997
1000
  n_files = len(objects)
998
1001
  n_diff = n_files - n_offset_objects
999
1002
  ask_for_deletion = (
@@ -1001,10 +1004,7 @@ def check_storage_is_empty(
1001
1004
  if raise_error
1002
1005
  else "consider deleting them"
1003
1006
  )
1004
- message = (
1005
- f"'{directory_string}' contains {n_files - n_offset_objects} objects"
1006
- f" - {ask_for_deletion}"
1007
- )
1007
+ message = f"'{directory_string}' contains {n_diff} objects" f" - {ask_for_deletion}"
1008
1008
  if n_diff > 0:
1009
1009
  if raise_error:
1010
1010
  raise StorageNotEmpty(message) from None
lamindb_setup/errors.py CHANGED
@@ -3,6 +3,7 @@
3
3
  .. autosummary::
4
4
  :toctree: .
5
5
 
6
+ CurrentInstanceNotConfigured
6
7
  InstanceNotSetupError
7
8
  ModuleWasntConfigured
8
9
  StorageAlreadyManaged
@@ -27,6 +28,7 @@ class DefaultMessageException(Exception):
27
28
  super().__init__(message)
28
29
 
29
30
 
31
+ # TODO: remove this exception sooner or later because we don't have a need for it anymore
30
32
  class InstanceNotSetupError(DefaultMessageException):
31
33
  default_message = """\
32
34
  To use lamindb, you need to connect to an instance.
@@ -37,6 +39,14 @@ If you used the CLI to set up lamindb in a notebook, restart the Python session.
37
39
  """
38
40
 
39
41
 
42
+ class CurrentInstanceNotConfigured(DefaultMessageException):
43
+ default_message = """\
44
+ No instance is connected! Call
45
+ - CLI: lamin connect / lamin init
46
+ - Python: ln.connect() / ln.setup.init()
47
+ - R: ln$connect() / ln$setup$init()"""
48
+
49
+
40
50
  MODULE_WASNT_CONFIGURED_MESSAGE_TEMPLATE = (
41
51
  "'{}' wasn't configured for this instance -- "
42
52
  "if you want it, go to your instance settings page and add it under 'schema modules' (or ask an admin to do so)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb_setup
3
- Version: 1.9.0
3
+ Version: 1.10.0
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10
@@ -21,7 +21,7 @@ Requires-Dist: psutil
21
21
  Requires-Dist: packaging
22
22
  Requires-Dist: urllib3<2 ; extra == "aws"
23
23
  Requires-Dist: aiobotocore[boto3]>=2.5.4,<3.0.0 ; extra == "aws"
24
- Requires-Dist: s3fs>=2023.12.2,<=2025.3.2,!=2024.10.0 ; extra == "aws"
24
+ Requires-Dist: s3fs>=2023.12.2,<=2025.7.0,!=2024.10.0 ; extra == "aws"
25
25
  Requires-Dist: line_profiler ; extra == "dev"
26
26
  Requires-Dist: psycopg2-binary ; extra == "dev"
27
27
  Requires-Dist: python-dotenv ; extra == "dev"
@@ -32,7 +32,7 @@ Requires-Dist: pytest-xdist ; extra == "dev"
32
32
  Requires-Dist: nbproject-test>=0.4.3 ; extra == "dev"
33
33
  Requires-Dist: pandas ; extra == "dev"
34
34
  Requires-Dist: django-schema-graph ; extra == "erdiagram"
35
- Requires-Dist: gcsfs>=2023.12.2,<=2025.3.2 ; extra == "gcp"
35
+ Requires-Dist: gcsfs>=2023.12.2,<=2025.7.0 ; extra == "gcp"
36
36
  Project-URL: Home, https://github.com/laminlabs/lamindb-setup
37
37
  Provides-Extra: aws
38
38
  Provides-Extra: dev