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.
Files changed (40) hide show
  1. lamindb_setup/__init__.py +107 -107
  2. lamindb_setup/_cache.py +87 -87
  3. lamindb_setup/_check_setup.py +192 -166
  4. lamindb_setup/_connect_instance.py +415 -328
  5. lamindb_setup/_delete.py +144 -141
  6. lamindb_setup/_disconnect.py +35 -32
  7. lamindb_setup/_init_instance.py +430 -440
  8. lamindb_setup/_migrate.py +278 -266
  9. lamindb_setup/_register_instance.py +32 -35
  10. lamindb_setup/_schema_metadata.py +441 -441
  11. lamindb_setup/_set_managed_storage.py +69 -70
  12. lamindb_setup/_setup_user.py +172 -133
  13. lamindb_setup/core/__init__.py +21 -21
  14. lamindb_setup/core/_aws_options.py +223 -223
  15. lamindb_setup/core/_aws_storage.py +9 -1
  16. lamindb_setup/core/_hub_client.py +248 -248
  17. lamindb_setup/core/_hub_core.py +728 -665
  18. lamindb_setup/core/_hub_crud.py +227 -227
  19. lamindb_setup/core/_private_django_api.py +83 -83
  20. lamindb_setup/core/_settings.py +384 -377
  21. lamindb_setup/core/_settings_instance.py +577 -569
  22. lamindb_setup/core/_settings_load.py +141 -141
  23. lamindb_setup/core/_settings_save.py +95 -95
  24. lamindb_setup/core/_settings_storage.py +427 -429
  25. lamindb_setup/core/_settings_store.py +91 -91
  26. lamindb_setup/core/_settings_user.py +55 -55
  27. lamindb_setup/core/_setup_bionty_sources.py +44 -44
  28. lamindb_setup/core/cloud_sqlite_locker.py +240 -240
  29. lamindb_setup/core/django.py +315 -305
  30. lamindb_setup/core/exceptions.py +1 -1
  31. lamindb_setup/core/hashing.py +134 -134
  32. lamindb_setup/core/types.py +1 -1
  33. lamindb_setup/core/upath.py +1013 -1013
  34. lamindb_setup/errors.py +80 -70
  35. lamindb_setup/types.py +20 -20
  36. {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.0.dist-info}/METADATA +3 -3
  37. lamindb_setup-1.10.0.dist-info/RECORD +50 -0
  38. lamindb_setup-1.9.1.dist-info/RECORD +0 -50
  39. {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.0.dist-info}/LICENSE +0 -0
  40. {lamindb_setup-1.9.1.dist-info → lamindb_setup-1.10.0.dist-info}/WHEEL +0 -0
