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.
Files changed (96) hide show
  1. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/.github/workflows/build.yml +12 -14
  2. lamindb_setup-0.74.0/.github/workflows/doc-changes.yml +23 -0
  3. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/.gitignore +1 -0
  4. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/PKG-INFO +2 -2
  5. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/changelog.md +5 -0
  6. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/__init__.py +1 -1
  7. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_init_instance.py +9 -5
  8. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_migrate.py +4 -10
  9. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_register_instance.py +4 -0
  10. lamindb_setup-0.74.0/lamindb_setup/_schema_metadata.py +378 -0
  11. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_client.py +2 -2
  12. lamindb_setup-0.74.0/lamindb_setup/core/_private_django_api.py +88 -0
  13. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings.py +43 -9
  14. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_instance.py +2 -1
  15. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_storage.py +2 -0
  16. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/hashing.py +29 -2
  17. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/noxfile.py +4 -1
  18. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/pyproject.toml +1 -1
  19. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_init_instance.py +18 -1
  20. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-local/conftest.py +4 -6
  21. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-local/test_update_schema_in_hub.py +65 -26
  22. lamindb_setup-0.74.0/tests/hub-prod/test_global_settings.py +55 -0
  23. lamindb_setup-0.73.2/.github/workflows/latest-changes.jinja2 +0 -2
  24. lamindb_setup-0.73.2/.github/workflows/latest-changes.yml +0 -25
  25. lamindb_setup-0.73.2/lamindb_setup/_schema_metadata.py +0 -479
  26. lamindb_setup-0.73.2/tests/hub-prod/test_auto_connect.py +0 -12
  27. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/.pre-commit-config.yaml +0 -0
  28. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/LICENSE +0 -0
  29. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/README.md +0 -0
  30. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
  31. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
  32. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
  33. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  34. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
  35. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
  36. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
  37. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/test-multi-session.ipynb +0 -0
  38. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-cloud/test_notebooks.py +0 -0
  39. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-cache-management.ipynb +0 -0
  40. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
  41. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
  42. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-empty-init.ipynb +0 -0
  43. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-import-schema.ipynb +0 -0
  44. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  45. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  46. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  47. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/hub-prod/test_notebooks2.py +0 -0
  48. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/index.md +0 -0
  49. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/notebooks.md +0 -0
  50. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/docs/reference.md +0 -0
  51. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_cache.py +0 -0
  52. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_check.py +0 -0
  53. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_check_setup.py +0 -0
  54. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_close.py +0 -0
  55. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_connect_instance.py +0 -0
  56. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_delete.py +0 -0
  57. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_django.py +0 -0
  58. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_exportdb.py +0 -0
  59. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_importdb.py +0 -0
  60. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_schema.py +0 -0
  61. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_set_managed_storage.py +0 -0
  62. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_setup_user.py +0 -0
  63. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/_silence_loggers.py +0 -0
  64. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/__init__.py +0 -0
  65. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_aws_credentials.py +0 -0
  66. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_aws_storage.py +0 -0
  67. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_deprecated.py +0 -0
  68. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_docs.py +0 -0
  69. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_core.py +0 -0
  70. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_crud.py +0 -0
  71. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_hub_utils.py +0 -0
  72. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_load.py +0 -0
  73. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_save.py +0 -0
  74. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_store.py +0 -0
  75. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_settings_user.py +0 -0
  76. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
  77. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  78. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/django.py +0 -0
  79. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/exceptions.py +0 -0
  80. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/types.py +0 -0
  81. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/lamindb_setup/core/upath.py +0 -0
  82. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_connect_instance.py +0 -0
  83. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_delete_instance.py +0 -0
  84. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_login.py +0 -0
  85. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_migrate.py +0 -0
  86. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-cloud/test_set_storage.py +0 -0
  87. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-local/test_all.py +0 -0
  88. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/conftest.py +0 -0
  89. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/test_django.py +0 -0
  90. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  91. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/hub-prod/test_upath.py +0 -0
  92. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_hashing.py +0 -0
  93. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_storage_access.py +0 -0
  94. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_storage_basis.py +0 -0
  95. {lamindb_setup-0.73.2 → lamindb_setup-0.74.0}/tests/storage/test_storage_stats.py +0 -0
  96. {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.9" # run one job on 3.9
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 -U laminci
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.10
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
- - name: checkout laminhub
60
- uses: actions/checkout@v4
59
+ - uses: actions/checkout@v4
61
60
  with:
62
- repository: laminlabs/laminapp-ui
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 -U laminci
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.11" # we need to run everything for coverage on 3.11
100
+ python-version: "3.9"
102
101
  cache: "pip"
103
102
  cache-dependency-path: ".github/workflows/build.yml"
104
- - run: pip install -U laminci
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
- - name: checkout laminhub
129
- uses: actions/checkout@v4
127
+ - uses: actions/checkout@v4
130
128
  with:
131
- repository: laminlabs/laminapp-ui
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 -U laminci
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 -U laminci
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
@@ -108,3 +108,4 @@ lamin_sphinx
108
108
  docs/conf.py
109
109
  _docs_tmp*
110
110
  *.lndb
111
+ _is_initialized
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamindb_setup
3
- Version: 0.73.2
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.2.0
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 |
@@ -34,7 +34,7 @@ Modules & settings:
34
34
 
35
35
  """
36
36
 
37
- __version__ = "0.73.2" # denote a release candidate for 0.1.0 with 0.1rc1
37
+ __version__ = "0.74.0" # denote a release candidate for 0.1.0 with 0.1rc1
38
38
 
39
39
  import sys
40
40
  from os import name as _os_name
@@ -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, tried two package names:"
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
- if isettings.is_remote and instance_state != "instance-corrupted-or-deleted":
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
- # we can debate whether this is the right setting, but this is how
274
- # things have been and we'd like to not easily break backward compat
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
- instance_id_str = settings.instance._id.hex
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
- # without idempotence
83
+ # due to insufficient SQL permissions with cryptic error
90
84
  collaborator = call_with_fallback_auth(
91
85
  select_collaborator,
92
- instance_id=instance_id_str,
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 instance_is_on_hub:
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":
@@ -27,3 +27,7 @@ def register(_test: bool = False):
27
27
  init_instance_hub(isettings)
28
28
  isettings._is_on_hub = True
29
29
  isettings._persist()
30
+ if isettings.dialect != "sqlite" and not _test:
31
+ from ._schema_metadata import update_schema_in_hub
32
+
33
+ update_schema_in_hub()
@@ -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.important(
123
- "Renewing expired lamin token: call lamin login to avoid this"
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