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.
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/PKG-INFO +1 -1
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/06-connect-hosted-instance.ipynb +3 -2
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/07-keep-artifacts-local.ipynb +4 -5
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-cloud-sync.ipynb +30 -7
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/__init__.py +1 -1
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_check_setup.py +3 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_connect_instance.py +20 -1
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_aws_options.py +1 -5
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_client.py +24 -9
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_core.py +9 -7
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings.py +1 -1
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_instance.py +42 -15
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_storage.py +7 -45
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_user.py +1 -1
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/django.py +1 -1
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/upath.py +181 -137
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/scripts/script-connect-fine-grained-access.py +2 -2
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_stats.py +7 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_to_url.py +7 -6
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.github/workflows/build.yml +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.github/workflows/doc-changes.yml +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.gitignore +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/.pre-commit-config.yaml +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/LICENSE +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/README.md +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/changelog.md +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/08-test-multi-session.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-cloud/test_notebooks.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-cache-management.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-empty-init.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-import-schema.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-init-load-local-anonymously.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/hub-prod/test_notebooks2.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/index.md +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/notebooks.md +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/docs/reference.md +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_cache.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_check.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_delete.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_disconnect.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_django.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_entry_points.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_exportdb.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_importdb.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_init_instance.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_migrate.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_register_instance.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_schema.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_schema_metadata.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_set_managed_storage.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_setup_user.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/_silence_loggers.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/__init__.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_aws_storage.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_deprecated.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_docs.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_crud.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_hub_utils.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_private_django_api.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_load.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_save.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_settings_store.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/exceptions.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/hashing.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/core/types.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/errors.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/py.typed +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/lamindb_setup/types.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/noxfile.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/pyproject.toml +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/scripts/script-init-pass-user-no-writes.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/scripts/script-to-fail-managed-storage.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_connect_instance.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_delete_instance.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_edge_request.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_fail_managed_storage.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_init_instance.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_init_pass_user_no_writes.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_login.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_migrate.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-cloud/test_set_storage.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/conftest.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/test_all.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-local/test_update_schema_in_hub.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/conftest.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_aws_options_manager.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_django.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_global_settings.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/hub-prod/test_upath.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_entry_point.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_hashing.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_access.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_basis.py +0 -0
- {lamindb_setup-1.8.1 → lamindb_setup-1.8.3}/tests/storage/test_storage_settings.py +0 -0
|
@@ -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
|
-
"
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
819
|
+
" hf_path.synchronize_to(UPath(\"./does_not_exist.file\"), error_no_origin=True)"
|
|
797
820
|
]
|
|
798
821
|
},
|
|
799
822
|
{
|
|
@@ -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.
|
|
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
|
-
|
|
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=
|
|
74
|
-
postgrest_client_timeout=
|
|
75
|
+
function_client_timeout=DEFAULT_TIMEOUT,
|
|
76
|
+
postgrest_client_timeout=DEFAULT_TIMEOUT,
|
|
75
77
|
)
|
|
76
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
501
|
-
and (env_db_token := os.environ["
|
|
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
|
-
|
|
535
|
-
if
|
|
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: {
|
|
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.
|
|
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:
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
238
|
+
"""An alternative default local storage location in the current environment.
|
|
214
239
|
|
|
215
|
-
|
|
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.
|
|
220
|
-
raise ValueError(
|
|
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.
|
|
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.
|
|
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
|
|
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", "
|
|
42
|
+
attrs = ["handle", "uid"]
|
|
43
43
|
for attr in attrs:
|
|
44
44
|
value = getattr(self, attr)
|
|
45
45
|
representation += f"\n - {attr}: {value}"
|