@@ -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.conf import settings
160
- from django.core.management import call_command
161
-
162
- # configuration
163
- if not settings.configured:
164
- instance_db = isettings.db
165
- if isettings.dialect == "postgresql":
166
- if os.getenv("LAMIN_DB_SSL_REQUIRE") == "false":
167
- ssl_require = False
168
- else:
169
- ssl_require = not is_local_db_url(instance_db)
170
- else:
171
- ssl_require = False
172
- default_db = dj_database_url.config(
173
- env="LAMINDB_DJANGO_DATABASE_URL",
174
- default=instance_db,
175
- # see comment next to patching BaseDatabaseWrapper below
176
- conn_max_age=CONN_MAX_AGE,
177
- conn_health_checks=True,
178
- ssl_require=ssl_require,
179
- )
180
- DATABASES = {
181
- "default": default_db,
182
- }
183
- from .._init_instance import get_schema_module_name
184
-
185
- module_names = ["core"] + list(isettings.modules)
186
- raise_import_error = True if init else False
187
- installed_apps = [
188
- package_name
189
- for name in module_names
190
- if (
191
- package_name := get_schema_module_name(
192
- name, raise_import_error=raise_import_error
193
- )
194
- )
195
- is not None
196
- ]
197
- if view_schema:
198
- installed_apps = installed_apps[::-1] # to fix how apps appear
199
- installed_apps += ["schema_graph", "django.contrib.staticfiles"]
200
-
201
- kwargs = dict(
202
- INSTALLED_APPS=installed_apps,
203
- DATABASES=DATABASES,
204
- DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
205
- TIME_ZONE="UTC",
206
- USE_TZ=True,
207
- )
208
- if view_schema:
209
- kwargs.update(
210
- DEBUG=True,
211
- ROOT_URLCONF="lamindb_setup._schema",
212
- SECRET_KEY="dummy",
213
- TEMPLATES=[
214
- {
215
- "BACKEND": "django.template.backends.django.DjangoTemplates",
216
- "APP_DIRS": True,
217
- },
218
- ],
219
- STATIC_ROOT=f"{Path.home().as_posix()}/.lamin/",
220
- STATICFILES_FINDERS=[
221
- "django.contrib.staticfiles.finders.AppDirectoriesFinder",
222
- ],
223
- STATIC_URL="static/",
224
- )
225
- settings.configure(**kwargs)
226
- django.setup(set_prefix=False)
227
- # https://laminlabs.slack.com/archives/C04FPE8V01W/p1698239551460289
228
- from django.db.backends.base.base import BaseDatabaseWrapper
229
-
230
- BaseDatabaseWrapper.close_if_health_check_failed = close_if_health_check_failed
231
- logger.debug(
232
- "django.db.backends.base.base.BaseDatabaseWrapper.close_if_health_check_failed has been patched"
233
- )
234
-
235
- if isettings._fine_grained_access and isettings._db_permissions == "jwt":
236
- db_token = DBToken(isettings)
237
- db_token_manager.set(db_token) # sets for the default connection
238
-
239
- if configure_only:
240
- return None
241
-
242
- # migrations management
243
- if create_migrations:
244
- call_command("makemigrations")
245
- return None
246
-
247
- if deploy_migrations:
248
- if appname_number is None:
249
- call_command("migrate", verbosity=2)
250
- else:
251
- app_name, app_number = appname_number
252
- call_command("migrate", app_name, app_number, verbosity=2)
253
- isettings._update_cloud_sqlite_file(unlock_cloud_sqlite=False)
254
- elif init:
255
- global IS_MIGRATING
256
- IS_MIGRATING = True
257
- call_command("migrate", verbosity=0)
258
- IS_MIGRATING = False
259
-
260
- global IS_SETUP
261
- IS_SETUP = True
262
-
263
- if isettings.keep_artifacts_local:
264
- isettings._local_storage = isettings._search_local_root()
265
-
266
-
267
- # THIS IS NOT SAFE
268
- # especially if lamindb is imported already
269
- # django.setup fails if called for the second time
270
- # reset_django() allows to call setup again,
271
- # needed to connect to a different instance in the same process if connected already
272
- # there could be problems if models are already imported from lamindb or other modules
273
- # these 'old' models can have any number of problems
274
- def reset_django():
275
- from django.conf import settings
276
- from django.apps import apps
277
- from django.db import connections
278
-
279
- if not settings.configured:
280
- return
281
-
282
- connections.close_all()
283
-
284
- if getattr(settings, "_wrapped", None) is not None:
285
- settings._wrapped = None
286
-
287
- app_names = {"django"} | {app.name for app in apps.get_app_configs()}
288
-
289
- apps.app_configs.clear()
290
- apps.apps_ready = apps.models_ready = apps.ready = apps.loading = False
291
- apps.clear_cache()
292
-
293
- # i suspect it is enough to just drop django and all the apps from sys.modules
294
- # the code above is just a precaution
295
- for module_name in list(sys.modules):
296
- if module_name.partition(".")[0] in app_names:
297
- del sys.modules[module_name]
298
-
299
- il.invalidate_caches()
300
-
301
- global db_token_manager
302
- db_token_manager = DBTokenManager()
303
-
304
- global IS_SETUP
305
- IS_SETUP = False
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
@@ -1 +1 @@
1
- from lamindb_setup.errors import DefaultMessageException # backwards compatibility
1
+ from lamindb_setup.errors import DefaultMessageException # backwards compatibility