lamindb_setup 1.0.3__tar.gz → 1.1.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 (104) hide show
  1. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/PKG-INFO +6 -4
  2. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/01-init-local-instance.ipynb +1 -2
  3. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/02-connect-local-instance.ipynb +50 -3
  4. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/05-init-hosted-instance.ipynb +1 -1
  5. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-cloud-sync.ipynb +22 -6
  6. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/__init__.py +17 -1
  7. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_check_setup.py +68 -9
  8. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_connect_instance.py +9 -1
  9. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_delete.py +1 -1
  10. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_init_instance.py +1 -6
  11. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_migrate.py +6 -6
  12. lamindb_setup-1.0.3/lamindb_setup/core/_aws_credentials.py → lamindb_setup-1.1.1/lamindb_setup/core/_aws_options.py +39 -18
  13. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_hub_client.py +5 -1
  14. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_hub_core.py +4 -4
  15. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_private_django_api.py +0 -5
  16. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings_instance.py +11 -14
  17. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings_storage.py +33 -16
  18. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/hashing.py +13 -6
  19. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/upath.py +66 -9
  20. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/noxfile.py +3 -2
  21. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/pyproject.toml +6 -4
  22. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_init_instance.py +23 -0
  23. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-local/conftest.py +2 -2
  24. lamindb_setup-1.1.1/tests/hub-prod/test_aws_options_manager.py +18 -0
  25. lamindb_setup-1.1.1/tests/hub-prod/test_upath.py +70 -0
  26. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/storage/test_hashing.py +6 -0
  27. lamindb_setup-1.1.1/tests/storage/test_storage_settings.py +24 -0
  28. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/storage/test_storage_stats.py +3 -1
  29. lamindb_setup-1.0.3/tests/hub-prod/test_aws_credentials_manager.py +0 -20
  30. lamindb_setup-1.0.3/tests/hub-prod/test_upath.py +0 -44
  31. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/.github/workflows/build.yml +0 -0
  32. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/.github/workflows/doc-changes.yml +0 -0
  33. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/.gitignore +0 -0
  34. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/.pre-commit-config.yaml +0 -0
  35. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/LICENSE +0 -0
  36. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/README.md +0 -0
  37. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/changelog.md +0 -0
  38. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
  39. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  40. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
  41. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
  42. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/08-test-multi-session.ipynb +0 -0
  43. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-cloud/test_notebooks.py +0 -0
  44. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-cache-management.ipynb +0 -0
  45. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
  46. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-empty-init.ipynb +0 -0
  47. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-import-schema.ipynb +0 -0
  48. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-init-load-local-anonymously.ipynb +0 -0
  49. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  50. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  51. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  52. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/hub-prod/test_notebooks2.py +0 -0
  53. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/index.md +0 -0
  54. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/notebooks.md +0 -0
  55. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/docs/reference.md +0 -0
  56. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_cache.py +0 -0
  57. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_check.py +0 -0
  58. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_close.py +0 -0
  59. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_django.py +0 -0
  60. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_entry_points.py +0 -0
  61. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_exportdb.py +0 -0
  62. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_importdb.py +0 -0
  63. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_register_instance.py +0 -0
  64. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_schema.py +0 -0
  65. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_schema_metadata.py +0 -0
  66. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_set_managed_storage.py +0 -0
  67. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_setup_user.py +0 -0
  68. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/_silence_loggers.py +0 -0
  69. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/__init__.py +0 -0
  70. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_aws_storage.py +0 -0
  71. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_deprecated.py +0 -0
  72. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_docs.py +0 -0
  73. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_hub_crud.py +0 -0
  74. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_hub_utils.py +0 -0
  75. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings.py +0 -0
  76. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings_load.py +0 -0
  77. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings_save.py +0 -0
  78. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings_store.py +0 -0
  79. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_settings_user.py +0 -0
  80. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
  81. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  82. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/django.py +0 -0
  83. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/exceptions.py +0 -0
  84. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/lamindb_setup/core/types.py +0 -0
  85. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/scripts/script-init-pass-user-no-writes.py +0 -0
  86. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/scripts/script-to-fail-managed-storage.py +0 -0
  87. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_connect_instance.py +0 -0
  88. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_delete_instance.py +0 -0
  89. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_edge_request.py +0 -0
  90. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_fail_managed_storage.py +0 -0
  91. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_init_pass_user_no_writes.py +0 -0
  92. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_login.py +0 -0
  93. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_migrate.py +0 -0
  94. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-cloud/test_set_storage.py +0 -0
  95. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-local/test_all.py +0 -0
  96. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-local/test_update_schema_in_hub.py +0 -0
  97. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-prod/conftest.py +0 -0
  98. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-prod/test_django.py +0 -0
  99. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-prod/test_global_settings.py +0 -0
  100. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  101. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/storage/test_entry_point.py +0 -0
  102. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/storage/test_storage_access.py +0 -0
  103. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/storage/test_storage_basis.py +0 -0
  104. {lamindb_setup-1.0.3 → lamindb_setup-1.1.1}/tests/storage/test_to_url.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb_setup
