lamindb_setup 1.9.1__py3-none-any.whl → 1.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lamindb_setup/__init__.py +107 -107
- lamindb_setup/_cache.py +87 -87
- lamindb_setup/_check_setup.py +192 -166
- lamindb_setup/_connect_instance.py +415 -328
- lamindb_setup/_delete.py +144 -141
- lamindb_setup/_disconnect.py +35 -32
- lamindb_setup/_init_instance.py +430 -440
- lamindb_setup/_migrate.py +278 -266
- lamindb_setup/_register_instance.py +32 -35
- lamindb_setup/_schema_metadata.py +441 -441
- lamindb_setup/_set_managed_storage.py +69 -70
- lamindb_setup/_setup_user.py +172 -133
- lamindb_setup/core/__init__.py +21 -21
- lamindb_setup/core/_aws_options.py +223 -223
- lamindb_setup/core/_aws_storage.py +9 -1
- lamindb_setup/core/_hub_client.py +248 -248
- lamindb_setup/core/_hub_core.py +728 -665
- lamindb_setup/core/_hub_crud.py +227 -227
- lamindb_setup/core/_private_django_api.py +83 -83
- lamindb_setup/core/_settings.py +384 -377
- lamindb_setup/core/_settings_instance.py +577 -569
- lamindb_setup/core/_settings_load.py +141 -141
- lamindb_setup/core/_settings_save.py +95 -95
- lamindb_setup/core/_settings_storage.py +427 -429
- lamindb_setup/core/_settings_store.py +91 -91
- lamindb_setup/core/_settings_user.py +55 -55
- lamindb_setup/core/_setup_bionty_sources.py +44 -44
- lamindb_setup/core/cloud_sqlite_locker.py +240 -240
- lamindb_setup/core/django.py +315 -305
- lamindb_setup/core/exceptions.py +1 -1
- lamindb_setup/core/hashing.py +134 -134
- lamindb_setup/core/types.py +1 -1
- lamindb_setup/core/upath.py +1013 -1013
- lamindb_setup/errors.py +80 -70
- lamindb_setup/types.py +20 -20
- {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.0.dist-info}/METADATA +3 -3
- lamindb_setup-1.10.0.dist-info/RECORD +50 -0
- lamindb_setup-1.9.1.dist-info/RECORD +0 -50
- {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.0.dist-info}/LICENSE +0 -0
- {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.0.dist-info}/WHEEL +0 -0
lamindb_setup/core/django.py
CHANGED
|
@@ -1,305 +1,315 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
# flake8: noqa
|
|
4
|
-
import builtins
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
import importlib as il
|
|
8
|
-
import jwt
|
|
9
|
-
import time
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
import time
|
|
12
|
-
from ._settings_instance import InstanceSettings, is_local_db_url
|
|
13
|
-
|
|
14
|
-
from lamin_utils import logger
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
IS_RUN_FROM_IPYTHON = getattr(builtins, "__IPYTHON__", False)
|
|
18
|
-
IS_SETUP = False
|
|
19
|
-
IS_MIGRATING = False
|
|
20
|
-
CONN_MAX_AGE = 299
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# db token that refreshes on access if needed
|
|
24
|
-
class DBToken:
|
|
25
|
-
def __init__(
|
|
26
|
-
self, instance: InstanceSettings | dict, access_token: str | None = None
|
|
27
|
-
):
|
|
28
|
-
self.instance = instance
|
|
29
|
-
self.access_token = access_token
|
|
30
|
-
# initialized in token_query
|
|
31
|
-
self._token: str | None = None
|
|
32
|
-
self._token_query: str | None = None
|
|
33
|
-
self._expiration: float
|
|
34
|
-
|
|
35
|
-
def _refresh_token(self):
|
|
36
|
-
from ._hub_core import access_db
|
|
37
|
-
from psycopg2.extensions import adapt
|
|
38
|
-
|
|
39
|
-
self._token = access_db(self.instance, self.access_token)
|
|
40
|
-
self._token_query = (
|
|
41
|
-
f"SELECT set_token({adapt(self._token).getquoted().decode()}, true);"
|
|
42
|
-
)
|
|
43
|
-
self._expiration = jwt.decode(self._token, options={"verify_signature": False})[
|
|
44
|
-
"exp"
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def token_query(self) -> str:
|
|
49
|
-
# refresh token if needed
|
|
50
|
-
if self._token is None or time.time() >= self._expiration:
|
|
51
|
-
self._refresh_token()
|
|
52
|
-
|
|
53
|
-
return self._token_query # type: ignore
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# a class to manage jwt in dbs
|
|
57
|
-
class DBTokenManager:
|
|
58
|
-
def __init__(self):
|
|
59
|
-
from django.db.transaction import Atomic
|
|
60
|
-
|
|
61
|
-
self.original_atomic_enter = Atomic.__enter__
|
|
62
|
-
|
|
63
|
-
self.tokens: dict[str, DBToken] = {}
|
|
64
|
-
|
|
65
|
-
def get_connection(self, connection_name: str):
|
|
66
|
-
from django.db import connections
|
|
67
|
-
|
|
68
|
-
connection = connections[connection_name]
|
|
69
|
-
assert connection.vendor == "postgresql"
|
|
70
|
-
|
|
71
|
-
return connection
|
|
72
|
-
|
|
73
|
-
def set(self, token: DBToken, connection_name: str = "default"):
|
|
74
|
-
from django.db.transaction import Atomic
|
|
75
|
-
|
|
76
|
-
connection = self.get_connection(connection_name)
|
|
77
|
-
|
|
78
|
-
def set_token_wrapper(execute, sql, params, many, context):
|
|
79
|
-
not_in_atomic_block = (
|
|
80
|
-
context is None
|
|
81
|
-
or "connection" not in context
|
|
82
|
-
or not context["connection"].in_atomic_block
|
|
83
|
-
)
|
|
84
|
-
# ignore atomic blocks
|
|
85
|
-
if not_in_atomic_block:
|
|
86
|
-
sql = token.token_query + sql
|
|
87
|
-
result = execute(sql, params, many, context)
|
|
88
|
-
# this ensures that psycopg3 in the current env doesn't break this wrapper
|
|
89
|
-
# psycopg3 returns a cursor
|
|
90
|
-
# psycopg3 fetching differs from psycopg2, it returns the output of all sql statements
|
|
91
|
-
# not only the last one as psycopg2 does. So we shift the cursor from set_token
|
|
92
|
-
if (
|
|
93
|
-
not_in_atomic_block
|
|
94
|
-
and result is not None
|
|
95
|
-
and hasattr(result, "nextset")
|
|
96
|
-
):
|
|
97
|
-
result.nextset()
|
|
98
|
-
return result
|
|
99
|
-
|
|
100
|
-
connection.execute_wrappers.append(set_token_wrapper)
|
|
101
|
-
|
|
102
|
-
self.tokens[connection_name] = token
|
|
103
|
-
|
|
104
|
-
# ensure we set the token only once for an outer atomic block
|
|
105
|
-
def __enter__(atomic):
|
|
106
|
-
self.original_atomic_enter(atomic)
|
|
107
|
-
connection_name = "default" if atomic.using is None else atomic.using
|
|
108
|
-
if connection_name in self.tokens:
|
|
109
|
-
# here we don't use the connection from the closure
|
|
110
|
-
# because Atomic is a single class to manage transactions for all connections
|
|
111
|
-
connection = self.get_connection(connection_name)
|
|
112
|
-
if len(connection.atomic_blocks) == 1:
|
|
113
|
-
token = self.tokens[connection_name]
|
|
114
|
-
# use raw psycopg2 connection here
|
|
115
|
-
# atomic block ensures connection
|
|
116
|
-
connection.connection.cursor().execute(token.token_query)
|
|
117
|
-
|
|
118
|
-
Atomic.__enter__ = __enter__
|
|
119
|
-
logger.debug("django.db.transaction.Atomic.__enter__ has been patched")
|
|
120
|
-
|
|
121
|
-
def reset(self, connection_name: str = "default"):
|
|
122
|
-
connection = self.get_connection(connection_name)
|
|
123
|
-
|
|
124
|
-
connection.execute_wrappers = [
|
|
125
|
-
w
|
|
126
|
-
for w in connection.execute_wrappers
|
|
127
|
-
if getattr(w, "__name__", None) != "set_token_wrapper"
|
|
128
|
-
]
|
|
129
|
-
|
|
130
|
-
self.tokens.pop(connection_name, None)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
db_token_manager = DBTokenManager()
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def close_if_health_check_failed(self) -> None:
|
|
137
|
-
if self.close_at is not None:
|
|
138
|
-
if time.monotonic() >= self.close_at:
|
|
139
|
-
self.close()
|
|
140
|
-
self.close_at = time.monotonic() + CONN_MAX_AGE
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# this bundles set up and migration management
|
|
144
|
-
def setup_django(
|
|
145
|
-
isettings: InstanceSettings,
|
|
146
|
-
deploy_migrations: bool = False,
|
|
147
|
-
create_migrations: bool = False,
|
|
148
|
-
configure_only: bool = False,
|
|
149
|
-
init: bool = False,
|
|
150
|
-
view_schema: bool = False,
|
|
151
|
-
appname_number: tuple[str, int] | None = None,
|
|
152
|
-
):
|
|
153
|
-
if IS_RUN_FROM_IPYTHON:
|
|
154
|
-
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
|
155
|
-
logger.debug("DJANGO_ALLOW_ASYNC_UNSAFE env variable has been set to 'true'")
|
|
156
|
-
|
|
157
|
-
import dj_database_url
|
|
158
|
-
import django
|
|
159
|
-
from django.
|
|
160
|
-
from django.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# flake8: noqa
|
|
4
|
+
import builtins
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import importlib as il
|
|
8
|
+
import jwt
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import time
|
|
12
|
+
from ._settings_instance import InstanceSettings, is_local_db_url
|
|
13
|
+
|
|
14
|
+
from lamin_utils import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
IS_RUN_FROM_IPYTHON = getattr(builtins, "__IPYTHON__", False)
|
|
18
|
+
IS_SETUP = False
|
|
19
|
+
IS_MIGRATING = False
|
|
20
|
+
CONN_MAX_AGE = 299
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# db token that refreshes on access if needed
|
|
24
|
+
class DBToken:
|
|
25
|
+
def __init__(
|
|
26
|
+
self, instance: InstanceSettings | dict, access_token: str | None = None
|
|
27
|
+
):
|
|
28
|
+
self.instance = instance
|
|
29
|
+
self.access_token = access_token
|
|
30
|
+
# initialized in token_query
|
|
31
|
+
self._token: str | None = None
|
|
32
|
+
self._token_query: str | None = None
|
|
33
|
+
self._expiration: float
|
|
34
|
+
|
|
35
|
+
def _refresh_token(self):
|
|
36
|
+
from ._hub_core import access_db
|
|
37
|
+
from psycopg2.extensions import adapt
|
|
38
|
+
|
|
39
|
+
self._token = access_db(self.instance, self.access_token)
|
|
40
|
+
self._token_query = (
|
|
41
|
+
f"SELECT set_token({adapt(self._token).getquoted().decode()}, true);"
|
|
42
|
+
)
|
|
43
|
+
self._expiration = jwt.decode(self._token, options={"verify_signature": False})[
|
|
44
|
+
"exp"
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def token_query(self) -> str:
|
|
49
|
+
# refresh token if needed
|
|
50
|
+
if self._token is None or time.time() >= self._expiration:
|
|
51
|
+
self._refresh_token()
|
|
52
|
+
|
|
53
|
+
return self._token_query # type: ignore
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# a class to manage jwt in dbs
|
|
57
|
+
class DBTokenManager:
|
|
58
|
+
def __init__(self):
|
|
59
|
+
from django.db.transaction import Atomic
|
|
60
|
+
|
|
61
|
+
self.original_atomic_enter = Atomic.__enter__
|
|
62
|
+
|
|
63
|
+
self.tokens: dict[str, DBToken] = {}
|
|
64
|
+
|
|
65
|
+
def get_connection(self, connection_name: str):
|
|
66
|
+
from django.db import connections
|
|
67
|
+
|
|
68
|
+
connection = connections[connection_name]
|
|
69
|
+
assert connection.vendor == "postgresql"
|
|
70
|
+
|
|
71
|
+
return connection
|
|
72
|
+
|
|
73
|
+
def set(self, token: DBToken, connection_name: str = "default"):
|
|
74
|
+
from django.db.transaction import Atomic
|
|
75
|
+
|
|
76
|
+
connection = self.get_connection(connection_name)
|
|
77
|
+
|
|
78
|
+
def set_token_wrapper(execute, sql, params, many, context):
|
|
79
|
+
not_in_atomic_block = (
|
|
80
|
+
context is None
|
|
81
|
+
or "connection" not in context
|
|
82
|
+
or not context["connection"].in_atomic_block
|
|
83
|
+
)
|
|
84
|
+
# ignore atomic blocks
|
|
85
|
+
if not_in_atomic_block:
|
|
86
|
+
sql = token.token_query + sql
|
|
87
|
+
result = execute(sql, params, many, context)
|
|
88
|
+
# this ensures that psycopg3 in the current env doesn't break this wrapper
|
|
89
|
+
# psycopg3 returns a cursor
|
|
90
|
+
# psycopg3 fetching differs from psycopg2, it returns the output of all sql statements
|
|
91
|
+
# not only the last one as psycopg2 does. So we shift the cursor from set_token
|
|
92
|
+
if (
|
|
93
|
+
not_in_atomic_block
|
|
94
|
+
and result is not None
|
|
95
|
+
and hasattr(result, "nextset")
|
|
96
|
+
):
|
|
97
|
+
result.nextset()
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
connection.execute_wrappers.append(set_token_wrapper)
|
|
101
|
+
|
|
102
|
+
self.tokens[connection_name] = token
|
|
103
|
+
|
|
104
|
+
# ensure we set the token only once for an outer atomic block
|
|
105
|
+
def __enter__(atomic):
|
|
106
|
+
self.original_atomic_enter(atomic)
|
|
107
|
+
connection_name = "default" if atomic.using is None else atomic.using
|
|
108
|
+
if connection_name in self.tokens:
|
|
109
|
+
# here we don't use the connection from the closure
|
|
110
|
+
# because Atomic is a single class to manage transactions for all connections
|
|
111
|
+
connection = self.get_connection(connection_name)
|
|
112
|
+
if len(connection.atomic_blocks) == 1:
|
|
113
|
+
token = self.tokens[connection_name]
|
|
114
|
+
# use raw psycopg2 connection here
|
|
115
|
+
# atomic block ensures connection
|
|
116
|
+
connection.connection.cursor().execute(token.token_query)
|
|
117
|
+
|
|
118
|
+
Atomic.__enter__ = __enter__
|
|
119
|
+
logger.debug("django.db.transaction.Atomic.__enter__ has been patched")
|
|
120
|
+
|
|
121
|
+
def reset(self, connection_name: str = "default"):
|
|
122
|
+
connection = self.get_connection(connection_name)
|
|
123
|
+
|
|
124
|
+
connection.execute_wrappers = [
|
|
125
|
+
w
|
|
126
|
+
for w in connection.execute_wrappers
|
|
127
|
+
if getattr(w, "__name__", None) != "set_token_wrapper"
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
self.tokens.pop(connection_name, None)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
db_token_manager = DBTokenManager()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def close_if_health_check_failed(self) -> None:
|
|
137
|
+
if self.close_at is not None:
|
|
138
|
+
if time.monotonic() >= self.close_at:
|
|
139
|
+
self.close()
|
|
140
|
+
self.close_at = time.monotonic() + CONN_MAX_AGE
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# this bundles set up and migration management
|
|
144
|
+
def setup_django(
|
|
145
|
+
isettings: InstanceSettings,
|
|
146
|
+
deploy_migrations: bool = False,
|
|
147
|
+
create_migrations: bool = False,
|
|
148
|
+
configure_only: bool = False,
|
|
149
|
+
init: bool = False,
|
|
150
|
+
view_schema: bool = False,
|
|
151
|
+
appname_number: tuple[str, int] | None = None,
|
|
152
|
+
):
|
|
153
|
+
if IS_RUN_FROM_IPYTHON:
|
|
154
|
+
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
|
155
|
+
logger.debug("DJANGO_ALLOW_ASYNC_UNSAFE env variable has been set to 'true'")
|
|
156
|
+
|
|
157
|
+
import dj_database_url
|
|
158
|
+
import django
|
|
159
|
+
from django.apps import apps
|
|
160
|
+
from django.conf import settings
|
|
161
|
+
from django.core.management import call_command
|
|
162
|
+
|
|
163
|
+
# configuration
|
|
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 = {}
|
|
175
|
+
default_db = dj_database_url.config(
|
|
176
|
+
env="LAMINDB_DJANGO_DATABASE_URL",
|
|
177
|
+
default=instance_db,
|
|
178
|
+
# see comment next to patching BaseDatabaseWrapper below
|
|
179
|
+
conn_max_age=CONN_MAX_AGE,
|
|
180
|
+
conn_health_checks=True,
|
|
181
|
+
ssl_require=ssl_require,
|
|
182
|
+
)
|
|
183
|
+
if options:
|
|
184
|
+
# do not overwrite keys in options if set
|
|
185
|
+
default_db["OPTIONS"] = {**options, **default_db.get("OPTIONS", {})}
|
|
186
|
+
DATABASES = {
|
|
187
|
+
"default": default_db,
|
|
188
|
+
}
|
|
189
|
+
from .._init_instance import get_schema_module_name
|
|
190
|
+
|
|
191
|
+
module_names = ["core"] + list(isettings.modules)
|
|
192
|
+
raise_import_error = True if init else False
|
|
193
|
+
installed_apps = [
|
|
194
|
+
package_name
|
|
195
|
+
for name in module_names
|
|
196
|
+
if (
|
|
197
|
+
package_name := get_schema_module_name(
|
|
198
|
+
name, raise_import_error=raise_import_error
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
is not None
|
|
202
|
+
]
|
|
203
|
+
if view_schema:
|
|
204
|
+
installed_apps = installed_apps[::-1] # to fix how apps appear
|
|
205
|
+
installed_apps += ["schema_graph", "django.contrib.staticfiles"]
|
|
206
|
+
|
|
207
|
+
kwargs = dict(
|
|
208
|
+
INSTALLED_APPS=installed_apps,
|
|
209
|
+
DATABASES=DATABASES,
|
|
210
|
+
DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
|
|
211
|
+
TIME_ZONE="UTC",
|
|
212
|
+
USE_TZ=True,
|
|
213
|
+
)
|
|
214
|
+
if view_schema:
|
|
215
|
+
kwargs.update(
|
|
216
|
+
DEBUG=True,
|
|
217
|
+
ROOT_URLCONF="lamindb_setup._schema",
|
|
218
|
+
SECRET_KEY="dummy",
|
|
219
|
+
TEMPLATES=[
|
|
220
|
+
{
|
|
221
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
222
|
+
"APP_DIRS": True,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
STATIC_ROOT=f"{Path.home().as_posix()}/.lamin/",
|
|
226
|
+
STATICFILES_FINDERS=[
|
|
227
|
+
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
|
228
|
+
],
|
|
229
|
+
STATIC_URL="static/",
|
|
230
|
+
)
|
|
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
|
|
235
|
+
django.setup(set_prefix=False)
|
|
236
|
+
# https://laminlabs.slack.com/archives/C04FPE8V01W/p1698239551460289
|
|
237
|
+
from django.db.backends.base.base import BaseDatabaseWrapper
|
|
238
|
+
|
|
239
|
+
BaseDatabaseWrapper.close_if_health_check_failed = close_if_health_check_failed
|
|
240
|
+
logger.debug(
|
|
241
|
+
"django.db.backends.base.base.BaseDatabaseWrapper.close_if_health_check_failed has been patched"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if isettings._fine_grained_access and isettings._db_permissions == "jwt":
|
|
245
|
+
db_token = DBToken(isettings)
|
|
246
|
+
db_token_manager.set(db_token) # sets for the default connection
|
|
247
|
+
|
|
248
|
+
if configure_only:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
# migrations management
|
|
252
|
+
if create_migrations:
|
|
253
|
+
call_command("makemigrations")
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
if deploy_migrations:
|
|
257
|
+
if appname_number is None:
|
|
258
|
+
call_command("migrate", verbosity=2)
|
|
259
|
+
else:
|
|
260
|
+
app_name, app_number = appname_number
|
|
261
|
+
call_command("migrate", app_name, app_number, verbosity=2)
|
|
262
|
+
isettings._update_cloud_sqlite_file(unlock_cloud_sqlite=False)
|
|
263
|
+
elif init:
|
|
264
|
+
global IS_MIGRATING
|
|
265
|
+
IS_MIGRATING = True
|
|
266
|
+
call_command("migrate", verbosity=0)
|
|
267
|
+
IS_MIGRATING = False
|
|
268
|
+
|
|
269
|
+
global IS_SETUP
|
|
270
|
+
IS_SETUP = True
|
|
271
|
+
|
|
272
|
+
if isettings.keep_artifacts_local:
|
|
273
|
+
isettings._local_storage = isettings._search_local_root()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# THIS IS NOT SAFE
|
|
277
|
+
# especially if lamindb is imported already
|
|
278
|
+
# django.setup fails if called for the second time
|
|
279
|
+
# reset_django() allows to call setup again,
|
|
280
|
+
# needed to connect to a different instance in the same process if connected already
|
|
281
|
+
# there could be problems if models are already imported from lamindb or other modules
|
|
282
|
+
# these 'old' models can have any number of problems
|
|
283
|
+
def reset_django():
|
|
284
|
+
from django.conf import settings
|
|
285
|
+
from django.apps import apps
|
|
286
|
+
from django.db import connections
|
|
287
|
+
|
|
288
|
+
if not settings.configured:
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
connections.close_all()
|
|
292
|
+
|
|
293
|
+
if getattr(settings, "_wrapped", None) is not None:
|
|
294
|
+
settings._wrapped = None
|
|
295
|
+
|
|
296
|
+
app_names = {"django"} | {app.name for app in apps.get_app_configs()}
|
|
297
|
+
|
|
298
|
+
apps.app_configs.clear()
|
|
299
|
+
apps.all_models.clear()
|
|
300
|
+
apps.apps_ready = apps.models_ready = apps.ready = apps.loading = False
|
|
301
|
+
apps.clear_cache()
|
|
302
|
+
|
|
303
|
+
# i suspect it is enough to just drop django and all the apps from sys.modules
|
|
304
|
+
# the code above is just a precaution
|
|
305
|
+
for module_name in list(sys.modules):
|
|
306
|
+
if module_name.partition(".")[0] in app_names:
|
|
307
|
+
del sys.modules[module_name]
|
|
308
|
+
|
|
309
|
+
il.invalidate_caches()
|
|
310
|
+
|
|
311
|
+
global db_token_manager
|
|
312
|
+
db_token_manager = DBTokenManager()
|
|
313
|
+
|
|
314
|
+
global IS_SETUP
|
|
315
|
+
IS_SETUP = False
|
lamindb_setup/core/exceptions.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
from lamindb_setup.errors import DefaultMessageException # backwards compatibility
|
|
1
|
+
from lamindb_setup.errors import DefaultMessageException # backwards compatibility
|