lamindb_setup 0.73.0__tar.gz → 0.73.2__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-0.73.0 → lamindb_setup-0.73.2}/PKG-INFO +2 -2
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/changelog.md +5 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/06-connect-hosted-instance.ipynb +4 -27
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-cloud-sync.ipynb +2 -1
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-sqlite-lock.ipynb +3 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/__init__.py +1 -1
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_init_instance.py +2 -2
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_migrate.py +5 -0
- lamindb_setup-0.73.2/lamindb_setup/_schema_metadata.py +479 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_aws_credentials.py +4 -4
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings_instance.py +17 -5
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings_storage.py +2 -3
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/cloud_sqlite_locker.py +2 -1
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/upath.py +10 -29
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/noxfile.py +5 -8
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/pyproject.toml +1 -1
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-cloud/test_connect_instance.py +7 -7
- lamindb_setup-0.73.2/tests/hub-local/test_update_schema_in_hub.py +142 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-prod/test_upath.py +8 -3
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/storage/test_storage_access.py +1 -1
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/.github/workflows/build.yml +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/.github/workflows/latest-changes.jinja2 +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/.github/workflows/latest-changes.yml +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/.gitignore +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/.pre-commit-config.yaml +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/LICENSE +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/README.md +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/test-multi-session.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/test_notebooks.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-cache-management.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-empty-init.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-import-schema.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-prod/test_notebooks2.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/index.md +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/notebooks.md +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/reference.md +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_cache.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_check.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_check_setup.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_close.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_connect_instance.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_delete.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_django.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_exportdb.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_importdb.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_register_instance.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_schema.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_set_managed_storage.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_setup_user.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/_silence_loggers.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/__init__.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_aws_storage.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_deprecated.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_docs.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_hub_client.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_hub_core.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_hub_crud.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_hub_utils.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings_load.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings_save.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings_store.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_settings_user.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/django.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/exceptions.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/hashing.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/lamindb_setup/core/types.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-cloud/test_delete_instance.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-cloud/test_init_instance.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-cloud/test_login.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-cloud/test_migrate.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-cloud/test_set_storage.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-local/conftest.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-local/test_all.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-prod/conftest.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-prod/test_auto_connect.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-prod/test_django.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/storage/test_hashing.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/storage/test_storage_basis.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/storage/test_storage_stats.py +0 -0
- {lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/tests/storage/test_to_url.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lamindb_setup
|
|
3
|
-
Version: 0.73.
|
|
3
|
+
Version: 0.73.2
|
|
4
4
|
Summary: Setup & configure LaminDB.
|
|
5
5
|
Author-email: Lamin Labs <laminlabs@gmail.com>
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -11,7 +11,7 @@ Requires-Dist: dj_database_url>=1.3.0,<3.0.0
|
|
|
11
11
|
Requires-Dist: pydantic[dotenv]<2.0.0
|
|
12
12
|
Requires-Dist: appdirs<2.0.0
|
|
13
13
|
Requires-Dist: requests
|
|
14
|
-
Requires-Dist: universal_pathlib==0.
|
|
14
|
+
Requires-Dist: universal_pathlib==0.2.2
|
|
15
15
|
Requires-Dist: botocore<2.0.0
|
|
16
16
|
Requires-Dist: supabase==2.2.1
|
|
17
17
|
Requires-Dist: urllib3<2 ; extra == "aws"
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
<!-- prettier-ignore -->
|
|
4
4
|
Name | PR | Developer | Date | Version
|
|
5
5
|
--- | --- | --- | --- | ---
|
|
6
|
+
🐛 Fix permission error when screening for local storage root | [778](https://github.com/laminlabs/lamindb-setup/pull/778) | [falexwolf](https://github.com/falexwolf) | 2024-06-19 | 0.73.2
|
|
7
|
+
🐛 Fix same root check in get_locker | [777](https://github.com/laminlabs/lamindb-setup/pull/777) | [Koncopd](https://github.com/Koncopd) | 2024-06-13 |
|
|
8
|
+
✨ Add .ome.zarr as composite suffix | [775](https://github.com/laminlabs/lamindb-setup/pull/775) | [sunnyosun](https://github.com/sunnyosun) | 2024-06-05 |
|
|
9
|
+
🏗️ Update instance schema in the hub | [774](https://github.com/laminlabs/lamindb-setup/pull/774) | [fredericenard](https://github.com/fredericenard) | 2024-06-04 |
|
|
10
|
+
⬆️ Migrate to upath 0.2.2 | [723](https://github.com/laminlabs/lamindb-setup/pull/723) | [Koncopd](https://github.com/Koncopd) | 2024-06-02 |
|
|
6
11
|
🐛 Fix trailing slash in upload_from source | [773](https://github.com/laminlabs/lamindb-setup/pull/773) | [Koncopd](https://github.com/Koncopd) | 2024-05-23 | 0.73.0
|
|
7
12
|
🚸 Make `upload_from()`, `download_to()`, and `view_tree()` more user friendly | [772](https://github.com/laminlabs/lamindb-setup/pull/772) | [falexwolf](https://github.com/falexwolf) | 2024-05-23 |
|
|
8
13
|
✨ Resolve s3 bucket region even without access rights | [771](https://github.com/laminlabs/lamindb-setup/pull/771) | [Koncopd](https://github.com/Koncopd) | 2024-05-22 |
|
{lamindb_setup-0.73.0 → lamindb_setup-0.73.2}/docs/hub-cloud/06-connect-hosted-instance.ipynb
RENAMED
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"outputs": [],
|
|
87
87
|
"source": [
|
|
88
88
|
"target_dir = root / \"test-dir-upload\"\n",
|
|
89
|
-
"target_dir.upload_from(test_dir)\n",
|
|
89
|
+
"target_dir.upload_from(test_dir, create_folder=True) # default\n",
|
|
90
90
|
"\n",
|
|
91
91
|
"assert target_dir.is_dir()\n",
|
|
92
92
|
"assert (target_dir / \"test-dir-upload\").exists()\n",
|
|
@@ -102,34 +102,11 @@
|
|
|
102
102
|
"metadata": {},
|
|
103
103
|
"outputs": [],
|
|
104
104
|
"source": [
|
|
105
|
-
"# test trailing slash in target_dir\n",
|
|
106
|
-
"target_dir = root / \"test-dir-upload/\"\n",
|
|
107
|
-
"assert target_dir.path.endswith(\"/\")\n",
|
|
108
|
-
"\n",
|
|
109
|
-
"dest_dir = target_dir.upload_from(test_dir, create_folder=True)\n",
|
|
110
|
-
"\n",
|
|
111
|
-
"assert \"//\" not in dest_dir.path\n",
|
|
112
|
-
"assert dest_dir.as_posix() == (root / \"test-dir-upload/test-dir-upload\").as_posix()\n",
|
|
113
|
-
"assert dest_dir.is_dir()\n",
|
|
114
|
-
"\n",
|
|
115
|
-
"dest_dir.rmdir()\n",
|
|
116
|
-
"assert not dest_dir.exists()"
|
|
117
|
-
]
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
"cell_type": "code",
|
|
121
|
-
"execution_count": null,
|
|
122
|
-
"metadata": {},
|
|
123
|
-
"outputs": [],
|
|
124
|
-
"source": [
|
|
125
|
-
"target_dir_check = root / \"test-dir-upload\"\n",
|
|
126
|
-
"assert not target_dir_check.exists()\n",
|
|
127
|
-
"\n",
|
|
128
105
|
"target_dir.upload_from(test_dir, create_folder=False)\n",
|
|
129
106
|
"\n",
|
|
130
|
-
"assert
|
|
131
|
-
"assert (
|
|
132
|
-
"assert not (
|
|
107
|
+
"assert target_dir.is_dir()\n",
|
|
108
|
+
"assert (target_dir / \"file1\").exists()\n",
|
|
109
|
+
"assert not (target_dir / \"test-dir-upload\").exists()"
|
|
133
110
|
]
|
|
134
111
|
},
|
|
135
112
|
{
|
|
@@ -237,7 +237,8 @@
|
|
|
237
237
|
"source": [
|
|
238
238
|
"time.sleep(1)\n",
|
|
239
239
|
"cloud_file = dir_sync / \"file1\"\n",
|
|
240
|
-
"
|
|
240
|
+
"# update cloud timestamp, exist_ok=False needed due to truncate=not exist_ok in upath\n",
|
|
241
|
+
"cloud_file.touch(exist_ok=False) \n",
|
|
241
242
|
"\n",
|
|
242
243
|
"assert cloud_file.modified.timestamp() > local_file_new.stat().st_mtime"
|
|
243
244
|
]
|
|
@@ -75,6 +75,9 @@
|
|
|
75
75
|
"source": [
|
|
76
76
|
"ln_setup.init(storage=\"s3://lamindb-ci/test-load-lock\", name=\"test-load-lock\")\n",
|
|
77
77
|
"instance_id = ln_setup.settings.instance._id\n",
|
|
78
|
+
"\n",
|
|
79
|
+
"assert ln_setup.settings.instance._cloud_sqlite_locker is ln_setup.settings.instance._cloud_sqlite_locker\n",
|
|
80
|
+
"\n",
|
|
78
81
|
"ln_setup.close()"
|
|
79
82
|
]
|
|
80
83
|
},
|
|
@@ -16,7 +16,7 @@ from ._silence_loggers import silence_loggers
|
|
|
16
16
|
from .core import InstanceSettings
|
|
17
17
|
from .core._settings import settings
|
|
18
18
|
from .core._settings_storage import StorageSettings, init_storage
|
|
19
|
-
from .core.upath import
|
|
19
|
+
from .core.upath import UPath
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from pydantic import PostgresDsn
|
|
@@ -351,7 +351,7 @@ def infer_instance_name(
|
|
|
351
351
|
return str(db).split("/")[-1]
|
|
352
352
|
if storage == "create-s3":
|
|
353
353
|
raise ValueError("pass name to init if storage = 'create-s3'")
|
|
354
|
-
storage_path =
|
|
354
|
+
storage_path = UPath(storage)
|
|
355
355
|
if storage_path.name != "":
|
|
356
356
|
name = storage_path.name
|
|
357
357
|
else:
|
|
@@ -68,6 +68,8 @@ class migrate:
|
|
|
68
68
|
@classmethod
|
|
69
69
|
def deploy(cls) -> None:
|
|
70
70
|
"""Deploy a migration."""
|
|
71
|
+
from ._schema_metadata import update_schema_in_hub
|
|
72
|
+
|
|
71
73
|
if _check_instance_setup():
|
|
72
74
|
raise RuntimeError("Restart Python session to migrate or use CLI!")
|
|
73
75
|
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
@@ -104,6 +106,9 @@ class migrate:
|
|
|
104
106
|
# this populates the hub
|
|
105
107
|
if instance_is_on_hub:
|
|
106
108
|
logger.important(f"updating lamindb version in hub: {lamindb.__version__}")
|
|
109
|
+
# TODO: integrate update of instance table within update_schema_in_hub & below
|
|
110
|
+
if settings.instance.dialect != "sqlite":
|
|
111
|
+
update_schema_in_hub()
|
|
107
112
|
call_with_fallback_auth(
|
|
108
113
|
update_instance,
|
|
109
114
|
instance_id=settings.instance._id.hex,
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import importlib
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, Dict
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
import sqlparse
|
|
11
|
+
from django.contrib.postgres.expressions import ArraySubquery
|
|
12
|
+
from django.db.models import (
|
|
13
|
+
Field,
|
|
14
|
+
ForeignObjectRel,
|
|
15
|
+
ManyToManyField,
|
|
16
|
+
ManyToManyRel,
|
|
17
|
+
OuterRef,
|
|
18
|
+
QuerySet,
|
|
19
|
+
Subquery,
|
|
20
|
+
)
|
|
21
|
+
from django.db.models.functions import JSONObject
|
|
22
|
+
from sqlparse.sql import Identifier, IdentifierList
|
|
23
|
+
from sqlparse.tokens import DML, Keyword
|
|
24
|
+
|
|
25
|
+
from lamindb_setup import settings
|
|
26
|
+
from lamindb_setup._init_instance import get_schema_module_name
|
|
27
|
+
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from lnschema_core.models import Registry
|
|
31
|
+
from supabase import Client
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def update_schema_in_hub() -> tuple[bool, UUID, dict]:
|
|
35
|
+
return call_with_fallback_auth(_synchronize_schema)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _synchronize_schema(client: Client) -> tuple[bool, UUID, dict]:
|
|
39
|
+
schema_metadata = SchemaMetadata()
|
|
40
|
+
schema_metadata_dict = schema_metadata.to_json()
|
|
41
|
+
schema_uuid = _dict_to_uuid(schema_metadata_dict)
|
|
42
|
+
schema = _get_schema_by_id(schema_uuid, client)
|
|
43
|
+
|
|
44
|
+
is_new = schema is None
|
|
45
|
+
if is_new:
|
|
46
|
+
module_set_info = schema_metadata._get_module_set_info()
|
|
47
|
+
module_ids = "-".join(str(module_info["id"]) for module_info in module_set_info)
|
|
48
|
+
schema = (
|
|
49
|
+
client.table("schema")
|
|
50
|
+
.insert(
|
|
51
|
+
{
|
|
52
|
+
"id": schema_uuid.hex,
|
|
53
|
+
"module_ids": module_ids,
|
|
54
|
+
"module_set_info": module_set_info,
|
|
55
|
+
"json": schema_metadata_dict,
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
.execute()
|
|
59
|
+
.data[0]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
instance_response = (
|
|
63
|
+
client.table("instance")
|
|
64
|
+
.update({"schema_id": schema_uuid.hex})
|
|
65
|
+
.eq("id", settings.instance._id.hex)
|
|
66
|
+
.execute()
|
|
67
|
+
)
|
|
68
|
+
assert (
|
|
69
|
+
len(instance_response.data) == 1
|
|
70
|
+
), f"schema of instance {settings.instance._id.hex} could not be updated with schema {schema_uuid.hex}"
|
|
71
|
+
|
|
72
|
+
return is_new, schema_uuid, schema
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_schema_by_id(id: UUID):
|
|
76
|
+
return call_with_fallback_auth(_get_schema_by_id, id=id)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_schema_by_id(id: UUID, client: Client):
|
|
80
|
+
response = client.table("schema").select("*").eq("id", id.hex).execute()
|
|
81
|
+
if len(response.data) == 0:
|
|
82
|
+
return None
|
|
83
|
+
return response.data[0]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _dict_to_uuid(dict: dict):
|
|
87
|
+
encoded = json.dumps(dict, sort_keys=True).encode("utf-8")
|
|
88
|
+
hash = hashlib.md5(encoded).digest()
|
|
89
|
+
uuid = UUID(bytes=hash[:16])
|
|
90
|
+
return uuid
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SchemaMetadata:
|
|
94
|
+
def __init__(self) -> None:
|
|
95
|
+
self.included_modules = ["core"] + list(settings.instance.schema)
|
|
96
|
+
self.modules = self._get_modules_metadata()
|
|
97
|
+
|
|
98
|
+
def to_dict(
|
|
99
|
+
self, include_django_objects: bool = True, include_select_terms: bool = True
|
|
100
|
+
):
|
|
101
|
+
return {
|
|
102
|
+
module_name: {
|
|
103
|
+
model_name: model.to_dict(include_django_objects, include_select_terms)
|
|
104
|
+
for model_name, model in module.items()
|
|
105
|
+
}
|
|
106
|
+
for module_name, module in self.modules.items()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def to_json(self, include_select_terms: bool = True):
|
|
110
|
+
return self.to_dict(
|
|
111
|
+
include_django_objects=False, include_select_terms=include_select_terms
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _get_modules_metadata(self):
|
|
115
|
+
return {
|
|
116
|
+
module_name: {
|
|
117
|
+
model._meta.model_name: ModelMetadata(
|
|
118
|
+
model, module_name, self.included_modules
|
|
119
|
+
)
|
|
120
|
+
for model in self._get_schema_module(
|
|
121
|
+
module_name
|
|
122
|
+
).models.__dict__.values()
|
|
123
|
+
if model.__class__.__name__ == "ModelBase"
|
|
124
|
+
and model.__name__ not in ["Registry", "ORM"]
|
|
125
|
+
and not model._meta.abstract
|
|
126
|
+
and model.__get_schema_name__() == module_name
|
|
127
|
+
}
|
|
128
|
+
for module_name in self.included_modules
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def _get_module_set_info(self):
|
|
132
|
+
# TODO: rely on schemamodule table for this
|
|
133
|
+
module_set_info = []
|
|
134
|
+
for module_name in self.included_modules:
|
|
135
|
+
module = self._get_schema_module(module_name)
|
|
136
|
+
module_set_info.append(
|
|
137
|
+
{"id": 0, "name": module_name, "version": module.__version__}
|
|
138
|
+
)
|
|
139
|
+
return module_set_info
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _get_schema_module(module_name):
|
|
143
|
+
return importlib.import_module(get_schema_module_name(module_name))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class FieldMetadata:
|
|
148
|
+
schema_name: str
|
|
149
|
+
model_name: str
|
|
150
|
+
field_name: str
|
|
151
|
+
type: str
|
|
152
|
+
is_link_table: bool
|
|
153
|
+
column: str | None = None
|
|
154
|
+
relation_type: str | None = None
|
|
155
|
+
related_schema_name: str | None = None
|
|
156
|
+
related_model_name: str | None = None
|
|
157
|
+
related_field_name: str | None = None
|
|
158
|
+
through: dict | None = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class ModelRelations:
|
|
162
|
+
def __init__(self, fields: list[ForeignObjectRel]) -> None:
|
|
163
|
+
self.many_to_one = {}
|
|
164
|
+
self.one_to_many = {}
|
|
165
|
+
self.many_to_many = {}
|
|
166
|
+
self.one_to_one = {}
|
|
167
|
+
|
|
168
|
+
for field in fields:
|
|
169
|
+
if field.many_to_one:
|
|
170
|
+
self.many_to_one.update({field.name: field})
|
|
171
|
+
elif field.one_to_many:
|
|
172
|
+
self.one_to_many.update({field.name: field})
|
|
173
|
+
elif field.many_to_many:
|
|
174
|
+
self.many_to_many.update({field.name: field})
|
|
175
|
+
elif field.one_to_one:
|
|
176
|
+
self.one_to_one.update({field.name: field})
|
|
177
|
+
|
|
178
|
+
self.all = {
|
|
179
|
+
**self.many_to_one,
|
|
180
|
+
**self.one_to_many,
|
|
181
|
+
**self.many_to_many,
|
|
182
|
+
**self.one_to_one,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ModelMetadata:
|
|
187
|
+
def __init__(self, model, module_name: str, included_modules: list[str]) -> None:
|
|
188
|
+
self.model = model
|
|
189
|
+
self.class_name = model.__name__
|
|
190
|
+
self.module_name = module_name
|
|
191
|
+
self.model_name = model._meta.model_name
|
|
192
|
+
self.table_name = model._meta.db_table
|
|
193
|
+
self.included_modules = included_modules
|
|
194
|
+
self.fields, self.relations = self._get_fields_metadata(self.model)
|
|
195
|
+
|
|
196
|
+
def to_dict(
|
|
197
|
+
self, include_django_objects: bool = True, include_select_terms: bool = True
|
|
198
|
+
):
|
|
199
|
+
_dict = {
|
|
200
|
+
"fields": self.fields.copy(),
|
|
201
|
+
"class_name": self.class_name,
|
|
202
|
+
"table_name": self.table_name,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
select_terms = self.select_terms if include_select_terms else []
|
|
206
|
+
|
|
207
|
+
for field_name in self.fields.keys():
|
|
208
|
+
_dict["fields"][field_name] = _dict["fields"][field_name].__dict__
|
|
209
|
+
if field_name in select_terms:
|
|
210
|
+
_dict["fields"][field_name].update(
|
|
211
|
+
{"select_term": select_terms[field_name]}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if include_django_objects:
|
|
215
|
+
_dict.update({"model": self.model})
|
|
216
|
+
|
|
217
|
+
return _dict
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def select_terms(self):
|
|
221
|
+
return (
|
|
222
|
+
DjangoQueryBuilder(self.module_name, self.model_name)
|
|
223
|
+
.add_all_sub_queries()
|
|
224
|
+
.extract_select_terms()
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def _get_fields_metadata(self, model):
|
|
228
|
+
related_fields = []
|
|
229
|
+
fields_metadata: dict[str, FieldMetadata] = {}
|
|
230
|
+
|
|
231
|
+
for field in model._meta.get_fields():
|
|
232
|
+
field_metadata = self._get_field_metadata(model, field)
|
|
233
|
+
if field_metadata.related_schema_name is None:
|
|
234
|
+
fields_metadata.update({field.name: field_metadata})
|
|
235
|
+
|
|
236
|
+
if (
|
|
237
|
+
field_metadata.related_schema_name in self.included_modules
|
|
238
|
+
and field_metadata.schema_name in self.included_modules
|
|
239
|
+
):
|
|
240
|
+
related_fields.append(field)
|
|
241
|
+
|
|
242
|
+
model_relations_metadata = ModelRelations(related_fields)
|
|
243
|
+
|
|
244
|
+
related_fields_metadata = self._get_related_fields_metadata(
|
|
245
|
+
model, model_relations_metadata
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
fields_metadata = {**fields_metadata, **related_fields_metadata}
|
|
249
|
+
|
|
250
|
+
return fields_metadata, model_relations_metadata
|
|
251
|
+
|
|
252
|
+
def _get_related_fields_metadata(
|
|
253
|
+
self, model, model_relations_metadata: ModelRelations
|
|
254
|
+
):
|
|
255
|
+
related_fields: dict[str, FieldMetadata] = {}
|
|
256
|
+
|
|
257
|
+
# Many to one (foreign key defined in the model)
|
|
258
|
+
for link_field_name, link_field in model_relations_metadata.many_to_one.items():
|
|
259
|
+
related_fields.update(
|
|
260
|
+
{f"{link_field_name}": self._get_field_metadata(model, link_field)}
|
|
261
|
+
)
|
|
262
|
+
for field in link_field.related_model._meta.fields:
|
|
263
|
+
related_fields.update(
|
|
264
|
+
{
|
|
265
|
+
f"{link_field_name}__{field.name}": self._get_field_metadata(
|
|
266
|
+
model, field
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# One to many (foreign key defined in the related model)
|
|
272
|
+
for relation_name, relation in model_relations_metadata.one_to_many.items():
|
|
273
|
+
# exclude self reference as it is already included in the many to one
|
|
274
|
+
if relation.related_model == model:
|
|
275
|
+
continue
|
|
276
|
+
related_fields.update(
|
|
277
|
+
{f"{relation_name}": self._get_field_metadata(model, relation.field)}
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# One to one
|
|
281
|
+
for link_field_name, link_field in model_relations_metadata.one_to_one.items():
|
|
282
|
+
related_fields.update(
|
|
283
|
+
{f"{link_field_name}": self._get_field_metadata(model, link_field)}
|
|
284
|
+
)
|
|
285
|
+
for field in link_field.related_model._meta.fields:
|
|
286
|
+
related_fields.update(
|
|
287
|
+
{
|
|
288
|
+
f"{link_field_name}__{field.name}": self._get_field_metadata(
|
|
289
|
+
model, field
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Many to many
|
|
295
|
+
for (
|
|
296
|
+
link_field_name,
|
|
297
|
+
link_field,
|
|
298
|
+
) in model_relations_metadata.many_to_many.items():
|
|
299
|
+
related_fields.update(
|
|
300
|
+
{f"{link_field_name}": self._get_field_metadata(model, link_field)}
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return related_fields
|
|
304
|
+
|
|
305
|
+
def _get_field_metadata(self, model, field: Field):
|
|
306
|
+
from lnschema_core.models import LinkORM
|
|
307
|
+
|
|
308
|
+
internal_type = field.get_internal_type()
|
|
309
|
+
model_name = field.model._meta.model_name
|
|
310
|
+
relation_type = self._get_relation_type(model, field)
|
|
311
|
+
if field.related_model is None:
|
|
312
|
+
schema_name = field.model.__get_schema_name__()
|
|
313
|
+
related_model_name = None
|
|
314
|
+
related_schema_name = None
|
|
315
|
+
related_field_name = None
|
|
316
|
+
field_name = field.name
|
|
317
|
+
else:
|
|
318
|
+
related_model_name = field.related_model._meta.model_name
|
|
319
|
+
related_schema_name = field.related_model.__get_schema_name__()
|
|
320
|
+
schema_name = field.model.__get_schema_name__()
|
|
321
|
+
related_field_name = field.remote_field.name
|
|
322
|
+
field_name = field.name
|
|
323
|
+
|
|
324
|
+
if relation_type in ["one-to-many"]:
|
|
325
|
+
# For a one-to-many relation, the field belong
|
|
326
|
+
# to the other model as a foreign key.
|
|
327
|
+
# To make usage similar to other relation types
|
|
328
|
+
# we need to invert model and related model.
|
|
329
|
+
schema_name, related_schema_name = related_schema_name, schema_name
|
|
330
|
+
model_name, related_model_name = related_model_name, model_name
|
|
331
|
+
field_name, related_field_name = related_field_name, field_name
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
column = None
|
|
335
|
+
if relation_type not in ["many-to-many", "one-to-one", "one-to-many"]:
|
|
336
|
+
column = field.column
|
|
337
|
+
|
|
338
|
+
through = None
|
|
339
|
+
if relation_type == "many-to-many":
|
|
340
|
+
through = self._get_through(model, field)
|
|
341
|
+
|
|
342
|
+
return FieldMetadata(
|
|
343
|
+
schema_name,
|
|
344
|
+
model_name,
|
|
345
|
+
field_name,
|
|
346
|
+
internal_type,
|
|
347
|
+
issubclass(field.model, LinkORM),
|
|
348
|
+
column,
|
|
349
|
+
relation_type,
|
|
350
|
+
related_schema_name,
|
|
351
|
+
related_model_name,
|
|
352
|
+
related_field_name,
|
|
353
|
+
through,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _get_through(model, field_or_rel: ManyToManyField | ManyToManyRel):
|
|
358
|
+
table_name = model._meta.db_table
|
|
359
|
+
related_table_name = field_or_rel.related_model._meta.db_table
|
|
360
|
+
|
|
361
|
+
if isinstance(field_or_rel, ManyToManyField):
|
|
362
|
+
return {
|
|
363
|
+
"link_table_name": field_or_rel.remote_field.through._meta.db_table,
|
|
364
|
+
table_name: field_or_rel.m2m_column_name(),
|
|
365
|
+
related_table_name: field_or_rel.m2m_reverse_name(),
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if isinstance(field_or_rel, ManyToManyRel):
|
|
369
|
+
return {
|
|
370
|
+
"link_table_name": field_or_rel.through._meta.db_table,
|
|
371
|
+
table_name: field_or_rel.field.m2m_column_name(),
|
|
372
|
+
related_table_name: field_or_rel.field.m2m_reverse_name(),
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def _get_relation_type(model, field: Field):
|
|
377
|
+
if field.many_to_one:
|
|
378
|
+
# defined in the model
|
|
379
|
+
if model == field.model:
|
|
380
|
+
return "many-to-one"
|
|
381
|
+
# defined in the related model
|
|
382
|
+
else:
|
|
383
|
+
return "one-to-many"
|
|
384
|
+
elif field.one_to_many:
|
|
385
|
+
return "one-to-many"
|
|
386
|
+
elif field.many_to_many:
|
|
387
|
+
return "many-to-many"
|
|
388
|
+
elif field.one_to_one:
|
|
389
|
+
return "one-to-one"
|
|
390
|
+
else:
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class DjangoQueryBuilder:
|
|
395
|
+
def __init__(
|
|
396
|
+
self, module_name: str, model_name: str, query_set: QuerySet | None = None
|
|
397
|
+
) -> None:
|
|
398
|
+
self.schema_metadata = SchemaMetadata()
|
|
399
|
+
self.module_name = module_name
|
|
400
|
+
self.model_name = model_name
|
|
401
|
+
self.model_metadata = self.schema_metadata.modules[module_name][model_name]
|
|
402
|
+
self.query_set = query_set if query_set else self.model_metadata.model.objects
|
|
403
|
+
|
|
404
|
+
def add_all_sub_queries(self):
|
|
405
|
+
all_fields = self.model_metadata.fields
|
|
406
|
+
included_relations = [
|
|
407
|
+
field_name
|
|
408
|
+
for field_name, field in all_fields.items()
|
|
409
|
+
if field.relation_type is not None
|
|
410
|
+
]
|
|
411
|
+
self.add_sub_queries(included_relations)
|
|
412
|
+
return self
|
|
413
|
+
|
|
414
|
+
def add_sub_queries(self, included_relations: list[str]):
|
|
415
|
+
sub_queries = {
|
|
416
|
+
f"annotated_{relation_name}": self._get_sub_query(
|
|
417
|
+
self.model_metadata.fields[relation_name]
|
|
418
|
+
)
|
|
419
|
+
for relation_name in included_relations
|
|
420
|
+
}
|
|
421
|
+
self.query_set = self.query_set.annotate(**sub_queries)
|
|
422
|
+
return self
|
|
423
|
+
|
|
424
|
+
def extract_select_terms(self):
|
|
425
|
+
parsed = sqlparse.parse(self.sql_query)
|
|
426
|
+
select_found = False
|
|
427
|
+
select_terms = {}
|
|
428
|
+
|
|
429
|
+
def get_name(identifier):
|
|
430
|
+
name = identifier.get_name()
|
|
431
|
+
return name if name is not None else str(identifier).split(".")
|
|
432
|
+
|
|
433
|
+
for token in parsed[0].tokens:
|
|
434
|
+
if token.ttype is DML and token.value.upper() == "SELECT":
|
|
435
|
+
select_found = True
|
|
436
|
+
elif select_found and isinstance(token, IdentifierList):
|
|
437
|
+
for identifier in token.get_identifiers():
|
|
438
|
+
select_terms[get_name(identifier)] = str(identifier)
|
|
439
|
+
elif select_found and isinstance(token, Identifier):
|
|
440
|
+
select_terms[get_name(token)] = str(token)
|
|
441
|
+
elif token.ttype is Keyword:
|
|
442
|
+
if token.value.upper() in ["FROM", "WHERE", "GROUP BY", "ORDER BY"]:
|
|
443
|
+
break
|
|
444
|
+
|
|
445
|
+
return select_terms
|
|
446
|
+
|
|
447
|
+
def _get_sub_query(self, field_metadata: FieldMetadata):
|
|
448
|
+
module_name = field_metadata.related_schema_name
|
|
449
|
+
model_name = field_metadata.related_model_name
|
|
450
|
+
field_name = field_metadata.related_field_name
|
|
451
|
+
model_metadata = self.schema_metadata.modules[module_name][model_name]
|
|
452
|
+
query_set = model_metadata.model.objects.get_queryset()
|
|
453
|
+
select = {
|
|
454
|
+
field_name: field_name
|
|
455
|
+
for field_name in model_metadata.fields.keys()
|
|
456
|
+
if model_metadata.fields[field_name].relation_type is None
|
|
457
|
+
and "__" not in field_name
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if field_metadata.relation_type in ["many-to-many", "one-to-many"]:
|
|
461
|
+
return ArraySubquery(
|
|
462
|
+
Subquery(
|
|
463
|
+
query_set.filter(**{field_name: OuterRef("pk")}).values(
|
|
464
|
+
data=JSONObject(**select)
|
|
465
|
+
)[:5]
|
|
466
|
+
)
|
|
467
|
+
)
|
|
468
|
+
if field_metadata.relation_type in ["many-to-one", "one-to-one"]:
|
|
469
|
+
return Subquery(
|
|
470
|
+
query_set.filter(**{field_name: OuterRef("pk")}).values(
|
|
471
|
+
data=JSONObject(**select)
|
|
472
|
+
)[:5]
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
@property
|
|
476
|
+
def sql_query(self):
|
|
477
|
+
sql_template, params = self.query_set.query.sql_with_params()
|
|
478
|
+
sql_query = sql_template % tuple(f"'{p}'" for p in params)
|
|
479
|
+
return sql_query.replace("annotated_", "")
|
|
@@ -67,8 +67,8 @@ class AWSCredentialsManager:
|
|
|
67
67
|
def _path_inject_options(self, path: S3Path, credentials: dict) -> S3Path:
|
|
68
68
|
if credentials == {}:
|
|
69
69
|
# credentials were specified manually for the path
|
|
70
|
-
if "anon" in path.
|
|
71
|
-
anon = path.
|
|
70
|
+
if "anon" in path.storage_options:
|
|
71
|
+
anon = path.storage_options["anon"]
|
|
72
72
|
elif path.fs.key is not None and path.fs.secret is not None:
|
|
73
73
|
anon = False
|
|
74
74
|
else:
|
|
@@ -77,8 +77,8 @@ class AWSCredentialsManager:
|
|
|
77
77
|
else:
|
|
78
78
|
connection_options = credentials
|
|
79
79
|
|
|
80
|
-
if "cache_regions" in path.
|
|
81
|
-
cache_regions = path.
|
|
80
|
+
if "cache_regions" in path.storage_options:
|
|
81
|
+
cache_regions = path.storage_options["cache_regions"]
|
|
82
82
|
else:
|
|
83
83
|
cache_regions = True
|
|
84
84
|
|