3
- Version: 1.0.3
3
+ Version: 1.1.1
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10
@@ -11,13 +11,15 @@ 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
13
13
  Requires-Dist: requests
14
- Requires-Dist: universal_pathlib==0.2.5
14
+ Requires-Dist: universal_pathlib==0.2.6
15
15
  Requires-Dist: botocore<2.0.0
16
16
  Requires-Dist: supabase>=2.8.1,<=2.11.0
17
+ Requires-Dist: storage3!=0.11.2; python_version < '3.11'
17
18
  Requires-Dist: psutil
19
+ Requires-Dist: packaging
18
20
  Requires-Dist: urllib3<2 ; extra == "aws"
19
21
  Requires-Dist: aiobotocore[boto3]>=2.5.4,<3.0.0 ; extra == "aws"
20
- Requires-Dist: s3fs>=2023.12.2,<=2024.10.0,!=2024.10.0 ; extra == "aws"
22
+ Requires-Dist: s3fs>=2023.12.2,<=2025.2.0,!=2024.10.0 ; extra == "aws"
21
23
  Requires-Dist: line_profiler ; extra == "dev"
22
24
  Requires-Dist: pyjwt<3.0.0 ; extra == "dev"
23
25
  Requires-Dist: psycopg2-binary ; extra == "dev"
@@ -29,7 +31,7 @@ Requires-Dist: pytest-xdist ; extra == "dev"
29
31
  Requires-Dist: nbproject-test>=0.4.3 ; extra == "dev"
30
32
  Requires-Dist: pandas ; extra == "dev"
31
33
  Requires-Dist: django-schema-graph ; extra == "erdiagram"
32
- Requires-Dist: gcsfs>=2023.12.2,<=2024.10.0 ; extra == "gcp"
34
+ Requires-Dist: gcsfs>=2023.12.2,<=2025.2.0 ; extra == "gcp"
33
35
  Project-URL: Home, https://github.com/laminlabs/lamindb-setup
34
36
  Provides-Extra: aws
