lamindb_setup 1.8.1__tar.gz → 1.8.3__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 (106) hide show
  1. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/PKG-INFO +1 -1
  2. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/06-connect-hosted-instance.ipynb +3 -2
  3. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/07-keep-artifacts-local.ipynb +4 -5
  4. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-cloud-sync.ipynb +30 -7
  5. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/__init__.py +1 -1
  6. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_check_setup.py +3 -0
  7. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_connect_instance.py +20 -1
  8. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_aws_options.py +1 -5
  9. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_client.py +24 -9
  10. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_core.py +9 -7
  11. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings.py +1 -1
  12. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_instance.py +42 -15
  13. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_storage.py +7 -45
  14. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_user.py +1 -1
  15. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/django.py +1 -1
  16. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/upath.py +181 -137
  17. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/scripts/script-connect-fine-grained-access.py +2 -2
  18. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_stats.py +7 -0
  19. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_to_url.py +7 -6
  20. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.github/workflows/build.yml +0 -0
  21. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.github/workflows/doc-changes.yml +0 -0
  22. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.gitignore +0 -0
  23. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.pre-commit-config.yaml +0 -0
  24. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/LICENSE +0 -0
  25. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/README.md +0 -0
  26. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/changelog.md +0 -0
  27. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
  28. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
  29. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
  30. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  31. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
  32. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/08-test-multi-session.ipynb +0 -0
  33. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/test_notebooks.py +0 -0
  34. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-cache-management.ipynb +0 -0
  35. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
  36. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-empty-init.ipynb +0 -0
  37. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-import-schema.ipynb +0 -0
  38. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-init-load-local-anonymously.ipynb +0 -0
  39. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  40. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  41. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  42. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test_notebooks2.py +0 -0
  43. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/index.md +0 -0
  44. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/notebooks.md +0 -0
  45. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/reference.md +0 -0
  46. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_cache.py +0 -0
  47. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_check.py +0 -0
  48. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_delete.py +0 -0
  49. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_disconnect.py +0 -0
  50. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_django.py +0 -0
  51. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_entry_points.py +0 -0
  52. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_exportdb.py +0 -0
  53. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_importdb.py +0 -0
  54. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_init_instance.py +0 -0
  55. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_migrate.py +0 -0
  56. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_register_instance.py +0 -0
  57. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_schema.py +0 -0
  58. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_schema_metadata.py +0 -0
  59. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_set_managed_storage.py +0 -0
  60. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_setup_user.py +0 -0
  61. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_silence_loggers.py +0 -0
  62. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/__init__.py +0 -0
  63. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_aws_storage.py +0 -0
  64. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_deprecated.py +0 -0
  65. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_docs.py +0 -0
  66. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_crud.py +0 -0
  67. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_utils.py +0 -0
  68. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_private_django_api.py +0 -0
  69. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_load.py +0 -0
  70. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_save.py +0 -0
  71. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_store.py +0 -0
  72. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
  73. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  74. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/exceptions.py +0 -0
  75. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/hashing.py +0 -0
  76. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/types.py +0 -0
  77. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/errors.py +0 -0
  78. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/py.typed +0 -0
  79. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/types.py +0 -0
  80. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/noxfile.py +0 -0
  81. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/pyproject.toml +0 -0
  82. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/scripts/script-init-pass-user-no-writes.py +0 -0
  83. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/scripts/script-to-fail-managed-storage.py +0 -0
  84. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_connect_instance.py +0 -0
  85. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_delete_instance.py +0 -0
  86. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_edge_request.py +0 -0
  87. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_fail_managed_storage.py +0 -0
  88. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_init_instance.py +0 -0
  89. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_init_pass_user_no_writes.py +0 -0
  90. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_login.py +0 -0
  91. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_migrate.py +0 -0
  92. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_set_storage.py +0 -0
  93. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/conftest.py +0 -0
  94. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/test_all.py +0 -0
  95. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/test_update_schema_in_hub.py +0 -0
  96. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/conftest.py +0 -0
  97. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_aws_options_manager.py +0 -0
  98. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_django.py +0 -0
  99. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_global_settings.py +0 -0
  100. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  101. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_upath.py +0 -0
  102. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_entry_point.py +0 -0
  103. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_hashing.py +0 -0
  104. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_access.py +0 -0
  105. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_basis.py +0 -0
  106. {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb_setup
3
- Version: 1.8.1
3
+ Version: 1.8.3
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10
@@ -16,6 +16,7 @@
16
16
  "import pytest\n",
17
17
  "import shutil\n",
18
18
  "import lamindb_setup as ln_setup\n",
19
+ "from lamindb_setup._connect_instance import _connect_cli\n",
19
20
  "from lamindb_setup.core.upath import UPath\n",
20
21
  "from lamindb_setup.core._hub_core import delete_instance\n",
21
22
  "from lamindb_setup.core._hub_client import connect_hub_with_auth\n",
@@ -31,7 +32,7 @@
31
32
  "metadata": {},
32
33
  "outputs": [],
33
34
  "source": [
34
- "ln_setup.connect(f\"testuser1/{instance_name}\")"
35
+ "_connect_cli(f\"testuser1/{instance_name}\") # cover here for cloud sqlite"
35
36
  ]
36
37
  },
