lamindb_setup 0.73.0__tar.gz → 0.73.2__tar.gz

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