35
37
  Provides-Extra: dev
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "cells": [
3
3
  {
4
- "attachments": {},
5
4
  "cell_type": "markdown",
6
5
  "metadata": {
7
6
  "tags": []
@@ -107,7 +106,7 @@
107
106
  "name": "python",
108
107
  "nbconvert_exporter": "python",
109
108
  "pygments_lexer": "ipython3",
110
- "version": "3.10.13"
109
+ "version": "3.10.16"
111
110
  },
112
111
  "vscode": {
113
112
  "interpreter": {
@@ -32,7 +32,9 @@
32
32
  "metadata": {},
33
33
  "outputs": [],
34
34
  "source": [
35
- "import lamindb_setup as ln_setup"
35
+ "import lamindb_setup as ln_setup\n",
36
+ "from lamindb_setup._check_setup import ModuleWasntConfigured\n",
37
+ "import pytest"
36
38
  ]
37
39
  },
38
40
  {
@@ -51,6 +53,20 @@
51
53
  "If the user is the instance owner, load the instance by name:"
52
54
  ]
53
55
  },
56
+ {
57
+ "cell_type": "code",
58
+ "execution_count": null,
59
+ "id": "1106eff3",
60
+ "metadata": {},
61
+ "outputs": [],
62
+ "source": [
63
+ "# bionty is not in the (schema) modules of mydata\n",
64
+ "# _check_instance_setup is called inside with from_module=None\n",
65
+ "# the branch where django is not setup yet\n",
66
+ "# as from_module=None it won't connect to an instance here\n",
67
+ "import bionty"
68
+ ]
69
+ },
54
70
  {
55
71
  "cell_type": "code",
56
72
  "execution_count": null,
@@ -58,7 +74,38 @@
58
74
  "metadata": {},
59
75
  "outputs": [],
60
76
  "source": [
61
- "ln_setup.connect(\"mydata\")"
77
+ "ln_setup.connect(\n",
78
+ " \"mydata\", _reload_lamindb=False\n",
79
+ ") # also test passing _reload_lamindb explicitly"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "id": "86a1cb7c",
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": [
89
+ "# wetlab is not in the (schema) modules of mydata\n",
90
+ "with pytest.raises(ModuleWasntConfigured):\n",
91
+ " # _check_instance_setup is called inside with from_module=None\n",
92
+ " # the branch where django is setup\n",
93
+ " import wetlab"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "execution_count": null,
99
+ "id": "799740b9",
100
+ "metadata": {},
101
+ "outputs": [],
102
+ "source": [
103
+ "# wetlab is not in the (schema) modules of mydata\n",
104
+ "with pytest.raises(ModuleWasntConfigured):\n",
105
+ " # _check_instance_setup is called inside with from_module=\"bionty\"\n",
106
+ " # the branch where django is setup\n",
107
+ " # in __getattr__ in __init__.py\n",
108
+ " bionty.CellType"
62
109
  ]
63
110
  },
64
111
  {
@@ -122,7 +169,7 @@
122
169
  "name": "python",
123
170
  "nbconvert_exporter": "python",
124
171
  "pygments_lexer": "ipython3",
125
- "version": "3.9.17"
172
+ "version": "3.10.16"
126
173
  },
127
174
  "vscode": {
128
175
  "interpreter": {
@@ -82,7 +82,7 @@
82
82
  "metadata": {},
83
83
  "outputs": [],
84
84
  "source": [
85
- "from lamindb_setup.core._aws_credentials import HOSTED_BUCKETS"
85
+ "from lamindb_setup.core._aws_options import HOSTED_BUCKETS"
86
86
  ]
87
87
  },
88
88
  {
@@ -224,9 +224,13 @@
224
224
  "metadata": {},
225
225
  "outputs": [],
226
226
  "source": [
227
- "dir_sync_local = settings.paths.cloud_to_local(\n",
227
+ "dir_sync_local = settings.paths.cloud_to_local_no_update(\n",
228
228
  " dir_sync.as_posix(), cache_key=\"dir_cache/key\"\n",
229
- ")"
229
+ ")\n",
230
+ "assert dir_sync_local == settings.cache_dir / \"dir_cache/key\"\n",
231
+ "\n",
232
+ "assert dir_sync.synchronize(dir_sync_local, just_check=True)\n",
233
+ "assert not dir_sync_local.exists()"
230
234
  ]
231
235
  },
232
236
  {
@@ -236,7 +240,7 @@
236
240
  "metadata": {},
237
241
  "outputs": [],
238
242
  "source": [
239
- "assert dir_sync_local == settings.cache_dir / \"dir_cache/key\"\n",
243
+ "assert dir_sync.synchronize(dir_sync_local, just_check=False)\n",
240
244
  "assert dir_sync_local.is_dir()\n",
241
245
  "assert num_files(dir_sync_local) == 2"
242
246
  ]
@@ -744,7 +748,19 @@
744
748
  "outputs": [],
745
749
  "source": [
746
750
  "hf_path = UPath(\"hf://datasets/Koncopd/lamindb-test@main/anndata/pbmc68k_test.h5ad\")\n",
747
- "hf_path_local = settings.paths.cloud_to_local(hf_path)"
751
+ "hf_path_local = settings.paths.cloud_to_local_no_update(hf_path)\n",
752
+ "assert isinstance(hf_path_local, LocalPathClasses)"
753
+ ]
754
+ },
755
+ {
756
+ "cell_type": "code",
757
+ "execution_count": null,
758
+ "id": "b0aad1f1",
759
+ "metadata": {},
760
+ "outputs": [],
761
+ "source": [
762
+ "assert hf_path.synchronize(hf_path_local, just_check=True)\n",
763
+ "assert not hf_path_local.exists()"
748
764
  ]
749
765
  },
750
766
  {
@@ -754,7 +770,7 @@
754
770
  "metadata": {},
755
771
  "outputs": [],
756
772
  "source": [
757
- "assert isinstance(hf_path_local, LocalPathClasses)\n",
773
+ "assert hf_path.synchronize(hf_path_local)\n",
758
774
  "assert hf_path_local.is_file()"
759
775
  ]
760
776
  },
@@ -811,7 +827,7 @@
811
827
  "name": "python",
812
828
  "nbconvert_exporter": "python",
813
829
  "pygments_lexer": "ipython3",
814
- "version": "3.9.17"
830
+ "version": "3.10.16"
815
831
  },
816
832
  "vscode": {
817
833
  "interpreter": {
@@ -33,10 +33,12 @@ Modules & settings:
33
33
 
34
34
  """
35
35
 
36
- __version__ = "1.0.3" # denote a release candidate for 0.1.0 with 0.1rc1
36
+ __version__ = "1.1.1" # denote a release candidate for 0.1.0 with 0.1rc1
37
37
 
38
38
  import os
39
39
 
40
+ from packaging import version as packaging_version
41
+
40
42
  from . import core
41
43
  from ._check_setup import _check_instance_setup
42
44
  from ._close import close
@@ -50,6 +52,20 @@ from ._register_instance import register
50
52
  from ._setup_user import login, logout
51
53
  from .core._settings import settings
52
54
 
55
+ # check that the version of s3fs is higher than the lower bound
56
+ # needed because spatialdata installs old versions of s3fs
57
+ try:
58
+ from s3fs import __version__ as s3fs_version
59
+
60
+ if packaging_version.parse(s3fs_version) < packaging_version.parse("2023.12.2"):
61
+ raise RuntimeError(
62
+ f"The version of s3fs you have ({s3fs_version}) is impompatible "
63
+ "with lamindb, please upgrade it: pip install s3fs>=2023.12.2"
64
+ )
65
+ except ImportError:
66
+ # might be not installed
67
+ pass
68
+
53
69
 
54
70
  def _is_CI_environment() -> bool:
55
71
  ci_env_vars = [
@@ -2,13 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import functools
4
4
  import importlib as il
5
+ import inspect
5
6
  import os
6
7
  from typing import TYPE_CHECKING
7
8
 
8
9
  from lamin_utils import logger
9
10
 
10
11
  from ._silence_loggers import silence_loggers
11
- from .core import django
12
+ from .core import django as django_lamin
12
13
  from .core._settings import settings
13
14
  from .core._settings_store import current_instance_settings_file
14
15
  from .core.exceptions import DefaultMessageException
@@ -33,8 +34,12 @@ CURRENT_ISETTINGS: InstanceSettings | None = None
33
34
  IS_LOADING: bool = False
34
35
 
35
36
 
37
+ class ModuleWasntConfigured(SystemExit):
38
+ pass
39
+
40
+
36
41
  # decorator to disable auto-connect when importing a module such as lamindb
37
- def _loading(func: Callable):
42
+ def disable_auto_connect(func: Callable):
38
43
  @functools.wraps(func)
39
44
  def wrapper(*args, **kwargs):
40
45
  global IS_LOADING
@@ -70,14 +75,66 @@ def _get_current_instance_settings() -> InstanceSettings | None:
70
75
  return None
71
76
 
72
77
 
78
+ def _normalize_module_name(module_name: str) -> str:
79
+ return module_name.replace("lnschema_", "").replace("_", "-")
80
+
81
+
82
+ # checks that the provided modules is in the modules of the provided instance
83
+ # or in the apps setup by django
84
+ def _check_module_in_instance_modules(
85
+ module: str, isettings: InstanceSettings | None = None
86
+ ) -> None:
87
+ not_in_instance_msg = (
88
+ f"'{module}' is missing from this instance. "
89
+ "Please go to your instance settings page and add it under 'schema modules'."
90
+ )
91
+
92
+ if isettings is not None:
93
+ modules_raw = isettings.modules
94
+ modules = set(modules_raw).union(
95
+ _normalize_module_name(module) for module in modules_raw
96
+ )
97
+ if _normalize_module_name(module) not in modules and module not in modules:
98
+ raise ModuleWasntConfigured(not_in_instance_msg)
99
+ else:
100
+ return
101
+
102
+ from django.apps import apps
103
+
104
+ for app in apps.get_app_configs():
105
+ # app.name is always unnormalized module (python package) name
106
+ if module == app.name or module == _normalize_module_name(app.name):
107
+ return
108
+ raise ModuleWasntConfigured(not_in_instance_msg)
109
+
110
+
111
+ # infer the name of the module that calls this function
112
+ def _infer_callers_module_name() -> str | None:
113
+ stack = inspect.stack()
114
+ if len(stack) < 3:
115
+ return None
116
+ module = inspect.getmodule(stack[2][0])
117
+ return module.__name__.partition(".")[0] if module is not None else None
118
+
119
+
73
120
  # we make this a private function because in all the places it's used,
74
121
  # users should not see it
75
122
  def _check_instance_setup(from_module: str | None = None) -> bool:
76
- if django.IS_SETUP:
123
+ if django_lamin.IS_SETUP:
77
124
  # reload logic here because module might not yet have been imported
78
125
  # upon first setup
79
- if from_module is not None and from_module != "lamindb":
80
- il.reload(il.import_module(from_module))
126
+ if from_module is not None:
127
+ if from_module != "lamindb":
128
+ _check_module_in_instance_modules(from_module)
129
+ il.reload(il.import_module(from_module))
130
+ else:
131
+ infer_module = _infer_callers_module_name()
132
+ if infer_module is not None and infer_module not in {
133
+ "lamindb",
134
+ "lamindb_setup",
135
+ "lamin_cli",
136
+ }:
137
+ _check_module_in_instance_modules(infer_module)
81
138
  return True
82
139
  silence_loggers()
83
140
  if os.environ.get("LAMINDB_MULTI_INSTANCE") == "true":
@@ -91,17 +148,19 @@ def _check_instance_setup(from_module: str | None = None) -> bool:
91
148
  if (
92
149
  from_module is not None
93
150
  and settings.auto_connect
94
- and not django.IS_SETUP
151
+ and not django_lamin.IS_SETUP
95
152
  and not IS_LOADING
96
153
  ):
97
- if not from_module == "lamindb":
154
+ if from_module != "lamindb":
155
+ _check_module_in_instance_modules(from_module, isettings)
156
+
98
157
  import lamindb
99
158
 
100
159
  il.reload(il.import_module(from_module))
101
160
  else:
102
- django.setup_django(isettings)
161
+ django_lamin.setup_django(isettings)
103
162
  logger.important(f"connected lamindb: {isettings.slug}")
104
- return django.IS_SETUP
163
+ return django_lamin.IS_SETUP
105
164
  else:
106
165
  if from_module is not None and settings.auto_connect:
107
166
  logger.warning(InstanceNotSetupError.default_message)
@@ -271,6 +271,7 @@ def connect(instance: str | None = None, **kwargs) -> str | tuple | None:
271
271
  settings_dir / f"no_lnschema_core-{isettings.slug.replace('/', '--')}"
272
272
  )
273
273
  if not no_lnschema_core_file.exists():
274
+ # sqlite file for cloud sqlite instances is already updated here
274
275
  migrate_lnschema_core(
275
276
  isettings, no_lnschema_core_file, write_file=_write_settings
276
277
  )
@@ -311,7 +312,8 @@ def connect(instance: str | None = None, **kwargs) -> str | tuple | None:
311
312
  load_from_isettings(isettings, user=_user, write_settings=_write_settings)
312
313
  if _reload_lamindb:
313
314
  importlib.reload(importlib.import_module("lamindb"))
314
- logger.important(f"connected lamindb: {isettings.slug}")
315
+ else:
316
+ logger.important(f"connected lamindb: {isettings.slug}")
315
317
  except Exception as e:
316
318
  if isettings is not None:
317
319
  if _write_settings:
@@ -339,6 +341,12 @@ def migrate_lnschema_core(
339
341
  """Migrate lnschema_core tables to lamindb tables."""
340
342
  from urllib.parse import urlparse
341
343
 
344
+ # we need to do this because the sqlite file should be already synced
345
+ # has no effect if not cloud sqlite
346
+ # errors if the sqlite file is not in the cloud and doesn't exist locally
347
+ # isettings.db syncs but doesn't error in this case due to error_no_origin=False
348
+ isettings._update_local_sqlite_file()
349
+
342
350
  parsed_uri = urlparse(isettings.db)
343
351
  db_type = parsed_uri.scheme
344
352
 
@@ -7,7 +7,7 @@ from uuid import UUID
7
7
  from lamin_utils import logger
8
8
 
9
9
  from ._connect_instance import _connect_instance, get_owner_name_from_identifier
10
- from .core._aws_credentials import HOSTED_BUCKETS
10
+ from .core._aws_options import HOSTED_BUCKETS
11
11
  from .core._hub_core import delete_instance as delete_instance_on_hub
12
12
  from .core._hub_core import get_storage_records_for_instance
13
13
  from .core._settings import settings
@@ -396,10 +396,5 @@ def infer_instance_name(
396
396
  if storage == "create-s3":
397
397
  raise ValueError("pass name to init if storage = 'create-s3'")
398
398
  storage_path = UPath(storage).resolve()
399
- # not sure if name is ever ""
400
- if storage_path.name != "":
401
- name = storage_path.name
402
- else:
403
- # dedicated treatment of bucket names
404
- name = storage_path.drive
399
+ name = storage_path.path.rstrip("/").split("/")[-1]
405
400
  return name.lower()
@@ -5,7 +5,7 @@ from django.db.migrations.loader import MigrationLoader
5
5
  from lamin_utils import logger
6
6
  from packaging import version
7
7
 
8
- from ._check_setup import _check_instance_setup, _loading
8
+ from ._check_setup import _check_instance_setup, disable_auto_connect
9
9
  from .core._settings import settings
10
10
  from .core.django import setup_django
11
11
 
@@ -62,7 +62,7 @@ class migrate:
62
62
  """
63
63
 
64
64
  @classmethod
65
- @_loading
65
+ @disable_auto_connect
66
66
  def create(cls) -> None:
67
67
  """Create a migration."""
68
68
  if _check_instance_setup():
@@ -70,7 +70,7 @@ class migrate:
70
70
  setup_django(settings.instance, create_migrations=True)
71
71
 
72
72
  @classmethod
73
- @_loading
73
+ @disable_auto_connect
74
74
  def deploy(cls) -> None:
75
75
  """Deploy a migration."""
76
76
  from ._schema_metadata import update_schema_in_hub
@@ -115,7 +115,7 @@ class migrate:
115
115
  )
116
116
 
117
117
  @classmethod
118
- @_loading
118
+ @disable_auto_connect
119
119
  def check(cls) -> bool:
120
120
  """Check whether Registry definitions are in sync with migrations."""
121
121
  from django.core.management import call_command
@@ -132,7 +132,7 @@ class migrate:
132
132
  return True
133
133
 
134
134
  @classmethod
135
- @_loading
135
+ @disable_auto_connect
136
136
  def squash(
137
137
  cls, package_name, migration_nr, start_migration_nr: str | None = None
138
138
  ) -> None:
@@ -148,7 +148,7 @@ class migrate:
148
148
  call_command("squashmigrations", package_name, migration_nr)
149
149
 
150
150
  @classmethod
151
- @_loading
151
+ @disable_auto_connect
152
152
  def show(cls) -> None:
153
153
  """Show migrations."""
154
154
  from django.core.management import call_command
@@ -4,7 +4,7 @@ import os
4
4
  import time
5
5
 
6
6
  from lamin_utils import logger
7
- from upath.implementations.cloud import S3Path
7
+ from upath import UPath
8
8
 
9
9
  HOSTED_REGIONS = [
10
10
  "eu-central-1",
@@ -25,22 +25,29 @@ def _keep_trailing_slash(path_str: str):
25
25
  return path_str if path_str[-1] == "/" else path_str + "/"
26
26
 
27
27
 
28
- AWS_CREDENTIALS_EXPIRATION = 11 * 60 * 60 # refresh credentials after 11 hours
28
+ AWS_CREDENTIALS_EXPIRATION: int = 11 * 60 * 60 # refresh credentials after 11 hours
29
29
 
30
30
 
31
31
  # set anon=True for these buckets if credentials fail for a public bucket
32
32
  # to be expanded
33
- PUBLIC_BUCKETS = ("cellxgene-data-public",)
33
+ PUBLIC_BUCKETS: tuple[str] = ("cellxgene-data-public",)
34
34
 
35
35
 
36
- class AWSCredentialsManager:
36
+ # s3-comaptible endpoints managed by lamin
37
+ # None means the standard aws s3 endpoint
38
+ LAMIN_ENDPOINTS: tuple[str | None] = (None,)
39
+
40
+
41
+ class AWSOptionsManager:
37
42
  def __init__(self):
38
43
  self._credentials_cache = {}
39
44
 
40
45
  from s3fs import S3FileSystem
41
46
 
42
47
  # this is cached so will be resued with the connection initialized
43
- fs = S3FileSystem(cache_regions=True)
48
+ fs = S3FileSystem(
49
+ cache_regions=True, use_listings_cache=True, version_aware=False
50
+ )
44
51
  try:
45
52
  fs.connect()
46
53
  self.anon: bool = fs.session._credentials is None
@@ -83,7 +90,7 @@ class AWSCredentialsManager:
83
90
  def _get_cached_credentials(self, root: str) -> dict:
84
91
  return self._credentials_cache[root]["credentials"]
85
92
 
86
- def _path_inject_options(self, path: S3Path, credentials: dict) -> S3Path:
93
+ def _path_inject_options(self, path: UPath, credentials: dict) -> UPath:
87
94
  if credentials == {}:
88
95
  # credentials were specified manually for the path
89
96
  if "anon" in path.storage_options:
@@ -99,13 +106,27 @@ class AWSCredentialsManager:
99
106
  connection_options = credentials
100
107
 
101
108
  if "cache_regions" in path.storage_options:
102
- cache_regions = path.storage_options["cache_regions"]
109
+ connection_options["cache_regions"] = path.storage_options["cache_regions"]
103
110
  else:
104
- cache_regions = True
105
-
106
- return S3Path(path, cache_regions=cache_regions, **connection_options)
107
-
108
- def enrich_path(self, path: S3Path, access_token: str | None = None) -> S3Path:
111
+ connection_options["cache_regions"] = (
112
+ path.storage_options.get("endpoint_url", None) is None
113
+ )
114
+ # we use cache to avoid some uneeded downloads or credential problems
115
+ # see in upload_from
116
+ connection_options["use_listings_cache"] = path.storage_options.get(
117
+ "use_listings_cache", True
118
+ )
119
+ # normally we want to ignore objects vsrsions in a versioned bucket
120
+ connection_options["version_aware"] = path.storage_options.get(
121
+ "version_aware", False
122
+ )
123
+
124
+ return UPath(path, **connection_options)
125
+
126
+ def enrich_path(self, path: UPath, access_token: str | None = None) -> UPath:
127
+ # ignore paths with non-lamin-managed endpoints
128
+ if path.storage_options.get("endpoint_url", None) not in LAMIN_ENDPOINTS:
129
+ return path
109
130
  # trailing slash is needed to avoid returning incorrect results
110
131
  # with .startswith
111
132
  # for example s3://lamindata-eu should not receive cache for s3://lamindata
@@ -160,13 +181,13 @@ class AWSCredentialsManager:
160
181
  return self._path_inject_options(path, credentials)
161
182
 
162
183
 
163
- _aws_credentials_manager: AWSCredentialsManager | None = None
184
+ _aws_options_manager: AWSOptionsManager | None = None
164
185
 
165
186
 
166
- def get_aws_credentials_manager() -> AWSCredentialsManager:
167
- global _aws_credentials_manager
187
+ def get_aws_options_manager() -> AWSOptionsManager:
188
+ global _aws_options_manager
168
189
 
169
- if _aws_credentials_manager is None:
170
- _aws_credentials_manager = AWSCredentialsManager()
190
+ if _aws_options_manager is None:
191
+ _aws_options_manager = AWSOptionsManager()
171
192
 
172
- return _aws_credentials_manager
193
+ return _aws_options_manager
@@ -66,7 +66,11 @@ def connect_hub(
66
66
  ) -> Client:
67
67
  env = Environment(fallback=fallback_env)
68
68
  if client_options is None:
69
- client_options = ClientOptions(auto_refresh_token=False)
69
+ # function_client_timeout=5 by default
70
+ # increase to avoid rare timeouts for edge functions
71
+ client_options = ClientOptions(
72
+ auto_refresh_token=False, function_client_timeout=10
73
+ )
70
74
  return create_client(env.supabase_api_url, env.supabase_anon_key, client_options)
71
75
 
72
76
 
@@ -311,6 +311,7 @@ def _init_instance(
311
311
  "db_database": db_dsn.db.database,
312
312
  }
313
313
  fields.update(db_fields)
314
+ slug = isettings.slug
314
315
  # I'd like the following to be an upsert, but this seems to violate RLS
315
316
  # Similarly, if we don't specify `returning="minimal"`, we'll violate RLS
316
317
  # we could make this idempotent by catching an error, but this seems dangerous
@@ -318,14 +319,13 @@ def _init_instance(
318
319
  try:
319
320
  client.table("instance").insert(fields, returning="minimal").execute()
320
321
  except APIError:
321
- logger.warning(
322
- f"instance already existed at: https://lamin.ai/{isettings.owner}/{isettings.name}"
323
- )
322
+ logger.warning(f"instance already existed at: https://lamin.ai/{slug}")
324
323
  return None
325
324
  client.table("storage").update(
326
325
  {"instance_id": isettings._id.hex, "is_default": True}
327
326
  ).eq("id", isettings.storage._uuid.hex).execute() # type: ignore
328
- logger.important(f"go to: https://lamin.ai/{isettings.owner}/{isettings.name}")
327
+ if isettings.dialect != "sqlite" and isettings.is_remote:
328
+ logger.important(f"go to: https://lamin.ai/{slug}")
329
329
 
330
330
 
331
331
  def _connect_instance_hub(
@@ -34,7 +34,6 @@ def private_django_api(reverse=False):
34
34
  # the order here matters
35
35
  # changing it might break the tests
36
36
  attributes = [
37
- "DoesNotExist",
38
37
  "MultipleObjectsReturned",
39
38
  "add_to_class",
40
39
  "adelete",
@@ -72,10 +71,6 @@ def private_django_api(reverse=False):
72
71
  new_name = attr if reverse else f"_{attr}"
73
72
  content = content.replace(old_name, new_name)
74
73
 
75
- if not reverse:
76
- content = content.replace("Field_DoesNotExist", "FieldDoesNotExist")
77
- content = content.replace("Object_DoesNotExist", "ObjectDoesNotExist")
78
-
79
74
  if content != original_content:
80
75
  file_path.write_text(content, encoding=encoding)
81
76