lamindb_setup 1.3.0__tar.gz → 1.3.1__tar.gz

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 (103) hide show
  1. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/PKG-INFO +2 -2
  2. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-connect-anonymously-set-token.ipynb +3 -3
  3. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/__init__.py +1 -1
  4. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_connect_instance.py +1 -1
  5. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_init_instance.py +14 -1
  6. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_migrate.py +18 -7
  7. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_register_instance.py +1 -2
  8. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_hub_client.py +34 -0
  9. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_hub_core.py +39 -6
  10. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings_instance.py +5 -1
  11. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings_load.py +3 -0
  12. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings_save.py +1 -0
  13. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings_storage.py +2 -2
  14. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings_store.py +1 -0
  15. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/django.py +6 -1
  16. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/noxfile.py +4 -2
  17. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/pyproject.toml +1 -1
  18. lamindb_setup-1.3.1/tests/hub-local/scripts/script-connect-fine-grained-access.py +18 -0
  19. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-local/test_all.py +48 -4
  20. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_storage_access.py +11 -0
  21. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_storage_stats.py +2 -2
  22. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/.github/workflows/build.yml +0 -0
  23. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/.github/workflows/doc-changes.yml +0 -0
  24. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/.gitignore +0 -0
  25. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/.pre-commit-config.yaml +0 -0
  26. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/LICENSE +0 -0
  27. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/README.md +0 -0
  28. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/changelog.md +0 -0
  29. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
  30. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
  31. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
  32. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  33. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
  34. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
  35. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
  36. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/08-test-multi-session.ipynb +0 -0
  37. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-cloud/test_notebooks.py +0 -0
  38. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-cache-management.ipynb +0 -0
  39. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
  40. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-empty-init.ipynb +0 -0
  41. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-import-schema.ipynb +0 -0
  42. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-init-load-local-anonymously.ipynb +0 -0
  43. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  44. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  45. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  46. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/hub-prod/test_notebooks2.py +0 -0
  47. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/index.md +0 -0
  48. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/notebooks.md +0 -0
  49. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/docs/reference.md +0 -0
  50. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_cache.py +0 -0
  51. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_check.py +0 -0
  52. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_check_setup.py +0 -0
  53. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_close.py +0 -0
  54. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_delete.py +0 -0
  55. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_django.py +0 -0
  56. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_entry_points.py +0 -0
  57. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_exportdb.py +0 -0
  58. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_importdb.py +0 -0
  59. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_schema.py +0 -0
  60. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_schema_metadata.py +0 -0
  61. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_set_managed_storage.py +0 -0
  62. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_setup_user.py +0 -0
  63. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/_silence_loggers.py +0 -0
  64. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/__init__.py +0 -0
  65. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_aws_options.py +0 -0
  66. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_aws_storage.py +0 -0
  67. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_deprecated.py +0 -0
  68. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_docs.py +0 -0
  69. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_hub_crud.py +0 -0
  70. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_hub_utils.py +0 -0
  71. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_private_django_api.py +0 -0
  72. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings.py +0 -0
  73. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_settings_user.py +0 -0
  74. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
  75. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  76. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/exceptions.py +0 -0
  77. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/hashing.py +0 -0
  78. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/types.py +0 -0
  79. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/lamindb_setup/core/upath.py +0 -0
  80. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/scripts/script-init-pass-user-no-writes.py +0 -0
  81. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/scripts/script-to-fail-managed-storage.py +0 -0
  82. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_connect_instance.py +0 -0
  83. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_delete_instance.py +0 -0
  84. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_edge_request.py +0 -0
  85. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_fail_managed_storage.py +0 -0
  86. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_init_instance.py +0 -0
  87. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_init_pass_user_no_writes.py +0 -0
  88. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_login.py +0 -0
  89. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_migrate.py +0 -0
  90. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-cloud/test_set_storage.py +0 -0
  91. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-local/conftest.py +0 -0
  92. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-local/test_update_schema_in_hub.py +0 -0
  93. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-prod/conftest.py +0 -0
  94. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-prod/test_aws_options_manager.py +0 -0
  95. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-prod/test_django.py +0 -0
  96. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-prod/test_global_settings.py +0 -0
  97. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  98. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/hub-prod/test_upath.py +0 -0
  99. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_entry_point.py +0 -0
  100. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_hashing.py +0 -0
  101. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_storage_basis.py +0 -0
  102. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_storage_settings.py +0 -0
  103. {lamindb_setup-1.3.0 → lamindb_setup-1.3.1}/tests/storage/test_to_url.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb_setup
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: lamin_utils>=0.3.3
9
- Requires-Dist: django>=5,<5.2
9
+ Requires-Dist: django>=5.1,<5.2
10
10
  Requires-Dist: dj_database_url>=1.3.0,<3.0.0
