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.
- lamindb_setup/__init__.py +1 -1
- lamindb_setup/_cache.py +3 -3
- lamindb_setup/_check_setup.py +40 -14
- lamindb_setup/_connect_instance.py +117 -44
- lamindb_setup/_delete.py +3 -0
- lamindb_setup/_disconnect.py +4 -1
- lamindb_setup/_init_instance.py +36 -46
- lamindb_setup/_migrate.py +19 -7
- lamindb_setup/_register_instance.py +2 -5
- lamindb_setup/_set_managed_storage.py +0 -1
- lamindb_setup/_setup_user.py +52 -13
- lamindb_setup/core/_aws_storage.py +9 -1
- lamindb_setup/core/_hub_core.py +87 -24
- lamindb_setup/core/_settings.py +12 -5
- lamindb_setup/core/_settings_instance.py +23 -15
- lamindb_setup/core/_settings_load.py +2 -2
- lamindb_setup/core/_settings_storage.py +4 -6
- lamindb_setup/core/django.py +21 -2
- lamindb_setup/core/upath.py +4 -4
- lamindb_setup/errors.py +10 -0
- {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.10.0.dist-info}/METADATA +3 -3
- {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.10.0.dist-info}/RECORD +24 -24
- {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.10.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-1.9.0.dist-info → lamindb_setup-1.10.0.dist-info}/WHEEL +0 -0
lamindb_setup/_setup_user.py
CHANGED
|
@@ -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,
|
|
46
|
+
user: str | None = None, *, api_key: str | None = None, **kwargs
|
|
47
47
|
) -> UserSettings:
|
|
48
|
-
|
|
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
|
|
52
|
-
api_key: API key
|
|
53
|
-
|
|
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
|
|
56
|
-
if
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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}
|
|
134
|
+
logger.success(f"logged in {user_handle}")
|
|
105
135
|
else: # legacy flow
|
|
106
|
-
logger.success(f"logged in with email {user_settings.email}
|
|
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
|
-
|
|
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:
|
lamindb_setup/core/_hub_core.py
CHANGED
|
@@ -93,7 +93,75 @@ def _get_storage_records_for_instance(
|
|
|
93
93
|
return response.data
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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":
|
|
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
|
|
lamindb_setup/core/_settings.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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=
|
|
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":
|
lamindb_setup/core/django.py
CHANGED
|
@@ -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=
|
|
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
|
|
lamindb_setup/core/upath.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|