37
38
  {
@@ -223,7 +224,7 @@
223
224
  "name": "python",
224
225
  "nbconvert_exporter": "python",
225
226
  "pygments_lexer": "ipython3",
226
- "version": "3.9.17"
227
+ "version": "3.10.16"
227
228
  }
228
229
  },
229
230
  "nbformat": 4,
@@ -48,7 +48,7 @@
48
48
  " ln_setup.settings.instance.local_storage\n",
49
49
  "assert (\n",
50
50
  " error.exconly()\n",
51
- " == \"ValueError: `keep_artifacts_local` is not enabled for this instance.\"\n",
51
+ " == \"ValueError: `keep_artifacts_local` is False, switch via: ln.setup.settings.instance.keep_artifacts_local = True\"\n",
52
52
  ")"
53
53
  ]
54
54
  },
@@ -58,12 +58,11 @@
58
58
  "metadata": {},
59
59
  "outputs": [],
60
60
  "source": [
61
- "ln_setup.settings.instance._keep_artifacts_local = True\n",
61
+ "ln_setup.settings.instance.keep_artifacts_local = True\n",
62
62
  "with pytest.raises(ValueError) as error:\n",
63
63
  " ln_setup.settings.instance.local_storage\n",
64
- "assert (\n",
65
- " error.exconly()\n",
66
- " == \"ValueError: No storage location found in current environment: create one via, e.g., ln.Storage(root='/dir/our_shared_dir', host='our-server-123').save()\"\n",
64
+ "assert error.exconly().startswith(\n",
65
+ " \"ValueError: No local storage location found in current environment:\"\n",
67
66
  ")"
68
67
  ]
69
68
  },
@@ -229,7 +229,7 @@
229
229
  ")\n",
230
230
  "assert dir_sync_local == settings.cache_dir / \"dir_cache/key\"\n",
231
231
  "\n",
232
- "assert dir_sync.synchronize(dir_sync_local, just_check=True)\n",
232
+ "assert dir_sync.synchronize_to(dir_sync_local, just_check=True)\n",
233
233
  "assert not dir_sync_local.exists()"
234
234
  ]
235
235
  },
@@ -240,7 +240,7 @@
240
240
  "metadata": {},
241
241
  "outputs": [],
242
242
  "source": [
243
- "assert dir_sync.synchronize(dir_sync_local, just_check=False)\n",
243
+ "assert dir_sync.synchronize_to(dir_sync_local, just_check=False)\n",
244
244
  "assert dir_sync_local.is_dir()\n",
245
245
  "assert num_files(dir_sync_local) == 2"
246
246
  ]
@@ -308,6 +308,28 @@
308
308
  "http_local.unlink()"
309
309
  ]
310
310
  },