11
11
  Requires-Dist: pydantic-settings
12
12
  Requires-Dist: appdirs<2.0.0
@@ -15,7 +15,7 @@
15
15
  "source": [
16
16
  "import pytest\n",
17
17
  "import lamindb_setup as ln_setup\n",
18
- "from lamindb_setup.core.django import set_token\n",
18
+ "from lamindb_setup.core.django import set_db_token\n",
19
19
  "from psycopg2.errors import UndefinedFunction"
20
20
  ]
21
21
  },
@@ -55,10 +55,10 @@
55
55
  "source": [
56
56
  "# this just tests that the function is setup properly\n",
57
57
  "# the actual functionality is tested in lamindb\n",
58
- "set_token(None)\n",
58
+ "set_db_token(None)\n",
59
59
  "# set_token sql function is not defined\n",
60
60
  "with pytest.raises(UndefinedFunction):\n",
61
- " set_token(\"test\")"
61
+ " set_db_token(\"test\")"
62
62
  ]
63
63
  },
64
64
  {
@@ -33,7 +33,7 @@ Modules & settings:
33
33
 
34
34
  """
35
35
 
36
- __version__ = "1.3.0" # denote a release candidate for 0.1.0 with 0.1rc1
36
+ __version__ = "1.3.1" # denote a release candidate for 0.1.0 with 0.1rc1
37
37
 
38
38
  import os
39
39
 
@@ -168,6 +168,7 @@ def _connect_instance(
168
168
  schema_id=None
169
169
  if (schema_id := instance_result["schema_id"]) is None
170
170
  else UUID(schema_id),
171
+ fine_grained_access=instance_result.get("fine_grained_access", False),
171
172
  )
172
173
  else:
173
174
  if hub_result != "anonymous-user":
@@ -205,7 +206,6 @@ def connect(instance: str | None = None, **kwargs) -> str | tuple | None:
205
206
  for kwarg in kwargs:
206
207
  if kwarg not in valid_kwargs:
207
208
  raise TypeError(f"connect() got unexpected keyword argument '{kwarg}'")
208
-
209
209
  isettings: InstanceSettings = None # type: ignore
210
210
  # _db is still needed because it is called in init
211
211
  _db: str | None = kwargs.get("_db", None)
@@ -6,6 +6,7 @@ import uuid
6
6
  from typing import TYPE_CHECKING, Literal
7
7
  from uuid import UUID
8
8
 
9
+ import click
9
10
  from django.core.exceptions import FieldError
10
11
  from django.db.utils import OperationalError, ProgrammingError
11
12
  from lamin_utils import logger
@@ -25,6 +26,11 @@ if TYPE_CHECKING:
25
26
  from .core.types import UPathStr
26
27
 
27
28
 
29
+ class InstanceNotCreated(click.ClickException):
30
+ def show(self, file=None):
31
+ pass
32
+
33
+
28
34
  def get_schema_module_name(module_name, raise_import_error: bool = True) -> str | None:
29
35
  import importlib.util
30
36
 
@@ -242,7 +248,7 @@ def init(
242
248
  raise CannotSwitchDefaultInstance(MESSAGE_CANNOT_SWITCH_DEFAULT_INSTANCE)
243
249
  elif _write_settings:
244
250
  close_instance(mute=True)
245
- from .core._hub_core import init_instance as init_instance_hub
251
+ from .core._hub_core import init_instance_hub
246
252
 
247
253
  name_str, instance_id, instance_state, _ = validate_init_args(
248
254
  storage=storage,
@@ -284,6 +290,13 @@ def init(
284
290
  isettings.is_remote and instance_state != "instance-corrupted-or-deleted"
285
291
  )
286
292
  if register_on_hub:
293
+ # can't register the instance in the hub
294
+ # if storage is not in the hub
295
+ # raise the exception and initiate cleanups
296
+ if not isettings.storage.is_on_hub:
297
+ raise InstanceNotCreated(
298
+ "Unable to create the instance because failed to register the storage."
299
+ )
287
300
  init_instance_hub(
288
301
  isettings, account_id=user__uuid, access_token=access_token
289
302
  )
@@ -23,25 +23,36 @@ def check_whether_migrations_in_sync(db_version_str: str):
23
23
  return None
24
24
  installed_version = version.parse(installed_version_str)
25
25
  db_version = version.parse(db_version_str)
26
- if installed_version.major < db_version.major or (
26
+ if installed_version.major < db_version.major:
27
+ logger.warning(
28
+ f"the database ({db_version_str}) is far ahead of your installed lamindb package ({installed_version_str})"
29
+ )
30
+ logger.important(
31
+ f"please update lamindb: pip install lamindb>={db_version.major}"
32
+ )
33
+ elif (
27
34
  installed_version.major == db_version.major
28
35
  and installed_version.minor < db_version.minor
29
36
  ):
30
37
  db_version_lower = f"{db_version.major}.{db_version.minor}"
31
- db_version_upper = f"{db_version.major}.{db_version.minor + 1}"
32
- logger.warning(
38
+ logger.important(
33
39
  f"the database ({db_version_str}) is ahead of your installed lamindb"
34
40
  f" package ({installed_version_str})"
35
41
  )
36
42
  logger.important(
37
- "please update lamindb: pip install"
38
- f' "lamindb>={db_version_lower},<{db_version_upper}"'
43
+ f"consider updating lamindb: pip install lamindb>={db_version_lower}"
39
44
  )
40
- elif installed_version.major > db_version.major or (
45
+ elif installed_version.major > db_version.major:
46
+ logger.warning(
47
+ f"the database ({db_version_str}) is far behind your installed lamindb package"
48
+ f" ({installed_version_str})"
49
+ )
50
+ logger.important("migrate your database: lamin migrate deploy")
51
+ elif (
41
52
  installed_version.major == db_version.major
42
53
  and installed_version.minor > db_version.minor
43
54
  ):
44
- logger.warning(
55
+ logger.important(
45
56
  f"the database ({db_version_str}) is behind your installed lamindb package"
46
57
  f" ({installed_version_str})"
47
58
  )
@@ -10,8 +10,7 @@ from .core.django import setup_django
10
10
  def register(_test: bool = False):
11
11
  """Register an instance on the hub."""
12
12
  from ._check_setup import _check_instance_setup
13
- from .core._hub_core import init_instance as init_instance_hub
14
- from .core._hub_core import init_storage as init_storage_hub
13
+ from .core._hub_core import init_instance_hub, init_storage_hub
15
14
 
16
15
  logger.warning("""lamin register will be removed soon""")
17
16
 
@@ -180,3 +180,37 @@ def call_with_fallback(
180
180
  except NameError:
181
181
  pass
182
182
  return result
183
+
184
+
185
+ def requests_client():
186
+ # local is used in tests
187
+ if os.environ.get("LAMIN_ENV", "prod") == "local":
188
+ from fastapi.testclient import TestClient
189
+ from laminhub_rest.main import app
190
+
191
+ return TestClient(app)
192
+
193
+ import requests # type: ignore
194
+
195
+ return requests
196
+
197
+
198
+ def request_get_auth(url: str, access_token: str, renew_token: bool = True):
199
+ requests = requests_client()
200
+
201
+ response = requests.get(url, headers={"Authorization": f"Bearer {access_token}"})
202
+ # upate access_token and try again if failed
203
+ if response.status_code != 200 and renew_token:
204
+ from lamindb_setup import settings
205
+
206
+ access_token = get_access_token(
207
+ settings.user.email, settings.user.password, settings.user.api_key
208
+ )
209
+
210
+ settings.user.access_token = access_token
211
+ save_user_settings(settings.user)
212
+
213
+ response = requests.get(
214
+ url, headers={"Authorization": f"Bearer {access_token}"}
215
+ )
216
+ return response
@@ -16,6 +16,7 @@ from ._hub_client import (
16
16
  call_with_fallback,
17
17
  call_with_fallback_auth,
18
18
  connect_hub,
19
+ request_get_auth,
19
20
  )
20
21
  from ._hub_crud import (
21
22
  _delete_instance_record,
@@ -115,7 +116,7 @@ def _select_storage(
115
116
  return True
116
117
 
117
118
 
118
- def init_storage(
119
+ def init_storage_hub(
119
120
  ssettings: StorageSettings,
120
121
  auto_populate_instance: bool = True,
121
122
  created_by: UUID | None = None,
@@ -123,7 +124,7 @@ def init_storage(
123
124
  ) -> Literal["hub-record-retireved", "hub-record-created"]:
124
125
  if settings.user.handle != "anonymous" or access_token is not None:
125
126
  return call_with_fallback_auth(
126
- _init_storage,
127
+ _init_storage_hub,
127
128
  ssettings=ssettings,
128
129
  auto_populate_instance=auto_populate_instance,
129
130
  created_by=created_by,
@@ -139,7 +140,7 @@ def init_storage(
139
140
  raise ValueError("Log in to create a storage location on the hub.")
140
141
 
141
142
 
142
- def _init_storage(
143
+ def _init_storage_hub(
143
144
  client: Client,
144
145
  ssettings: StorageSettings,
145
146
  auto_populate_instance: bool,
@@ -269,20 +270,20 @@ def delete_instance_record(instance_id: UUID, access_token: str | None = None) -
269
270
  )
270
271
 
271
272
 
272
- def init_instance(
273
+ def init_instance_hub(
273
274
  isettings: InstanceSettings,
274
275
  account_id: UUID | None = None,
275
276
  access_token: str | None = None,
276
277
  ) -> None:
277
278
  return call_with_fallback_auth(
278
- _init_instance,
279
+ _init_instance_hub,
279
280
  isettings=isettings,
280
281
  account_id=account_id,
281
282
  access_token=access_token,
282
283
  )
283
284
 
284
285
 
285
- def _init_instance(
286
+ def _init_instance_hub(
286
287
  client: Client, isettings: InstanceSettings, account_id: UUID | None = None
287
288
  ) -> None:
288
289
  from ._settings import settings
@@ -437,6 +438,38 @@ def _access_aws(*, storage_root: str, client: Client) -> dict[str, dict]:
437
438
  return storage_root_info
438
439
 
439
440
 
441
+ def access_db(isettings: InstanceSettings, access_token: str | None = None) -> str:
442
+ if access_token is None:
443
+ if settings.user.handle == "anonymous":
444
+ raise RuntimeError(
445
+ f"Can only get fine-grained access to {isettings.slug} if authenticated."
446
+ )
447
+ else:
448
+ access_token = settings.user.access_token
449
+ renew_token = True
450
+ else:
451
+ renew_token = False
452
+ # local is used in tests
453
+ url = f"/access_v2/instances/{isettings._id}/db_token"
454
+ if os.environ.get("LAMIN_ENV", "prod") != "local":
455
+ api_url = isettings._api_url
456
+ if api_url is None:
457
+ raise RuntimeError(
458
+ f"Can only get fine-grained access to {isettings.slug} if api_url is present."
459
+ )
460
+ url = api_url + url
461
+
462
+ response = request_get_auth(url, access_token, renew_token) # type: ignore
463
+ response_json = response.json()
464
+ if response.status_code != 200:
465
+ raise PermissionError(
466
+ f"Fine-grained access to {isettings.slug} failed: {response_json}"
467
+ )
468
+ if "token" not in response_json:
469
+ raise RuntimeError("The response of access_db does not contain a db token.")
470
+ return response_json["token"]
471
+
472
+
440
473
  def get_lamin_site_base_url():
441
474
  if "LAMIN_ENV" in os.environ:
442
475
  if os.environ["LAMIN_ENV"] == "local":
@@ -59,6 +59,7 @@ class InstanceSettings:
59
59
  is_on_hub: bool | None = None, # initialized from hub
60
60
  api_url: str | None = None,
61
61
  schema_id: UUID | None = None,
62
+ fine_grained_access: bool = False,
62
63
  _locker_user: UserSettings | None = None, # user to lock for if cloud sqlite
63
64
  ):
64
65
  from ._hub_utils import validate_db_arg
@@ -76,9 +77,12 @@ class InstanceSettings:
76
77
  self._keep_artifacts_local = keep_artifacts_local
77
78
  self._storage_local: StorageSettings | None = None
78
79
  self._is_on_hub = is_on_hub
79
- # private, needed for writing instance settings
80
+ # private, needed for api requests
80
81
  self._api_url = api_url
81
82
  self._schema_id = schema_id
83
+ # private, whether fine grained access is used
84
+ # needed to be set to request jwt etc
85
+ self._fine_grained_access = fine_grained_access
82
86
  # if None then settings.user is used
83
87
  self._locker_user = _locker_user
84
88
 
@@ -101,6 +101,9 @@ def setup_instance_from_store(store: InstanceSettingsStore) -> InstanceSettings:
101
101
  modules=_null_to_value(store.schema_str),
102
102
  git_repo=_null_to_value(store.git_repo),
103
103
  keep_artifacts_local=store.keep_artifacts_local, # type: ignore
104
+ api_url=_null_to_value(store.api_url),
105
+ schema_id=None if store.schema_id == "null" else UUID(store.schema_id),
106
+ fine_grained_access=store.fine_grained_access,
104
107
  )
105
108
 
106
109
 
@@ -60,6 +60,7 @@ def save_settings(
60
60
  "id",
61
61
  "api_url",
62
62
  "schema_id",
63
+ "fine_grained_access",
63
64
  }:
64
65
  settings_key = f"_{store_key.rstrip('_')}"
65
66
  else:
@@ -150,8 +150,7 @@ def init_storage(
150
150
  # the below might update the uid with one that's already taken on the hub
151
151
  if not prevent_register_hub:
152
152
  if ssettings.type_is_cloud or register_hub:
153
- from ._hub_core import delete_storage_record
154
- from ._hub_core import init_storage as init_storage_hub
153
+ from ._hub_core import delete_storage_record, init_storage_hub
155
154
 
156
155
  hub_record_status = init_storage_hub(
157
156
  ssettings,
@@ -175,6 +174,7 @@ def init_storage(
175
174
  # only newly created
176
175
  if hub_record_status == "hub-record-created" and ssettings._uuid is not None:
177
176
  delete_storage_record(ssettings._uuid, access_token=access_token) # type: ignore
177
+ ssettings._uuid_ = None
178
178
  hub_record_status = "hub-record-not-created"
179
179
  ssettings._instance_id = None
180
180
  return ssettings, hub_record_status
@@ -57,6 +57,7 @@ class InstanceSettingsStore(BaseSettings):
57
57
  db: Optional[str] # doesn't like new types on 3.9 even with future annotations
58
58
  schema_str: Optional[str]
59
59
  schema_id: Optional[str] = None
60
+ fine_grained_access: bool = False
60
61
  id: str
61
62
  git_repo: Optional[str]
62
63
  keep_artifacts_local: Optional[bool]
@@ -14,7 +14,7 @@ IS_MIGRATING = False
14
14
  CONN_MAX_AGE = 299
15
15
 
16
16
 
17
- def set_token(token: str | None, connection_name: str = "default"):
17
+ def set_db_token(token: str | None, connection_name: str = "default"):
18
18
  # None to reset
19
19
  from django.db import connections
20
20
  from django.db.backends.base.base import BaseDatabaseWrapper
@@ -125,6 +125,11 @@ def setup_django(
125
125
 
126
126
  BaseDatabaseWrapper.close_if_health_check_failed = close_if_health_check_failed
127
127
 
128
+ if isettings._fine_grained_access:
129
+ from ._hub_core import access_db
130
+
131
+ set_db_token(access_db(isettings))
132
+
128
133
  if configure_only:
129
134
  return None
130
135
 
@@ -52,7 +52,9 @@ uv pip install --system git+https://github.com/laminlabs/bionty
52
52
 
53
53
  # above downgrades django, I don't understand why, try this
54
54
  if group == "hub-local":
55
- cmds += "\nuv pip install --system -e ./laminhub/rest-hub line_profiler"
55
+ cmds += (
56
+ "\nuv pip install --system -e ./laminhub/rest-hub line_profiler sentry_sdk"
57
+ )
56
58
 
57
59
  run(session, "uv pip install --system pandera") # needed to import lamindb
58
60
  [run(session, line) for line in cmds.splitlines()]
@@ -85,7 +87,7 @@ def hub_local(session: nox.Session):
85
87
  # the -n 1 is to ensure that supabase thread exits properly
86
88
  run(
87
89
  session,
88
- f"pytest -n 1 {COVERAGE_ARGS} ./tests/hub-local",
90
+ f"pytest {COVERAGE_ARGS} ./tests/hub-local",
89
91
  env=os.environ,
90
92
  )
91
93
 
@@ -11,7 +11,7 @@ dynamic = ["version", "description"]
11
11
  dependencies = [
12
12
  "lamin_utils>=0.3.3",
13
13
  # External dependencies
14
- "django>=5,<5.2",
14
+ "django>=5.1,<5.2",
15
15
  "dj_database_url>=1.3.0,<3.0.0",
16
16
  "pydantic-settings",
17
17
  "appdirs<2.0.0",
@@ -0,0 +1,18 @@
1
+ import os
2
+
3
+ import lamindb_setup as ln_setup
4
+ import pytest
5
+ from lamindb_setup.core._hub_core import access_db
6
+
7
+ assert os.environ["LAMIN_ENV"] == "local"
8
+
9
+ ln_setup.connect("instance_access_v2")
10
+
11
+ assert ln_setup.settings.instance._fine_grained_access
12
+
13
+ # check calling access_db with anonymous user
14
+ ln_setup.settings.user.handle = "anonymous"
15
+ with pytest.raises(RuntimeError):
16
+ access_db(ln_setup.settings.instance)
17
+ # check with providing access_token explicitly
18
+ access_db(ln_setup.settings.instance, ln_setup.settings.user.access_token)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ import subprocess
4
5
  from uuid import UUID, uuid4
5
6
 
6
7
  import lamindb_setup as ln_setup
@@ -13,9 +14,10 @@ from lamindb_setup.core._hub_client import (
13
14
  )
14
15
  from lamindb_setup.core._hub_core import (
15
16
  _connect_instance_hub,
17
+ access_db,
16
18
  connect_instance_hub,
17
- init_instance,
18
- init_storage,
19
+ init_instance_hub,
20
+ init_storage_hub,
19
21
  sign_in_hub,
20
22
  sign_up_local_hub,
21
23
  )
@@ -35,8 +37,14 @@ from lamindb_setup.core._settings_instance import InstanceSettings
35
37
  from lamindb_setup.core._settings_save import save_user_settings
36
38
  from lamindb_setup.core._settings_storage import base62
37
39
  from lamindb_setup.core._settings_storage import init_storage as init_storage_base
40
+ from lamindb_setup.core._settings_store import instance_settings_file
38
41
  from lamindb_setup.core._settings_user import UserSettings
42
+ from laminhub_rest.core.access_v2 import OrganizationHandler
39
43
  from laminhub_rest.core.instance.collaborator import InstanceCollaboratorHandler
44
+ from laminhub_rest.test.instance.utils import (
45
+ create_hosted_test_instance,
46
+ delete_hosted_test_instance,
47
+ )
40
48
  from postgrest.exceptions import APIError
41
49
  from supafunc.errors import FunctionsHttpError
42
50
 
@@ -127,7 +135,7 @@ def create_myinstance(create_testadmin1_session): # -> Dict
127
135
  )[0],
128
136
  db=db_str,
129
137
  )
130
- init_instance(isettings)
138
+ init_instance_hub(isettings)
131
139
  # test loading it
132
140
  with pytest.raises(PermissionError) as error:
133
141
  ln_setup.connect("testadmin1/myinstance", _test=True)
@@ -159,6 +167,18 @@ def create_myinstance(create_testadmin1_session): # -> Dict
159
167
  yield instance
160
168
 
161
169
 
170
+ @pytest.fixture(scope="session")
171
+ def create_instance_fine_grained_access(create_testadmin1_session):
172
+ client, testadmin1 = create_testadmin1_session
173
+
174
+ org_handler = OrganizationHandler(client)
175
+ org_handler.create(testadmin1._uuid)
176
+
177
+ instance = create_hosted_test_instance("instance_access_v2", access_v2=True)
178
+ yield instance
179
+ delete_hosted_test_instance(instance)
180
+
181
+
162
182
  def test_connection_string_decomp(create_myinstance, create_testadmin1_session):
163
183
  client, _ = create_testadmin1_session
164
184
  assert create_myinstance["db_scheme"] == "postgresql"
@@ -357,7 +377,7 @@ def test_init_storage_with_non_existing_bucket(create_testadmin1_session):
357
377
  from botocore.exceptions import ClientError
358
378
 
359
379
  with pytest.raises(ClientError) as error:
360
- init_storage(
380
+ init_storage_hub(
361
381
  ssettings=init_storage_base(
362
382
  "s3://non_existing_storage_root", instance_id=uuid4()
363
383
  )[0]
@@ -369,3 +389,27 @@ def test_init_storage_incorrect_protocol():
369
389
  with pytest.raises(ValueError) as error:
370
390
  init_storage_base("incorrect-protocol://some-path/some-path-level")
371
391
  assert "Protocol incorrect-protocol is not supported" in error.exconly()
392
+
393
+
394
+ def test_fine_grained_access(create_instance_fine_grained_access):
395
+ instance = create_instance_fine_grained_access
396
+
397
+ isettings_file = instance_settings_file(
398
+ instance.name, ln_setup.settings.user.handle
399
+ )
400
+ # the file is written by create_instance_fine_grained_access
401
+ # has fine_grained_access=False because create_instance_fine_grained_access
402
+ # updates the instance after init without updating the settings file
403
+ assert isettings_file.exists()
404
+ # need to delete it, because otheriwse
405
+ # isettings.is_remote evaluates to False
406
+ # and ln_setup.connect doesn't make a hub request
407
+ # thus fine_grained_access stays False
408
+ isettings_file.unlink()
409
+ # run from a script because test_update_schema_in_hub.py has ln_setup.init
410
+ # which fails if we connect here
411
+ subprocess.run(
412
+ "python ./tests/hub-local/scripts/script-connect-fine-grained-access.py",
413
+ shell=True,
414
+ check=True,
415
+ )
@@ -5,6 +5,7 @@ from uuid import UUID
5
5
 
6
6
  import lamindb_setup as ln_setup
7
7
  import pytest
8
+ from lamindb_setup._init_instance import InstanceNotCreated
8
9
  from lamindb_setup.core._hub_client import connect_hub_with_auth
9
10
  from lamindb_setup.core._hub_crud import (
10
11
  select_account_by_handle,
@@ -33,6 +34,16 @@ def test_connect_instance_with_private_storage_and_no_storage_access():
33
34
  path.fs.call_s3("head_bucket", Bucket=path.drive)
34
35
 
35
36
 
37
+ def test_init_instance_with_private_storage_and_no_storage_access():
38
+ ln_setup.login("testuser1@lamin.ai")
39
+ # test creating with no access to a cloud storage
40
+ with pytest.raises(InstanceNotCreated):
41
+ ln_setup.init(
42
+ storage="s3://lndb-setup-ci-eu-central-1/surely-nothing-here",
43
+ _test=True,
44
+ )
45
+
46
+
36
47
  def test_connect_instance_with_public_storage():
37
48
  # this loads a persistent instance created with a public s3 bucket
38
49
  # with s3:GetObject and s3:ListBucket policies enabled for all
@@ -41,9 +41,9 @@ def test_get_stat_file_cloud_http():
41
41
  string_path = "https://raw.githubusercontent.com/laminlabs/lamindb-setup/refs/heads/main/README.md"
42
42
  path = UPath(string_path)
43
43
  size, hash, hash_type = get_stat_file_cloud(path.stat().as_info())
44
- assert hash == "jGjepGeCASc6lx1OI23yKw"
44
+ assert hash == "h3XDCTkMambmtSVpTQetLQ"
45
45
  assert hash_type == "md5-etag"
46
- assert size == 265
46
+ assert size == 272
47
47
 
48
48
 
49
49
  def test_get_stat_dir_cloud_aws():
File without changes
File without changes
File without changes