lamindb_setup 0.73.3__tar.gz → 0.74.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.
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/.github/workflows/build.yml +12 -14
- lamindb_setup-0.74.1/.github/workflows/doc-changes.yml +23 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/PKG-INFO +3 -2
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/changelog.md +1 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/__init__.py +1 -1
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_connect_instance.py +0 -6
- lamindb_setup-0.74.1/lamindb_setup/_schema_metadata.py +378 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_hub_client.py +2 -2
- lamindb_setup-0.74.1/lamindb_setup/core/_private_django_api.py +88 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings.py +33 -4
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings_instance.py +2 -1
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/hashing.py +29 -2
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/noxfile.py +4 -1
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/pyproject.toml +2 -1
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-cloud/test_init_instance.py +18 -1
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-local/conftest.py +2 -2
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-local/test_update_schema_in_hub.py +62 -30
- lamindb_setup-0.74.1/tests/hub-prod/test_global_settings.py +55 -0
- lamindb_setup-0.73.3/.github/workflows/latest-changes.jinja2 +0 -2
- lamindb_setup-0.73.3/.github/workflows/latest-changes.yml +0 -25
- lamindb_setup-0.73.3/lamindb_setup/_schema_metadata.py +0 -479
- lamindb_setup-0.73.3/tests/hub-prod/test_auto_connect.py +0 -12
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/.gitignore +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/.pre-commit-config.yaml +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/LICENSE +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/README.md +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/test-multi-session.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-cloud/test_notebooks.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-cache-management.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-empty-init.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-import-schema.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/hub-prod/test_notebooks2.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/index.md +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/notebooks.md +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/docs/reference.md +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_cache.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_check.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_check_setup.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_close.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_delete.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_django.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_exportdb.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_importdb.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_init_instance.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_migrate.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_register_instance.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_schema.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_set_managed_storage.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_setup_user.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/_silence_loggers.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/__init__.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_aws_credentials.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_aws_storage.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_deprecated.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_docs.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_hub_core.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_hub_crud.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_hub_utils.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings_load.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings_save.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings_storage.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings_store.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_settings_user.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/django.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/exceptions.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/types.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/lamindb_setup/core/upath.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-cloud/test_connect_instance.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-cloud/test_delete_instance.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-cloud/test_login.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-cloud/test_migrate.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-cloud/test_set_storage.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-local/test_all.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-prod/conftest.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-prod/test_django.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/hub-prod/test_upath.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/storage/test_hashing.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/storage/test_storage_access.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/storage/test_storage_basis.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/storage/test_storage_stats.py +0 -0
- {lamindb_setup-0.73.3 → lamindb_setup-0.74.1}/tests/storage/test_to_url.py +0 -0
|
@@ -17,14 +17,14 @@ jobs:
|
|
|
17
17
|
- uses: actions/checkout@v3
|
|
18
18
|
- uses: actions/setup-python@v4
|
|
19
19
|
with:
|
|
20
|
-
python-version: "3.
|
|
20
|
+
python-version: "3.10" # run one job on 3.9
|
|
21
21
|
cache: "pip"
|
|
22
22
|
- uses: aws-actions/configure-aws-credentials@v2
|
|
23
23
|
with:
|
|
24
24
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
25
25
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
26
26
|
aws-region: eu-central-1
|
|
27
|
-
- run: pip install -
|
|
27
|
+
- run: pip install "laminci@git+https://x-access-token:${{ secrets.LAMIN_BUILD_DOCS }}@github.com/laminlabs/laminci"
|
|
28
28
|
- run: nox -s "install(group='hub-prod')"
|
|
29
29
|
- run: nox -s "build(lamin_env='prod', group='hub-prod')"
|
|
30
30
|
- uses: actions/upload-artifact@v2
|
|
@@ -42,7 +42,7 @@ jobs:
|
|
|
42
42
|
- lamin_env: "prod"
|
|
43
43
|
python-version: "3.11"
|
|
44
44
|
- lamin_env: "staging"
|
|
45
|
-
python-version: "3.10" # test on 3.
|
|
45
|
+
python-version: "3.10" # test on 3.9
|
|
46
46
|
timeout-minutes: 6
|
|
47
47
|
steps:
|
|
48
48
|
- uses: aws-actions/configure-aws-credentials@v2
|
|
@@ -56,10 +56,9 @@ jobs:
|
|
|
56
56
|
python-version: ${{ matrix.python-version }}
|
|
57
57
|
cache: "pip"
|
|
58
58
|
cache-dependency-path: ".github/workflows/build.yml"
|
|
59
|
-
-
|
|
60
|
-
uses: actions/checkout@v4
|
|
59
|
+
- uses: actions/checkout@v4
|
|
61
60
|
with:
|
|
62
|
-
repository: laminlabs/
|
|
61
|
+
repository: laminlabs/laminhub
|
|
63
62
|
token: ${{ secrets.GH_TOKEN_DEPLOY_LAMINAPP }}
|
|
64
63
|
path: laminhub
|
|
65
64
|
ref: main
|
|
@@ -76,7 +75,7 @@ jobs:
|
|
|
76
75
|
run: docker pull postgres:latest && docker image save postgres:latest --output ~/postgres.tar
|
|
77
76
|
- if: steps.cache-postgres.outputs.cache-hit == 'true'
|
|
78
77
|
run: docker image load --input ~/postgres.tar
|
|
79
|
-
- run: pip install -
|
|
78
|
+
- run: pip install "laminci@git+https://x-access-token:${{ secrets.LAMIN_BUILD_DOCS }}@github.com/laminlabs/laminci"
|
|
80
79
|
# account for in a different job
|
|
81
80
|
# - uses: "google-github-actions/auth@v0"
|
|
82
81
|
# with:
|
|
@@ -98,10 +97,10 @@ jobs:
|
|
|
98
97
|
- uses: actions/checkout@v4
|
|
99
98
|
- uses: actions/setup-python@v4
|
|
100
99
|
with:
|
|
101
|
-
python-version: "3.
|
|
100
|
+
python-version: "3.9"
|
|
102
101
|
cache: "pip"
|
|
103
102
|
cache-dependency-path: ".github/workflows/build.yml"
|
|
104
|
-
- run: pip install -
|
|
103
|
+
- run: pip install "laminci@git+https://x-access-token:${{ secrets.LAMIN_BUILD_DOCS }}@github.com/laminlabs/laminci"
|
|
105
104
|
- run: nox -s "install(group='storage')"
|
|
106
105
|
- run: nox -s lint
|
|
107
106
|
- run: nox -s storage
|
|
@@ -125,10 +124,9 @@ jobs:
|
|
|
125
124
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
126
125
|
aws-region: eu-central-1
|
|
127
126
|
- uses: actions/checkout@v4
|
|
128
|
-
-
|
|
129
|
-
uses: actions/checkout@v4
|
|
127
|
+
- uses: actions/checkout@v4
|
|
130
128
|
with:
|
|
131
|
-
repository: laminlabs/
|
|
129
|
+
repository: laminlabs/laminhub
|
|
132
130
|
token: ${{ secrets.GH_TOKEN_DEPLOY_LAMINAPP }}
|
|
133
131
|
path: laminhub
|
|
134
132
|
ref: main
|
|
@@ -143,7 +141,7 @@ jobs:
|
|
|
143
141
|
python-version: "3.11" # we need to run everything for coverage on 3.11
|
|
144
142
|
cache: "pip"
|
|
145
143
|
cache-dependency-path: ".github/workflows/build.yml"
|
|
146
|
-
- run: pip install -
|
|
144
|
+
- run: pip install "laminci@git+https://x-access-token:${{ secrets.LAMIN_BUILD_DOCS }}@github.com/laminlabs/laminci"
|
|
147
145
|
- run: nox -s "install(group='hub-local')"
|
|
148
146
|
- id: cache-supabase
|
|
149
147
|
uses: actions/cache@v3
|
|
@@ -206,7 +204,7 @@ jobs:
|
|
|
206
204
|
python-version: "3.10"
|
|
207
205
|
cache: "pip"
|
|
208
206
|
cache-dependency-path: ".github/workflows/build.yml"
|
|
209
|
-
- run: pip install -
|
|
207
|
+
- run: pip install "laminci@git+https://x-access-token:${{ secrets.LAMIN_BUILD_DOCS }}@github.com/laminlabs/laminci"
|
|
210
208
|
- run: nox -s "install(group='docs')"
|
|
211
209
|
- uses: actions/download-artifact@v2
|
|
212
210
|
- run: nox -s docs
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: doc-changes
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
types:
|
|
8
|
+
- closed
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
latest-changes:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.11"
|
|
18
|
+
- run: pip install "laminci[doc-changes]@git+https://x-access-token:${{ secrets.LAMIN_BUILD_DOCS }}@github.com/laminlabs/laminci"
|
|
19
|
+
- run: laminci doc-changes
|
|
20
|
+
env:
|
|
21
|
+
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
docs_token: ${{ secrets.LAMIN_BUILD_DOCS }}
|
|
23
|
+
changelog_file: lamin-docs/docs/changelog/soon/lamindb.md
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lamindb_setup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.74.1
|
|
4
4
|
Summary: Setup & configure LaminDB.
|
|
5
5
|
Author-email: Lamin Labs <laminlabs@gmail.com>
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: lnschema_core>=0.51.0
|
|
8
8
|
Requires-Dist: lamin_utils>=0.3.3
|
|
9
|
-
Requires-Dist: django>4.2,<5.
|
|
9
|
+
Requires-Dist: django>4.2,<5.3.0
|
|
10
10
|
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
|
|
@@ -14,6 +14,7 @@ Requires-Dist: requests
|
|
|
14
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
|
+
Requires-Dist: psutil
|
|
17
18
|
Requires-Dist: urllib3<2 ; extra == "aws"
|
|
18
19
|
Requires-Dist: aiobotocore[boto3]>=2.5.4,<3.0.0 ; extra == "aws"
|
|
19
20
|
Requires-Dist: s3fs>=2023.12.2,<=2024.3.1 ; extra == "aws"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<!-- prettier-ignore -->
|
|
4
4
|
Name | PR | Developer | Date | Version
|
|
5
5
|
--- | --- | --- | --- | ---
|
|
6
|
+
🐛 Only search local managed storage locations | [788](https://github.com/laminlabs/lamindb-setup/pull/788) | [falexwolf](https://github.com/falexwolf) | 2024-07-03 |
|
|
6
7
|
💚 Fix test | [784](https://github.com/laminlabs/lamindb-setup/pull/784) | [falexwolf](https://github.com/falexwolf) | 2024-06-26 | 0.73.3
|
|
7
8
|
♻️ Save schema to hub upon init | [782](https://github.com/laminlabs/lamindb-setup/pull/782) | [falexwolf](https://github.com/falexwolf) | 2024-06-24 |
|
|
8
9
|
💚 Fix schema serialization test | [781](https://github.com/laminlabs/lamindb-setup/pull/781) | [falexwolf](https://github.com/falexwolf) | 2024-06-24 |
|
|
@@ -270,12 +270,6 @@ def load(
|
|
|
270
270
|
This is exactly the same as ``ln.connect()`` except for that
|
|
271
271
|
``ln.connect()`` doesn't change the state of ``auto-connect``.
|
|
272
272
|
"""
|
|
273
|
-
# enable the message in the next release
|
|
274
|
-
logger.warning(
|
|
275
|
-
"`lamin connect` replaces `lamin load`, which will be removed in a future"
|
|
276
|
-
" version\nif you still want to auto-connect to an instance upon lamindb"
|
|
277
|
-
" import, call: `lamin load` on the CLI"
|
|
278
|
-
)
|
|
279
273
|
result = connect(slug, db=db, storage=storage)
|
|
280
274
|
settings.auto_connect = True
|
|
281
275
|
return result
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import importlib
|
|
5
|
+
import json
|
|
6
|
+
from typing import TYPE_CHECKING, Literal
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from django.db.models import (
|
|
10
|
+
Field,
|
|
11
|
+
ForeignKey,
|
|
12
|
+
ForeignObjectRel,
|
|
13
|
+
ManyToManyField,
|
|
14
|
+
ManyToManyRel,
|
|
15
|
+
ManyToOneRel,
|
|
16
|
+
OneToOneField,
|
|
17
|
+
OneToOneRel,
|
|
18
|
+
)
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
|
|
21
|
+
from lamindb_setup import settings
|
|
22
|
+
from lamindb_setup._init_instance import get_schema_module_name
|
|
23
|
+
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from supabase import Client
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def update_schema_in_hub() -> tuple[bool, UUID, dict]:
|
|
30
|
+
return call_with_fallback_auth(_synchronize_schema)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _synchronize_schema(client: Client) -> tuple[bool, UUID, dict]:
|
|
34
|
+
schema_metadata = _SchemaHandler()
|
|
35
|
+
schema_metadata_dict = schema_metadata.to_json()
|
|
36
|
+
schema_uuid = _dict_to_uuid(schema_metadata_dict)
|
|
37
|
+
schema = _get_schema_by_id(schema_uuid, client)
|
|
38
|
+
|
|
39
|
+
is_new = schema is None
|
|
40
|
+
if is_new:
|
|
41
|
+
module_set_info = schema_metadata._get_module_set_info()
|
|
42
|
+
module_ids = "-".join(str(module_info["id"]) for module_info in module_set_info)
|
|
43
|
+
schema = (
|
|
44
|
+
client.table("schema")
|
|
45
|
+
.insert(
|
|
46
|
+
{
|
|
47
|
+
"id": schema_uuid.hex,
|
|
48
|
+
"module_ids": module_ids,
|
|
49
|
+
"module_set_info": module_set_info,
|
|
50
|
+
"schema_json": schema_metadata_dict,
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
.execute()
|
|
54
|
+
.data[0]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
instance_response = (
|
|
58
|
+
client.table("instance")
|
|
59
|
+
.update({"schema_id": schema_uuid.hex})
|
|
60
|
+
.eq("id", settings.instance._id.hex)
|
|
61
|
+
.execute()
|
|
62
|
+
)
|
|
63
|
+
assert (
|
|
64
|
+
len(instance_response.data) == 1
|
|
65
|
+
), f"schema of instance {settings.instance._id.hex} could not be updated with schema {schema_uuid.hex}"
|
|
66
|
+
|
|
67
|
+
return is_new, schema_uuid, schema
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_schema_by_id(id: UUID):
|
|
71
|
+
return call_with_fallback_auth(_get_schema_by_id, id=id)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _get_schema_by_id(id: UUID, client: Client):
|
|
75
|
+
response = client.table("schema").select("*").eq("id", id.hex).execute()
|
|
76
|
+
if len(response.data) == 0:
|
|
77
|
+
return None
|
|
78
|
+
return response.data[0]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _dict_to_uuid(dict: dict):
|
|
82
|
+
encoded = json.dumps(dict, sort_keys=True).encode("utf-8")
|
|
83
|
+
hash = hashlib.md5(encoded).digest()
|
|
84
|
+
uuid = UUID(bytes=hash[:16])
|
|
85
|
+
return uuid
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
RelationType = Literal["many-to-one", "one-to-many", "many-to-many", "one-to-one"]
|
|
89
|
+
Type = Literal[
|
|
90
|
+
"ForeignKey",
|
|
91
|
+
"CharField",
|
|
92
|
+
"DateTimeField",
|
|
93
|
+
"AutoField",
|
|
94
|
+
"BooleanField",
|
|
95
|
+
"BigIntegerField",
|
|
96
|
+
"SmallIntegerField",
|
|
97
|
+
"TextField",
|
|
98
|
+
"BigAutoField",
|
|
99
|
+
"ManyToManyField",
|
|
100
|
+
"IntegerField",
|
|
101
|
+
"OneToOneField",
|
|
102
|
+
"JSONField",
|
|
103
|
+
"DateField",
|
|
104
|
+
"FloatField",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Through(BaseModel):
|
|
109
|
+
left_key: str
|
|
110
|
+
right_key: str
|
|
111
|
+
link_table_name: str | None = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class FieldMetadata(BaseModel):
|
|
115
|
+
type: Type
|
|
116
|
+
column_name: str | None = None
|
|
117
|
+
through: Through | None = None
|
|
118
|
+
field_name: str
|
|
119
|
+
model_name: str
|
|
120
|
+
schema_name: str
|
|
121
|
+
is_link_table: bool
|
|
122
|
+
relation_type: RelationType | None = None
|
|
123
|
+
related_field_name: str | None = None
|
|
124
|
+
related_model_name: str | None = None
|
|
125
|
+
related_schema_name: str | None = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class _ModelHandler:
|
|
129
|
+
def __init__(self, model, module_name: str, included_modules: list[str]) -> None:
|
|
130
|
+
self.model = model
|
|
131
|
+
self.class_name = model.__name__
|
|
132
|
+
self.module_name = module_name
|
|
133
|
+
self.model_name = model._meta.model_name
|
|
134
|
+
self.table_name = model._meta.db_table
|
|
135
|
+
self.included_modules = included_modules
|
|
136
|
+
self.fields = self._get_fields_metadata(self.model)
|
|
137
|
+
|
|
138
|
+
def to_dict(self, include_django_objects: bool = True):
|
|
139
|
+
_dict = {
|
|
140
|
+
"fields": self.fields.copy(),
|
|
141
|
+
"class_name": self.class_name,
|
|
142
|
+
"table_name": self.table_name,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for field_name in self.fields.keys():
|
|
146
|
+
_dict["fields"][field_name] = _dict["fields"][field_name].__dict__
|
|
147
|
+
through = _dict["fields"][field_name]["through"]
|
|
148
|
+
if through is not None:
|
|
149
|
+
_dict["fields"][field_name]["through"] = through.__dict__
|
|
150
|
+
|
|
151
|
+
if include_django_objects:
|
|
152
|
+
_dict.update({"model": self.model})
|
|
153
|
+
|
|
154
|
+
return _dict
|
|
155
|
+
|
|
156
|
+
def _get_fields_metadata(self, model):
|
|
157
|
+
related_fields = []
|
|
158
|
+
fields_metadata: dict[str, FieldMetadata] = {}
|
|
159
|
+
|
|
160
|
+
for field in model._meta.get_fields():
|
|
161
|
+
field_metadata = self._get_field_metadata(model, field)
|
|
162
|
+
if field_metadata.related_schema_name is None:
|
|
163
|
+
fields_metadata.update({field.name: field_metadata})
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
field_metadata.related_schema_name in self.included_modules
|
|
167
|
+
and field_metadata.schema_name in self.included_modules
|
|
168
|
+
):
|
|
169
|
+
related_fields.append(field)
|
|
170
|
+
|
|
171
|
+
related_fields_metadata = self._get_related_fields_metadata(
|
|
172
|
+
model, related_fields
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
fields_metadata = {**fields_metadata, **related_fields_metadata}
|
|
176
|
+
|
|
177
|
+
return fields_metadata
|
|
178
|
+
|
|
179
|
+
def _get_related_fields_metadata(self, model, fields: list[ForeignObjectRel]):
|
|
180
|
+
related_fields: dict[str, FieldMetadata] = {}
|
|
181
|
+
|
|
182
|
+
for field in fields:
|
|
183
|
+
if field.many_to_one:
|
|
184
|
+
related_fields.update(
|
|
185
|
+
{f"{field.name}": self._get_field_metadata(model, field)}
|
|
186
|
+
)
|
|
187
|
+
elif field.one_to_many:
|
|
188
|
+
# exclude self reference as it is already included in the many to one
|
|
189
|
+
if field.related_model == model:
|
|
190
|
+
continue
|
|
191
|
+
related_fields.update(
|
|
192
|
+
{f"{field.name}": self._get_field_metadata(model, field.field)}
|
|
193
|
+
)
|
|
194
|
+
elif field.many_to_many:
|
|
195
|
+
related_fields.update(
|
|
196
|
+
{f"{field.name}": self._get_field_metadata(model, field)}
|
|
197
|
+
)
|
|
198
|
+
elif field.one_to_one:
|
|
199
|
+
related_fields.update(
|
|
200
|
+
{f"{field.name}": self._get_field_metadata(model, field)}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return related_fields
|
|
204
|
+
|
|
205
|
+
def _get_field_metadata(self, model, field: Field):
|
|
206
|
+
from lnschema_core.models import LinkORM
|
|
207
|
+
|
|
208
|
+
internal_type = field.get_internal_type()
|
|
209
|
+
model_name = field.model._meta.model_name
|
|
210
|
+
relation_type = self._get_relation_type(model, field)
|
|
211
|
+
if field.related_model is None:
|
|
212
|
+
schema_name = field.model.__get_schema_name__()
|
|
213
|
+
related_model_name = None
|
|
214
|
+
related_schema_name = None
|
|
215
|
+
related_field_name = None
|
|
216
|
+
field_name = field.name
|
|
217
|
+
else:
|
|
218
|
+
related_model_name = field.related_model._meta.model_name
|
|
219
|
+
related_schema_name = field.related_model.__get_schema_name__()
|
|
220
|
+
schema_name = field.model.__get_schema_name__()
|
|
221
|
+
related_field_name = field.remote_field.name
|
|
222
|
+
field_name = field.name
|
|
223
|
+
|
|
224
|
+
if relation_type in ["one-to-many"]:
|
|
225
|
+
# For a one-to-many relation, the field belong
|
|
226
|
+
# to the other model as a foreign key.
|
|
227
|
+
# To make usage similar to other relation types
|
|
228
|
+
# we need to invert model and related model.
|
|
229
|
+
schema_name, related_schema_name = related_schema_name, schema_name
|
|
230
|
+
model_name, related_model_name = related_model_name, model_name
|
|
231
|
+
field_name, related_field_name = related_field_name, field_name
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
column = None
|
|
235
|
+
if relation_type not in ["many-to-many", "one-to-many"]:
|
|
236
|
+
if not isinstance(field, ForeignObjectRel):
|
|
237
|
+
column = field.column
|
|
238
|
+
|
|
239
|
+
if relation_type is None:
|
|
240
|
+
through = None
|
|
241
|
+
elif relation_type == "many-to-many":
|
|
242
|
+
through = self._get_through_many_to_many(field)
|
|
243
|
+
else:
|
|
244
|
+
through = self._get_through(field)
|
|
245
|
+
|
|
246
|
+
return FieldMetadata(
|
|
247
|
+
schema_name=schema_name,
|
|
248
|
+
model_name=model_name,
|
|
249
|
+
field_name=field_name,
|
|
250
|
+
type=internal_type,
|
|
251
|
+
is_link_table=issubclass(field.model, LinkORM),
|
|
252
|
+
column_name=column,
|
|
253
|
+
relation_type=relation_type,
|
|
254
|
+
related_schema_name=related_schema_name,
|
|
255
|
+
related_model_name=related_model_name,
|
|
256
|
+
related_field_name=related_field_name,
|
|
257
|
+
through=through,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@staticmethod
|
|
261
|
+
def _get_through_many_to_many(field_or_rel: ManyToManyField | ManyToManyRel):
|
|
262
|
+
from lnschema_core.models import Registry
|
|
263
|
+
|
|
264
|
+
if isinstance(field_or_rel, ManyToManyField):
|
|
265
|
+
if field_or_rel.model != Registry:
|
|
266
|
+
return Through(
|
|
267
|
+
left_key=field_or_rel.m2m_column_name(),
|
|
268
|
+
right_key=field_or_rel.m2m_reverse_name(),
|
|
269
|
+
link_table_name=field_or_rel.remote_field.through._meta.db_table,
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
return Through(
|
|
273
|
+
left_key=field_or_rel.m2m_reverse_name(),
|
|
274
|
+
right_key=field_or_rel.m2m_column_name(),
|
|
275
|
+
link_table_name=field_or_rel.remote_field.through._meta.db_table,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if isinstance(field_or_rel, ManyToManyRel):
|
|
279
|
+
if field_or_rel.model != Registry:
|
|
280
|
+
return Through(
|
|
281
|
+
left_key=field_or_rel.field.m2m_reverse_name(),
|
|
282
|
+
right_key=field_or_rel.field.m2m_column_name(),
|
|
283
|
+
link_table_name=field_or_rel.through._meta.db_table,
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
return Through(
|
|
287
|
+
left_key=field_or_rel.field.m2m_column_name(),
|
|
288
|
+
right_key=field_or_rel.field.m2m_reverse_name(),
|
|
289
|
+
link_table_name=field_or_rel.through._meta.db_table,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def _get_through(
|
|
293
|
+
self, field_or_rel: ForeignKey | OneToOneField | ManyToOneRel | OneToOneRel
|
|
294
|
+
):
|
|
295
|
+
if isinstance(field_or_rel, ForeignObjectRel):
|
|
296
|
+
rel_1 = field_or_rel.field.related_fields[0][0]
|
|
297
|
+
rel_2 = field_or_rel.field.related_fields[0][1]
|
|
298
|
+
else:
|
|
299
|
+
rel_1 = field_or_rel.related_fields[0][0]
|
|
300
|
+
rel_2 = field_or_rel.related_fields[0][1]
|
|
301
|
+
|
|
302
|
+
if rel_1.model._meta.model_name == self.model._meta.model_name:
|
|
303
|
+
return Through(
|
|
304
|
+
left_key=rel_1.column,
|
|
305
|
+
right_key=rel_2.column,
|
|
306
|
+
)
|
|
307
|
+
else:
|
|
308
|
+
return Through(
|
|
309
|
+
left_key=rel_2.column,
|
|
310
|
+
right_key=rel_1.column,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def _get_relation_type(model, field: Field):
|
|
315
|
+
if field.many_to_one:
|
|
316
|
+
# defined in the model
|
|
317
|
+
if model == field.model:
|
|
318
|
+
return "many-to-one"
|
|
319
|
+
# defined in the related model
|
|
320
|
+
else:
|
|
321
|
+
return "one-to-many"
|
|
322
|
+
elif field.one_to_many:
|
|
323
|
+
return "one-to-many"
|
|
324
|
+
elif field.many_to_many:
|
|
325
|
+
return "many-to-many"
|
|
326
|
+
elif field.one_to_one:
|
|
327
|
+
return "one-to-one"
|
|
328
|
+
else:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class _SchemaHandler:
|
|
333
|
+
def __init__(self) -> None:
|
|
334
|
+
self.included_modules = ["core"] + list(settings.instance.schema)
|
|
335
|
+
self.modules = self._get_modules_metadata()
|
|
336
|
+
|
|
337
|
+
def to_dict(self, include_django_objects: bool = True):
|
|
338
|
+
return {
|
|
339
|
+
module_name: {
|
|
340
|
+
model_name: model.to_dict(include_django_objects)
|
|
341
|
+
for model_name, model in module.items()
|
|
342
|
+
}
|
|
343
|
+
for module_name, module in self.modules.items()
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def to_json(self):
|
|
347
|
+
return self.to_dict(include_django_objects=False)
|
|
348
|
+
|
|
349
|
+
def _get_modules_metadata(self):
|
|
350
|
+
return {
|
|
351
|
+
module_name: {
|
|
352
|
+
model._meta.model_name: _ModelHandler(
|
|
353
|
+
model, module_name, self.included_modules
|
|
354
|
+
)
|
|
355
|
+
for model in self._get_schema_module(
|
|
356
|
+
module_name
|
|
357
|
+
).models.__dict__.values()
|
|
358
|
+
if model.__class__.__name__ == "RecordMeta"
|
|
359
|
+
and model.__name__ not in ["Record", "ORM"]
|
|
360
|
+
and not model._meta.abstract
|
|
361
|
+
and model.__get_schema_name__() == module_name
|
|
362
|
+
}
|
|
363
|
+
for module_name in self.included_modules
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
def _get_module_set_info(self):
|
|
367
|
+
# TODO: rely on schemamodule table for this
|
|
368
|
+
module_set_info = []
|
|
369
|
+
for module_name in self.included_modules:
|
|
370
|
+
module = self._get_schema_module(module_name)
|
|
371
|
+
module_set_info.append(
|
|
372
|
+
{"id": 0, "name": module_name, "version": module.__version__}
|
|
373
|
+
)
|
|
374
|
+
return module_set_info
|
|
375
|
+
|
|
376
|
+
@staticmethod
|
|
377
|
+
def _get_schema_module(module_name):
|
|
378
|
+
return importlib.import_module(get_schema_module_name(module_name))
|
|
@@ -119,8 +119,8 @@ def call_with_fallback_auth(
|
|
|
119
119
|
for renew_token, fallback_env in [(False, False), (True, False), (False, True)]:
|
|
120
120
|
try:
|
|
121
121
|
if renew_token:
|
|
122
|
-
logger.
|
|
123
|
-
"
|
|
122
|
+
logger.warning(
|
|
123
|
+
"renewing expired lamin token: call `lamin login` to avoid this"
|
|
124
124
|
)
|
|
125
125
|
client = connect_hub_with_auth(
|
|
126
126
|
renew_token=renew_token, fallback_env=fallback_env
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_vscode_stubs_folder() -> Path | None:
|
|
8
|
+
# Possible locations of VSCode extensions
|
|
9
|
+
possible_locations = [
|
|
10
|
+
Path.home() / ".vscode" / "extensions", # Linux and macOS
|
|
11
|
+
Path.home() / ".vscode-server" / "extensions", # Remote development
|
|
12
|
+
Path(os.environ.get("APPDATA", "")) / "Code" / "User" / "extensions", # Windows
|
|
13
|
+
Path("/usr/share/code/resources/app/extensions"), # Some Linux distributions
|
|
14
|
+
]
|
|
15
|
+
for location in possible_locations:
|
|
16
|
+
if location.exists():
|
|
17
|
+
# Look for Pylance extension folder
|
|
18
|
+
pylance_folders = list(location.glob("ms-python.vscode-pylance-*"))
|
|
19
|
+
if pylance_folders:
|
|
20
|
+
# Sort to get the latest version
|
|
21
|
+
latest_pylance = sorted(pylance_folders)[-1]
|
|
22
|
+
stubs_folder = (
|
|
23
|
+
latest_pylance / "dist" / "bundled" / "stubs" / "django-stubs"
|
|
24
|
+
)
|
|
25
|
+
if stubs_folder.exists():
|
|
26
|
+
return stubs_folder
|
|
27
|
+
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def private_django_api(reverse=False):
|
|
32
|
+
from django import db
|
|
33
|
+
|
|
34
|
+
# the order here matters
|
|
35
|
+
# changing it might break the tests
|
|
36
|
+
attributes = [
|
|
37
|
+
"DoesNotExist",
|
|
38
|
+
"MultipleObjectsReturned",
|
|
39
|
+
"add_to_class",
|
|
40
|
+
"adelete",
|
|
41
|
+
"refresh_from_db",
|
|
42
|
+
"asave",
|
|
43
|
+
"clean",
|
|
44
|
+
"clean_fields",
|
|
45
|
+
"date_error_message",
|
|
46
|
+
"get_constraints",
|
|
47
|
+
"get_deferred_fields",
|
|
48
|
+
"prepare_database_save",
|
|
49
|
+
"save_base",
|
|
50
|
+
"serializable_value",
|
|
51
|
+
"unique_error_message",
|
|
52
|
+
"validate_constraints",
|
|
53
|
+
"validate_unique",
|
|
54
|
+
]
|
|
55
|
+
if reverse:
|
|
56
|
+
attributes.append("arefresh_from_db")
|
|
57
|
+
attributes.append("full_clean")
|
|
58
|
+
else:
|
|
59
|
+
attributes.append("a_refresh_from_db")
|
|
60
|
+
attributes.append("full__clean")
|
|
61
|
+
|
|
62
|
+
django_path = Path(db.__file__).parent.parent
|
|
63
|
+
|
|
64
|
+
encoding = "utf8" if os.name == "nt" else None
|
|
65
|
+
|
|
66
|
+
def prune_file(file_path):
|
|
67
|
+
content = file_path.read_text(encoding=encoding)
|
|
68
|
+
original_content = content
|
|
69
|
+
|
|
70
|
+
for attr in attributes:
|
|
71
|
+
old_name = f"_{attr}" if reverse else attr
|
|
72
|
+
new_name = attr if reverse else f"_{attr}"
|
|
73
|
+
content = content.replace(old_name, new_name)
|
|
74
|
+
|
|
75
|
+
if not reverse:
|
|
76
|
+
content = content.replace("Field_DoesNotExist", "FieldDoesNotExist")
|
|
77
|
+
content = content.replace("Object_DoesNotExist", "ObjectDoesNotExist")
|
|
78
|
+
|
|
79
|
+
if content != original_content:
|
|
80
|
+
file_path.write_text(content, encoding=encoding)
|
|
81
|
+
|
|
82
|
+
for file_path in django_path.rglob("*.py"):
|
|
83
|
+
prune_file(file_path)
|
|
84
|
+
|
|
85
|
+
pylance_path = find_vscode_stubs_folder()
|
|
86
|
+
if pylance_path is not None:
|
|
87
|
+
for file_path in pylance_path.rglob("*.pyi"):
|
|
88
|
+
prune_file(file_path)
|