311
+ {
312
+ "cell_type": "code",
313
+ "execution_count": null,
314
+ "id": "7e610d67",
315
+ "metadata": {},
316
+ "outputs": [],
317
+ "source": [
318
+ "# test download_to\n",
319
+ "http_path.download_to(http_local, print_progress=True)\n",
320
+ "assert not http_path.synchronize_to(http_local)"
321
+ ]
322
+ },
323
+ {
324
+ "cell_type": "code",
325
+ "execution_count": null,
326
+ "id": "1e293d0a",
327
+ "metadata": {},
328
+ "outputs": [],
329
+ "source": [
330
+ "http_local.unlink()"
331
+ ]
332
+ },
311
333
  {
312
334
  "cell_type": "markdown",
313
335
  "id": "574c3f95",
@@ -323,7 +345,8 @@
323
345
  "metadata": {},
324
346
  "outputs": [],
325
347
  "source": [
326
- "dir_sync_local = settings.paths.cloud_to_local(dir_sync)"
348
+ "dir_sync_local = settings.paths.cloud_to_local(dir_sync)\n",
349
+ "assert not dir_sync.synchronize_to(dir_sync_local)"
327
350
  ]
328
351
  },
329
352
  {
@@ -459,7 +482,7 @@
459
482
  "source": [
460
483
  "dir_sync_local = settings.paths.cloud_to_local(dir_sync)\n",
461
484
  "\n",
462
- "assert num_files(dir_sync_local) == 2\n",
485
+ "assert num_files(dir_sync_local) == 2, list(dir_sync_local.rglob(\"*\"))\n",
463
486
  "assert not local_file_new.exists()\n",
464
487
  "assert not local_file_new_parent.exists()\n",
465
488
  "\n",
@@ -759,7 +782,7 @@
759
782
  "metadata": {},
760
783
  "outputs": [],
761
784
  "source": [
762
- "assert hf_path.synchronize(hf_path_local, just_check=True)\n",
785
+ "assert hf_path.synchronize_to(hf_path_local, just_check=True)\n",
763
786
  "assert not hf_path_local.exists()"
764
787
  ]
765
788
  },
@@ -770,7 +793,7 @@
770
793
  "metadata": {},
771
794
  "outputs": [],
772
795
  "source": [
773
- "assert hf_path.synchronize(hf_path_local)\n",
796
+ "assert hf_path.synchronize_to(hf_path_local)\n",
774
797
  "assert hf_path_local.is_file()"
775
798
  ]
776
799
  },
@@ -793,7 +816,7 @@
793
816
  "source": [
794
817
  "hf_path = UPath(\"hf://datasets/Koncopd/lamindb-test@main/does_not_exist.file\")\n",
795
818
  "with pytest.raises(FileNotFoundError):\n",
796
- " hf_path.synchronize(UPath(\"./does_not_exist.file\"), error_no_origin=True)"
819
+ " hf_path.synchronize_to(UPath(\"./does_not_exist.file\"), error_no_origin=True)"
797
820
  ]
798
821
  },
