lamindb_setup 0.73.2__tar.gz → 0.74.0__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.2 → lamindb_setup-0.74.0}/.github/workflows/build.yml +12 -14
- lamindb_setup-0.74.0/.github/workflows/doc-changes.yml +23 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/.gitignore +1 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/PKG-INFO +2 -2
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/changelog.md +5 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/__init__.py +1 -1
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_init_instance.py +9 -5
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_migrate.py +4 -10
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_register_instance.py +4 -0
- lamindb_setup-0.74.0/lamindb_setup/_schema_metadata.py +378 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_client.py +2 -2
- lamindb_setup-0.74.0/lamindb_setup/core/_private_django_api.py +88 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings.py +43 -9
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_instance.py +2 -1
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_storage.py +2 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/hashing.py +29 -2
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/noxfile.py +4 -1
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/pyproject.toml +1 -1
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_init_instance.py +18 -1
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-local/conftest.py +4 -6
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-local/test_update_schema_in_hub.py +65 -26
- lamindb_setup-0.74.0/tests/hub-prod/test_global_settings.py +55 -0
- lamindb_setup-0.73.2/.github/workflows/latest-changes.jinja2 +0 -2
- lamindb_setup-0.73.2/.github/workflows/latest-changes.yml +0 -25
- lamindb_setup-0.73.2/lamindb_setup/_schema_metadata.py +0 -479
- lamindb_setup-0.73.2/tests/hub-prod/test_auto_connect.py +0 -12
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/.pre-commit-config.yaml +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/LICENSE +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/README.md +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/test-multi-session.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/test_notebooks.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-cache-management.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-empty-init.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-import-schema.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test_notebooks2.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/index.md +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/notebooks.md +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/reference.md +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_cache.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_check.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_check_setup.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_close.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_connect_instance.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_delete.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_django.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_exportdb.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_importdb.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_schema.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_set_managed_storage.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_setup_user.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_silence_loggers.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/__init__.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_aws_credentials.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_aws_storage.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_deprecated.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_docs.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_core.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_crud.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_utils.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_load.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_save.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_store.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_user.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/django.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/exceptions.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/types.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/upath.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_connect_instance.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_delete_instance.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_login.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_migrate.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_set_storage.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-local/test_all.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/conftest.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/test_django.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/test_upath.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_hashing.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_storage_access.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_storage_basis.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_storage_stats.py +0 -0
- {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/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.0
|
|
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
|
|
@@ -3,6 +3,11 @@
|
|
|
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 |
|
|
7
|
+
💚 Fix test | [784](https://github.com/laminlabs/lamindb-setup/pull/784) | [falexwolf](https://github.com/falexwolf) | 2024-06-26 | 0.73.3
|
|
8
|
+
♻️ Save schema to hub upon init | [782](https://github.com/laminlabs/lamindb-setup/pull/782) | [falexwolf](https://github.com/falexwolf) | 2024-06-24 |
|
|
9
|
+
💚 Fix schema serialization test | [781](https://github.com/laminlabs/lamindb-setup/pull/781) | [falexwolf](https://github.com/falexwolf) | 2024-06-24 |
|
|
10
|
+
Better error message | [780](https://github.com/laminlabs/lamindb-setup/pull/780) | [falexwolf](https://github.com/falexwolf) | 2024-06-20 |
|
|
6
11
|
🐛 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
12
|
🐛 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
13
|
✨ Add .ome.zarr as composite suffix | [775](https://github.com/laminlabs/lamindb-setup/pull/775) | [sunnyosun](https://github.com/sunnyosun) | 2024-06-05 |
|
|
@@ -33,8 +33,7 @@ def get_schema_module_name(schema_name) -> str:
|
|
|
33
33
|
if module_spec is not None:
|
|
34
34
|
return name
|
|
35
35
|
raise ImportError(
|
|
36
|
-
f"Python package for '{schema_name}' is not installed,
|
|
37
|
-
f" {name_attempts}\nHave you installed the schema package using `pip install`?"
|
|
36
|
+
f"Python package for '{schema_name}' is not installed.\nIf your package is on PyPI, run `pip install {schema_name}`"
|
|
38
37
|
)
|
|
39
38
|
|
|
40
39
|
|
|
@@ -256,7 +255,10 @@ def init(
|
|
|
256
255
|
schema=schema,
|
|
257
256
|
uid=ssettings.uid,
|
|
258
257
|
)
|
|
259
|
-
|
|
258
|
+
register_on_hub = (
|
|
259
|
+
isettings.is_remote and instance_state != "instance-corrupted-or-deleted"
|
|
260
|
+
)
|
|
261
|
+
if register_on_hub:
|
|
260
262
|
init_instance_hub(isettings)
|
|
261
263
|
validate_sqlite_state(isettings)
|
|
262
264
|
isettings._persist()
|
|
@@ -270,8 +272,10 @@ def init(
|
|
|
270
272
|
"locked instance (to unlock and push changes to the cloud SQLite file,"
|
|
271
273
|
" call: lamin close)"
|
|
272
274
|
)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
if register_on_hub and isettings.dialect != "sqlite":
|
|
276
|
+
from ._schema_metadata import update_schema_in_hub
|
|
277
|
+
|
|
278
|
+
update_schema_in_hub()
|
|
275
279
|
settings.auto_connect = True
|
|
276
280
|
except Exception as e:
|
|
277
281
|
from ._delete import delete_by_isettings
|
|
@@ -75,21 +75,15 @@ class migrate:
|
|
|
75
75
|
from lamindb_setup.core._hub_client import call_with_fallback_auth
|
|
76
76
|
from lamindb_setup.core._hub_crud import (
|
|
77
77
|
select_collaborator,
|
|
78
|
-
select_instance_by_id,
|
|
79
78
|
update_instance,
|
|
80
79
|
)
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
instance = call_with_fallback_auth(
|
|
84
|
-
select_instance_by_id, instance_id=instance_id_str
|
|
85
|
-
)
|
|
86
|
-
instance_is_on_hub = instance is not None
|
|
87
|
-
if instance_is_on_hub:
|
|
81
|
+
if settings.instance.is_on_hub:
|
|
88
82
|
# double check that user is an admin, otherwise will fail below
|
|
89
|
-
#
|
|
83
|
+
# due to insufficient SQL permissions with cryptic error
|
|
90
84
|
collaborator = call_with_fallback_auth(
|
|
91
85
|
select_collaborator,
|
|
92
|
-
instance_id=
|
|
86
|
+
instance_id=settings.instance._id,
|
|
93
87
|
account_id=settings.user._uuid,
|
|
94
88
|
)
|
|
95
89
|
if collaborator is None or collaborator["role"] != "admin":
|
|
@@ -104,7 +98,7 @@ class migrate:
|
|
|
104
98
|
# this sets up django and deploys the migrations
|
|
105
99
|
setup_django(settings.instance, deploy_migrations=True)
|
|
106
100
|
# this populates the hub
|
|
107
|
-
if
|
|
101
|
+
if settings.instance.is_on_hub:
|
|
108
102
|
logger.important(f"updating lamindb version in hub: {lamindb.__version__}")
|
|
109
103
|
# TODO: integrate update of instance table within update_schema_in_hub & below
|
|
110
104
|
if settings.instance.dialect != "sqlite":
|
|
@@ -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: 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=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__ == "RegistryMeta"
|
|
359
|
+
and model.__name__ not in ["Registry", "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
|