799
822
  {
@@ -35,7 +35,7 @@ Modules & settings:
35
35
 
36
36
  """
37
37
 
38
- __version__ = "1.8.1" # denote a release candidate for 0.1.0 with 0.1rc1
38
+ __version__ = "1.8.3" # denote a release candidate for 0.1.0 with 0.1rc1
39
39
 
40
40
  import os
41
41
 
@@ -147,6 +147,9 @@ def _check_instance_setup(from_module: str | None = None) -> bool:
147
147
  else:
148
148
  django_lamin.setup_django(isettings)
149
149
  logger.important(f"connected lamindb: {isettings.slug}")
150
+ settings._instance_settings = (
151
+ isettings # update of local storage location
152
+ )
150
153
  return django_lamin.IS_SETUP
151
154
  else:
152
155
  if from_module is not None and settings.auto_connect:
@@ -188,13 +188,32 @@ def _connect_instance(
188
188
  return isettings
189
189
 
190
190
 
191
+ def _connect_cli(instance: str) -> None:
192
+ from lamindb_setup import settings as settings_
193
+
194
+ settings_.auto_connect = True
195
+ owner, name = get_owner_name_from_identifier(instance)
196
+ isettings = _connect_instance(owner, name)
197
+ isettings._persist(write_to_disk=True)
198
+ if not isettings.is_on_hub or isettings._is_cloud_sqlite:
199
+ # there are two reasons to call the full-blown connect
200
+ # (1) if the instance is not on the hub, we need to register
201
+ # potential users through register_user()
202
+ # (2) if the instance is cloud sqlite, we need to lock it
203
+ connect(_write_settings=False, _reload_lamindb=False)
204
+ else:
205
+ logger.important(f"connected lamindb: {isettings.slug}")
206
+ return None
207
+
208
+
191
209
  @unlock_cloud_sqlite_upon_exception(ignore_prev_locker=True)
192
210
  def connect(instance: str | None = None, **kwargs: Any) -> str | tuple | None:
193
211
  """Connect to an instance.
194
212
 
195
213
  Args:
196
214
  instance: Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
197
- If `None`, looks for an environment variable `LAMIN_CURRENT_INSTANCE` to get the instance identifier. If it doesn't find this variable, it connects to the instance that was connected with `lamin connect` through the CLI.
215
+ If `None`, looks for an environment variable `LAMIN_CURRENT_INSTANCE` to get the instance identifier.
216
+ If it doesn't find this variable, it connects to the instance that was connected with `lamin connect` through the CLI.
198
217
  """
199
218
  # validate kwargs
200
219
  valid_kwargs = {
@@ -172,11 +172,7 @@ class AWSOptionsManager:
172
172
  from ._hub_core import access_aws
173
173
  from ._settings import settings
174
174
 
175
- if settings.user.handle != "anonymous" or access_token is not None:
176
- storage_root_info = access_aws(path_str, access_token=access_token)
177
- else:
178
- storage_root_info = {"credentials": {}, "accessibility": {}}
179
-
175
+ storage_root_info = access_aws(path_str, access_token=access_token)
180
176
  accessibility = storage_root_info["accessibility"]
181
177
  is_managed = accessibility.get("is_managed", False)
182
178
  if is_managed:
@@ -5,6 +5,7 @@ import os
5
5
  from typing import Literal
6
6
  from urllib.request import urlretrieve
7
7
 
8
+ from httpx import HTTPTransport
8
9
  from lamin_utils import logger
9
10
  from pydantic_settings import BaseSettings
10
11
  from supabase import Client, create_client # type: ignore
@@ -60,20 +61,29 @@ class Environment:
60
61
  self.supabase_anon_key: str = key
61
62
 
62
63
 
64
+ DEFAULT_TIMEOUT = 20
65
+
66
+
63
67
  # runs ~0.5s
64
68
  def connect_hub(
65
69
  fallback_env: bool = False, client_options: ClientOptions | None = None
66
70
  ) -> Client:
67
71
  env = Environment(fallback=fallback_env)
68
72
  if client_options is None:
69
- # function_client_timeout=5 by default
70
- # increase to avoid rare timeouts for edge functions
71
73
  client_options = ClientOptions(
72
74
  auto_refresh_token=False,
73
- function_client_timeout=30,
74
- postgrest_client_timeout=20,
75
+ function_client_timeout=DEFAULT_TIMEOUT,
76
+ postgrest_client_timeout=DEFAULT_TIMEOUT,
75
77
  )
76
- return create_client(env.supabase_api_url, env.supabase_anon_key, client_options)
78
+ client = create_client(env.supabase_api_url, env.supabase_anon_key, client_options)
79
+ # needed to enable retries for http requests in supabase
80
+ # these are separate clients and need separate transports
81
+ # retries are done only in case an httpx.ConnectError or an httpx.ConnectTimeout occurs
82
+ transport_kwargs = {"verify": True, "http2": True, "retries": 2}
83
+ client.auth._http_client._transport = HTTPTransport(**transport_kwargs)
84
+ client.functions._client._transport = HTTPTransport(**transport_kwargs)
85
+ client.postgrest.session._transport = HTTPTransport(**transport_kwargs)
86
+ return client
77
87
 
78
88
 
79
89
  def connect_hub_with_auth(
@@ -210,11 +220,16 @@ def request_with_auth(
210
220
  headers["Authorization"] = f"Bearer {access_token}"
211
221
 
212
222
  make_request = getattr(requests, method)
213
- response = make_request(url, headers=headers, **kwargs)
214
- # upate access_token and try again if failed
215
- if response.status_code != 200 and renew_token:
223
+ timeout = kwargs.pop("timeout", DEFAULT_TIMEOUT)
224
+
225
+ response = make_request(url, headers=headers, timeout=timeout, **kwargs)
226
+ status_code = response.status_code
227
+ # update access_token and try again if failed
228
+ if not (200 <= status_code < 300) and renew_token:
216
229
  from lamindb_setup import settings
217
230
 
231
+ logger.debug(f"{method} {url} failed: {status_code} {response.text}")
232
+
218
233
  access_token = get_access_token(
219
234
  settings.user.email, settings.user.password, settings.user.api_key
220
235
  )
@@ -224,5 +239,5 @@ def request_with_auth(
224
239
 
225
240
  headers["Authorization"] = f"Bearer {access_token}"
226
241
 
227
- response = make_request(url, headers=headers, **kwargs)
242
+ response = make_request(url, headers=headers, timeout=timeout, **kwargs)
228
243
  return response
@@ -460,9 +460,9 @@ def access_aws(storage_root: str, access_token: str | None = None) -> dict[str,
460
460
  storage_root_info = call_with_fallback_auth(
461
461
  _access_aws, storage_root=storage_root, access_token=access_token
462
462
  )
463
- return storage_root_info
464
463
  else:
465
- raise RuntimeError("Can only get access to AWS if authenticated.")
464
+ storage_root_info = call_with_fallback(_access_aws, storage_root=storage_root)
465
+ return storage_root_info
466
466
 
467
467
 
468
468
  def _access_aws(*, storage_root: str, client: Client) -> dict[str, dict]:
@@ -497,8 +497,8 @@ def access_db(
497
497
  instance_slug: str
498
498
  instance_api_url: str | None
499
499
  if (
500
- "LAMIN_TEST_DB_TOKEN" in os.environ
501
- and (env_db_token := os.environ["LAMIN_TEST_DB_TOKEN"]) != ""
500
+ "LAMIN_DB_TOKEN" in os.environ
501
+ and (env_db_token := os.environ["LAMIN_DB_TOKEN"]) != ""
502
502
  ):
503
503
  return env_db_token
504
504
 
@@ -531,11 +531,13 @@ def access_db(
531
531
  url = instance_api_url + url
532
532
 
533
533
  response = request_with_auth(url, "get", access_token, renew_token) # type: ignore
534
- response_json = response.json()
535
- if response.status_code != 200:
534
+ status_code = response.status_code
535
+ if not (200 <= status_code < 300):
536
536
  raise PermissionError(
537
- f"Fine-grained access to {instance_slug} failed: {response_json}"
537
+ f"Fine-grained access to {instance_slug} failed: {status_code} {response.text}"
538
538
  )
539
+
540
+ response_json = response.json()
539
541
  if "token" not in response_json:
540
542
  raise RuntimeError("The response of access_db does not contain a db token.")
541
543
  return response_json["token"]
@@ -350,7 +350,7 @@ class SetupPaths:
350
350
  local_filepath = SetupPaths.cloud_to_local_no_update(filepath, cache_key)
351
351
  if not isinstance(filepath, LocalPathClasses):
352
352
  local_filepath.parent.mkdir(parents=True, exist_ok=True)
353
- filepath.synchronize(local_filepath, **kwargs) # type: ignore
353
+ filepath.synchronize_to(local_filepath, **kwargs) # type: ignore
354
354
  return local_filepath
355
355
 
356
356
 
@@ -32,7 +32,7 @@ if TYPE_CHECKING:
32
32
 
33
33
  from ._settings_user import UserSettings
34
34
 
35
- LOCAL_STORAGE_MESSAGE = "No storage location found in current environment: create one via, e.g., ln.Storage(root='/dir/our_shared_dir', host='our-server-123').save()"
35
+ LOCAL_STORAGE_MESSAGE = "No local storage location found in current environment: defaulting to cloud storage"
36
36
 
37
37
 
38
38
  def sanitize_git_repo_url(repo_url: str) -> str:
@@ -104,8 +104,20 @@ class InstanceSettings:
104
104
  for attr in attrs:
105
105
  value = getattr(self, attr)
106
106
  if attr == "storage":
107
- representation += f"\n - storage root: {value.root_as_str}"
108
- representation += f"\n - storage region: {value.region}"
107
+ if self.keep_artifacts_local:
108
+ import lamindb as ln
109
+
110
+ self._local_storage = ln.setup.settings.instance._local_storage
111
+ if self._local_storage is not None:
112
+ value_local = self.local_storage
113
+ representation += f"\n - local storage: {value_local.root_as_str} ({value_local.region})"
114
+ representation += (
115
+ f"\n - cloud storage: {value.root_as_str} ({value.region})"
116
+ )
117
+ else:
118
+ representation += (
119
+ f"\n - storage: {value.root_as_str} ({value.region})"
120
+ )
109
121
  elif attr == "db":
110
122
  if self.dialect != "sqlite":
111
123
  model = LaminDsnModel(db=value)
@@ -156,9 +168,17 @@ class InstanceSettings:
156
168
  found = []
157
169
  for record in all_local_records:
158
170
  root_path = Path(record.root)
159
- if root_path.exists():
171
+ try:
172
+ root_path_exists = root_path.exists()
173
+ except PermissionError:
174
+ continue
175
+ if root_path_exists:
160
176
  marker_path = root_path / STORAGE_UID_FILE_KEY
161
- if not marker_path.exists():
177
+ try:
178
+ marker_path_exists = marker_path.exists()
179
+ except PermissionError:
180
+ continue
181
+ if not marker_path_exists:
162
182
  legacy_filepath = root_path / LEGACY_STORAGE_UID_FILE_KEY
163
183
  if legacy_filepath.exists():
164
184
  logger.warning(
@@ -182,8 +202,9 @@ class InstanceSettings:
182
202
  if len(found) > 1:
183
203
  found_display = "\n - ".join([f"{record.root}" for record in found])
184
204
  logger.important(f"found locations:\n - {found_display}")
205
+ record = found[0]
185
206
  logger.important(f"defaulting to local storage: {record.root}")
186
- return StorageSettings(record.root)
207
+ return StorageSettings(record.root, region=record.region)
187
208
  elif not mute_warning:
188
209
  start = LOCAL_STORAGE_MESSAGE[0].lower()
189
210
  logger.warning(f"{start}{LOCAL_STORAGE_MESSAGE[1:]}")
@@ -193,15 +214,19 @@ class InstanceSettings:
193
214
  def keep_artifacts_local(self) -> bool:
194
215
  """Default to keeping artifacts local.
195
216
 
196
- Enable this optional setting for cloud instances on lamin.ai.
197
-
198
217
  Guide: :doc:`faq/keep-artifacts-local`
199
218
  """
200
219
  return self._keep_artifacts_local
201
220
 
221
+ @keep_artifacts_local.setter
222
+ def keep_artifacts_local(self, value: bool):
223
+ if not isinstance(value, bool):
224
+ raise ValueError("keep_artifacts_local must be a boolean value.")
225
+ self._keep_artifacts_local = value
226
+
202
227
  @property
203
228
  def storage(self) -> StorageSettings:
204
- """Default storage.
229
+ """Default storage of instance.
205
230
 
206
231
  For a cloud instance, this is cloud storage. For a local instance, this
207
232
  is a local directory.
@@ -210,14 +235,16 @@ class InstanceSettings:
210
235
 
211
236
  @property
212
237
  def local_storage(self) -> StorageSettings:
213
- """An additional local storage location.
238
+ """An alternative default local storage location in the current environment.
214
239
 
215
- Is only available if :attr:`keep_artifacts_local` is enabled.
240
+ Serves as the default storage location if :attr:`keep_artifacts_local` is enabled.
216
241
 
217
242
  Guide: :doc:`faq/keep-artifacts-local`
218
243
  """
219
- if not self._keep_artifacts_local:
220
- raise ValueError("`keep_artifacts_local` is not enabled for this instance.")
244
+ if not self.keep_artifacts_local:
245
+ raise ValueError(
246
+ "`keep_artifacts_local` is False, switch via: ln.setup.settings.instance.keep_artifacts_local = True"
247
+ )
221
248
  if self._local_storage is None:
222
249
  self._local_storage = self._search_local_root()
223
250
  if self._local_storage is None:
@@ -235,7 +262,7 @@ class InstanceSettings:
235
262
  local_root, host = local_root_host
236
263
 
237
264
  local_root = Path(local_root)
238
- if not self._keep_artifacts_local:
265
+ if not self.keep_artifacts_local:
239
266
  raise ValueError("`keep_artifacts_local` is not enabled for this instance.")
240
267
  local_storage = self._search_local_root(
241
268
  local_root=StorageSettings(local_root).root_as_str, mute_warning=True
@@ -370,7 +397,7 @@ class InstanceSettings:
370
397
  self._check_sqlite_lock()
371
398
  sqlite_file = self._sqlite_file
372
399
  cache_file = self.storage.cloud_to_local_no_update(sqlite_file)
373
- sqlite_file.synchronize(cache_file, print_progress=True) # type: ignore
400
+ sqlite_file.synchronize_to(cache_file, print_progress=True) # type: ignore
374
401
 
375
402
  def _check_sqlite_lock(self):
376
403
  if not self._cloud_sqlite_locker.has_lock:
@@ -19,7 +19,13 @@ from ._aws_options import (
19
19
  from ._aws_storage import find_closest_aws_region
20
20
  from ._deprecated import deprecated
21
21
  from .hashing import hash_and_encode_as_b62
22
- from .upath import LocalPathClasses, UPath, _split_path_query, create_path
22
+ from .upath import (
23
+ LocalPathClasses,
24
+ UPath,
25
+ _split_path_query,
26
+ create_path,
27
+ get_storage_region,
28
+ )
23
29
 
24
30
  if TYPE_CHECKING:
25
31
  from lamindb_setup.types import StorageType, UPathStr
@@ -43,50 +49,6 @@ def instance_uid_from_uuid(instance_id: UUID) -> str:
43
49
  return hash_and_encode_as_b62(instance_id.hex)[:12]
44
50
 
45
51
 
46
- def get_storage_region(path: UPathStr) -> str | None:
47
- path_str = str(path)
48
- if path_str.startswith("s3://"):
49
- import botocore.session
50
- from botocore.config import Config
51
- from botocore.exceptions import ClientError
52
-
53
- # check for endpoint_url in storage options if upath
54
- if isinstance(path, UPath):
55
- endpoint_url = path.storage_options.get("endpoint_url", None)
56
- else:
57
- endpoint_url = None
58
- path_part = path_str.replace("s3://", "")
59
- # check for endpoint_url in the path string
60
- if "?" in path_part:
61
- assert endpoint_url is None
62
- path_part, query = _split_path_query(path_part)
63
- endpoint_url = query.get("endpoint_url", [None])[0]
64
- bucket = path_part.split("/")[0]
65
- session = botocore.session.get_session()
66
- credentials = session.get_credentials()
67
- if credentials is None or credentials.access_key is None:
68
- config = Config(signature_version=botocore.session.UNSIGNED)
69
- else:
70
- config = None
71
- s3_client = session.create_client(
72
- "s3", endpoint_url=endpoint_url, config=config
73
- )
74
- try:
75
- response = s3_client.head_bucket(Bucket=bucket)
76
- except ClientError as exc:
77
- response = getattr(exc, "response", {})
78
- if response.get("Error", {}).get("Code") == "404":
79
- raise exc
80
- region = (
81
- response.get("ResponseMetadata", {})
82
- .get("HTTPHeaders", {})
83
- .get("x-amz-bucket-region", None)
84
- )
85
- else:
86
- region = None
87
- return region
88
-
89
-
90
52
  def get_storage_type(root_as_str: str) -> StorageType:
91
53
  import fsspec
92
54
 
@@ -39,7 +39,7 @@ class UserSettings:
39
39
  def __repr__(self) -> str:
40
40
  """Rich string representation."""
41
41
  representation = "Current user:"
42
- attrs = ["handle", "email", "uid"]
42
+ attrs = ["handle", "uid"]
43
43
  for attr in attrs:
44
44
  value = getattr(self, attr)
45
45
  representation += f"\n - {attr}: {value}"
@@ -247,7 +247,7 @@ def setup_django(
247
247
  IS_SETUP = True
248
248
 
249
249
  if isettings.keep_artifacts_local:
250
- isettings._search_local_root()
250
+ isettings._local_storage = isettings._search_local_root()
251
251
 
252
252
 
253
253
  # THIS IS NOT SAFE