httk-store 2.1.0__py3-none-any.whl

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 (73) hide show
  1. httk/registry/cli/store/__init__.py +5 -0
  2. httk/registry/entries/store/__init__.py +13 -0
  3. httk/registry/entries/store/py.typed +0 -0
  4. httk/store/__init__.py +171 -0
  5. httk/store/backend/__init__.py +24 -0
  6. httk/store/backend/clickhouse/__init__.py +8 -0
  7. httk/store/backend/clickhouse/engine.py +61 -0
  8. httk/store/backend/clickhouse/support.py +1177 -0
  9. httk/store/backend/codecs.py +443 -0
  10. httk/store/backend/duckdb/__init__.py +7 -0
  11. httk/store/backend/duckdb/engine.py +60 -0
  12. httk/store/backend/duckdb/hooks.py +51 -0
  13. httk/store/backend/mongo/__init__.py +101 -0
  14. httk/store/backend/mongo/database.py +109 -0
  15. httk/store/backend/mongo/documents.py +244 -0
  16. httk/store/backend/mongo/entry_provider.py +508 -0
  17. httk/store/backend/mongo/evaluator.py +579 -0
  18. httk/store/backend/mongo/fsck.py +535 -0
  19. httk/store/backend/mongo/leases.py +219 -0
  20. httk/store/backend/mongo/mapping.py +510 -0
  21. httk/store/backend/mongo/optimade.py +269 -0
  22. httk/store/backend/mongo/results.py +508 -0
  23. httk/store/backend/mongo/searcher.py +1717 -0
  24. httk/store/backend/mongo/store.py +2825 -0
  25. httk/store/backend/mongo/stored_properties.py +1072 -0
  26. httk/store/backend/postgresql/__init__.py +8 -0
  27. httk/store/backend/postgresql/compiler.py +66 -0
  28. httk/store/backend/postgresql/engine.py +55 -0
  29. httk/store/backend/postgresql/hooks.py +21 -0
  30. httk/store/backend/schema.py +820 -0
  31. httk/store/backend/sql/__init__.py +132 -0
  32. httk/store/backend/sql/bulk.py +2729 -0
  33. httk/store/backend/sql/bulk_deferred.py +975 -0
  34. httk/store/backend/sql/bulk_parallel.py +1874 -0
  35. httk/store/backend/sql/engine.py +444 -0
  36. httk/store/backend/sql/entry_provider.py +667 -0
  37. httk/store/backend/sql/fsck.py +496 -0
  38. httk/store/backend/sql/graph.py +269 -0
  39. httk/store/backend/sql/layout.py +379 -0
  40. httk/store/backend/sql/mapping.py +411 -0
  41. httk/store/backend/sql/optimade.py +313 -0
  42. httk/store/backend/sql/paging.py +10 -0
  43. httk/store/backend/sql/provenance_edges.py +320 -0
  44. httk/store/backend/sql/results.py +897 -0
  45. httk/store/backend/sql/rows.py +605 -0
  46. httk/store/backend/sql/searcher.py +1443 -0
  47. httk/store/backend/sql/store.py +4195 -0
  48. httk/store/backend/sql/stored_federation.py +1565 -0
  49. httk/store/backend/sql/stored_properties.py +1856 -0
  50. httk/store/backend/sqlite/__init__.py +6 -0
  51. httk/store/backend/sqlite/engine.py +42 -0
  52. httk/store/backend/sqlite/hooks.py +52 -0
  53. httk/store/entry_providers.py +631 -0
  54. httk/store/export.py +285 -0
  55. httk/store/federated_store.py +1045 -0
  56. httk/store/id_ledger.py +1271 -0
  57. httk/store/py.typed +0 -0
  58. httk/store/query/__init__.py +20 -0
  59. httk/store/query/optimade_filters.py +993 -0
  60. httk/store/query/paging_tokens.py +278 -0
  61. httk/store/query/portable.py +145 -0
  62. httk/store/query/protocols.py +487 -0
  63. httk/store/query/slicer.py +483 -0
  64. httk/store/served_specs.py +88 -0
  65. httk/store/storage_layout.py +840 -0
  66. httk/store/store_common.py +336 -0
  67. httk/store/store_timestamp.py +104 -0
  68. httk/store/validation.py +150 -0
  69. httk_store-2.1.0.dist-info/METADATA +90 -0
  70. httk_store-2.1.0.dist-info/RECORD +73 -0
  71. httk_store-2.1.0.dist-info/WHEEL +5 -0
  72. httk_store-2.1.0.dist-info/licenses/LICENSE +661 -0
  73. httk_store-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ """Register httk-store's command-line namespace."""
2
+
3
+ from httk.core import register_cli_command
4
+
5
+ register_cli_command("store", "httk.store.export:command", "export and inspect httk stores")
@@ -0,0 +1,13 @@
1
+ """Register entry providers implemented by :mod:`httk.store`."""
2
+
3
+ from httk.core.register import register_entry_provider
4
+
5
+ register_entry_provider(name="store-references", factory="httk.store.entry_providers:ReferenceEntryProvider")
6
+ register_entry_provider(name="store-files", factory="httk.store.entry_providers:FileEntryProvider")
7
+ register_entry_provider(name="store-calculations", factory="httk.store.entry_providers:CalculationEntryProvider")
8
+ register_entry_provider(name="store-runs", factory="httk.store.entry_providers:RunEntryProvider")
9
+ register_entry_provider(name="store-records", factory="httk.store.entry_providers:DataRecordEntryProvider")
10
+
11
+ # The database-backed provider (requires the httk-store[db] extra); the factory
12
+ # reference is lazy, so registration itself never imports sqlalchemy.
13
+ register_entry_provider(name="store-db-store", factory="httk.store.backend.sql.entry_provider:StoreEntryProvider")
File without changes
httk/store/__init__.py ADDED
@@ -0,0 +1,171 @@
1
+ """Provide httk-store's data-management capability layer for httk v2.
2
+
3
+ Built on the stdlib-only *contracts and models* in *httk-core*, httk-store
4
+ supplies *capabilities*:
5
+
6
+ - in-memory :class:`~httk.core.EntryProvider` implementations for the standard
7
+ OPTIMADE entry types (:class:`ReferenceEntryProvider`,
8
+ :class:`FileEntryProvider`, :class:`CalculationEntryProvider`), serving
9
+ httk-core's record models through the neutral provider contract; and
10
+ - **property-definition validation** (:func:`validate_property`,
11
+ :func:`validate_record`, :class:`PropertyValidationError`) built on
12
+ ``jsonschema`` (Draft 2020-12), checking record values against their OPTIMADE
13
+ property definitions fully offline; and
14
+ - the **store/searcher query protocols** (:mod:`httk.store.query`) — the
15
+ backend-agnostic query contract implemented by httk data stores and consumed
16
+ by serving modules; and
17
+ - the **federated store** (:mod:`httk.store.federated_store`) — ordered, immutable
18
+ source and target bindings plus lazy sequential union query execution; and
19
+ - the **generic OPTIMADE filter translation** (:mod:`httk.store.query.optimade_filters`)
20
+ — turning filter syntax trees parsed by
21
+ :func:`httk.core.optimade.parse_optimade_filter` into search expressions over the
22
+ query protocols (the machinery in :mod:`httk.store.query.optimade_filters`, including
23
+ :func:`~httk.store.query.optimade_filters.filter_searcher`), with
24
+ neutral :class:`~httk.store.query.optimade_filters.FilterTranslationError` categories; and
25
+
26
+ - the **database storage layer** (:mod:`httk.store.backend.sql`, requiring the
27
+ ``httk-store[db]`` extra) — relational storage and querying of plain frozen
28
+ dataclasses (:class:`~httk.store.backend.sql.store.SqlStore` over SQLite, DuckDB,
29
+ PostgreSQL, or ClickHouse),
30
+ served through the provider contract by
31
+ :class:`~httk.store.backend.sql.entry_provider.StoreEntryProvider`.
32
+
33
+ The providers self-register (under ``httk.registry.entries.store``, as
34
+ ``store-references``/``store-files``/``store-calculations``/``store-db-store``)
35
+ when ``httk.core`` discovers the module, so a serving module (such as
36
+ *httk-serve*) can find them through the registry.
37
+
38
+ .. py:class:: StandardEntryProvider
39
+ :canonical: httk.store.entry_providers.StandardEntryProvider
40
+ """
41
+
42
+ import importlib
43
+ from typing import Any
44
+
45
+ from .entry_providers import (
46
+ CalculationEntryProvider,
47
+ DataRecordEntryProvider,
48
+ FileEntryProvider,
49
+ ReferenceEntryProvider,
50
+ RunEntryProvider,
51
+ product_relationships,
52
+ )
53
+ from .export import export_dataset
54
+ from .federated_store import (
55
+ FederatedResultSet,
56
+ FederatedSearcher,
57
+ FederatedSourceError,
58
+ FederatedStore,
59
+ FederatedStoreError,
60
+ FederatedTarget,
61
+ )
62
+ from .id_ledger import IdLedger, IdLedgerError, check_ledger_key
63
+ from .query import (
64
+ ContinuationToken,
65
+ CountUnavailableError,
66
+ MultipleResultsError,
67
+ NoResultError,
68
+ PageableResultSetLike,
69
+ PageOrder,
70
+ PaginationCursorError,
71
+ PortableQueryCapabilities,
72
+ ResultPage,
73
+ ResultRow,
74
+ ResultRowLike,
75
+ ResultSetLike,
76
+ Searcher,
77
+ SearchExpression,
78
+ SearchField,
79
+ SearchResult,
80
+ SearchVariable,
81
+ Store,
82
+ UnsupportedQueryError,
83
+ portable_query_capabilities,
84
+ portable_query_fields,
85
+ )
86
+ from .query.optimade_filters import (
87
+ FilterTranslationCategory,
88
+ FilterTranslationError,
89
+ filter_searcher,
90
+ )
91
+ from .storage_layout import EntryFamilyDeclaration, EntryLayoutBindingError, EntryRecordDeclaration
92
+ from .store_common import EntryIdConflictError, EntryIdScheme, EntryStore
93
+ from .validation import PropertyValidationError, validate_property, validate_record
94
+
95
+ __all__ = [
96
+ "Backend", # pyright: ignore[reportUnsupportedDunderAll] (provided lazily via __getattr__)
97
+ "CalculationEntryProvider",
98
+ "ContinuationToken",
99
+ "CountUnavailableError",
100
+ "DataRecordEntryProvider",
101
+ "EntryFamilyDeclaration",
102
+ "EntryIdConflictError",
103
+ "EntryIdScheme",
104
+ "EntryLayoutBindingError",
105
+ "EntryRecordDeclaration",
106
+ "EntryStore",
107
+ "FederatedResultSet",
108
+ "FederatedSearcher",
109
+ "FederatedSourceError",
110
+ "FederatedStore",
111
+ "FederatedStoreError",
112
+ "FederatedTarget",
113
+ "FileEntryProvider",
114
+ "FilterTranslationCategory",
115
+ "FilterTranslationError",
116
+ "IdLedger",
117
+ "IdLedgerError",
118
+ "MongoStore", # pyright: ignore[reportUnsupportedDunderAll] (provided lazily via __getattr__)
119
+ "MultipleResultsError",
120
+ "NoResultError",
121
+ "PageOrder",
122
+ "PageableResultSetLike",
123
+ "PaginationCursorError",
124
+ "PortableQueryCapabilities",
125
+ "PropertyValidationError",
126
+ "ReferenceEntryProvider",
127
+ "ResultPage",
128
+ "ResultRow",
129
+ "ResultRowLike",
130
+ "ResultSetLike",
131
+ "RunEntryProvider",
132
+ "SearchExpression",
133
+ "SearchField",
134
+ "SearchResult",
135
+ "SearchVariable",
136
+ "Searcher",
137
+ "SqlStore", # pyright: ignore[reportUnsupportedDunderAll] (provided lazily via __getattr__)
138
+ "Store",
139
+ "UnsupportedQueryError",
140
+ "check_ledger_key",
141
+ "export_dataset",
142
+ "filter_searcher",
143
+ "portable_query_capabilities",
144
+ "portable_query_fields",
145
+ "product_relationships",
146
+ "validate_property",
147
+ "validate_record",
148
+ ]
149
+
150
+ # The storage-backend engines are re-exported lazily: importing ``httk.store``
151
+ # must stay free of both ``sqlalchemy`` and ``pymongo``, so ``Backend`` and
152
+ # ``SqlStore`` load the SQL layer, and ``MongoStore`` the MongoDB layer, only on
153
+ # first attribute access.
154
+ _LAZY_EXPORTS = {
155
+ "Backend": "httk.store.backend.sql.engine",
156
+ "SqlStore": "httk.store.backend.sql.store",
157
+ "MongoStore": "httk.store.backend.mongo",
158
+ }
159
+
160
+
161
+ def __getattr__(name: str) -> Any:
162
+ """Import a storage-backend engine lazily on first access.
163
+
164
+ :param name: The module attribute to import.
165
+ :return: The requested storage-backend export.
166
+ :raises AttributeError: If ``name`` is not a lazily re-exported attribute.
167
+ """
168
+ module_name = _LAZY_EXPORTS.get(name)
169
+ if module_name is None:
170
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
171
+ return getattr(importlib.import_module(module_name), name)
@@ -0,0 +1,24 @@
1
+ """Storage backends for *httk-store*.
2
+
3
+ Each subpackage is a self-contained storage backend:
4
+
5
+ - :mod:`httk.store.backend.sql` — the SQLAlchemy-backed relational layer
6
+ (:class:`~httk.store.backend.sql.engine.Backend`,
7
+ :class:`~httk.store.backend.sql.store.SqlStore`) shared by every SQL dialect;
8
+ - :mod:`httk.store.backend.sqlite`, :mod:`httk.store.backend.duckdb`,
9
+ :mod:`httk.store.backend.clickhouse`, :mod:`httk.store.backend.postgresql` —
10
+ the per-dialect constructor and quirk code the SQL layer delegates to; and
11
+ - :mod:`httk.store.backend.mongo` — the MongoDB-backed layer
12
+ (:class:`~httk.store.backend.mongo.store.MongoStore`).
13
+
14
+ Two backend-neutral modules sit alongside the subpackages, driver-free and
15
+ shared by every backend:
16
+
17
+ - :mod:`httk.store.backend.schema` — :func:`~httk.store.backend.schema.resolve_schema`
18
+ and the schema IR that drives storage; and
19
+ - :mod:`httk.store.backend.codecs` — the :class:`~httk.store.backend.codecs.ValueCodec`
20
+ registry with exact, round-trippable encodings.
21
+
22
+ Importing this package pulls in neither ``sqlalchemy`` nor ``pymongo``; the
23
+ driver-backed names load lazily on first use of the relevant subpackage.
24
+ """
@@ -0,0 +1,8 @@
1
+ """ClickHouse-specific support for the SQL storage backend.
2
+
3
+ The dialect-agnostic engine wrapper and store live in
4
+ :mod:`httk.store.backend.sql`; this package holds the ClickHouse constructor
5
+ (:func:`httk.store.backend.clickhouse.engine.database`) and the Keeper-backed
6
+ schema, lease, and connection-guard machinery in
7
+ :mod:`httk.store.backend.clickhouse.support`.
8
+ """
@@ -0,0 +1,61 @@
1
+ """ClickHouse database construction for :class:`~httk.store.backend.sql.engine.Backend`."""
2
+
3
+ import importlib
4
+ from typing import TYPE_CHECKING
5
+
6
+ import sqlalchemy
7
+
8
+ if TYPE_CHECKING:
9
+ from httk.store.backend.sql.engine import Backend
10
+
11
+
12
+ def database(
13
+ cls: "type[Backend]",
14
+ url: str | sqlalchemy.URL,
15
+ *,
16
+ database: str | None = None,
17
+ ) -> "Backend":
18
+ """Build a ClickHouse-backed backend from a ``clickhousedb://`` URL.
19
+
20
+ The URL uses the SQLAlchemy ``clickhouse-connect`` dialect, for example
21
+ ``clickhousedb://default:@host:8123/my_database``. ``database`` replaces the
22
+ URL path when supplied. The constructor always merges ``join_use_nulls=1``
23
+ into the URL query and selects the ``bulk-fenced`` storage profile before any
24
+ :class:`~httk.store.backend.sql.store.SqlStore` initialization occurs.
25
+
26
+ :param cls: The backend class to instantiate.
27
+ :param url: ClickHouse SQLAlchemy URL or URL string.
28
+ :param database: The database name overriding the URL path, if supplied.
29
+ :return: Connected ClickHouse backend wrapper using the bulk-fenced profile.
30
+ :raises ImportError: If ``clickhouse-connect`` is not installed; install the
31
+ ``httk-store[clickhouse]`` extra.
32
+ :raises RuntimeError: If Keeper is unavailable, the server is too old, or
33
+ ``join_use_nulls`` cannot be enforced.
34
+ """
35
+ try:
36
+ importlib.import_module("clickhouse_connect")
37
+ # Importing this module registers the clickhousedb SQLAlchemy URL.
38
+ importlib.import_module("clickhouse_connect.cc_sqlalchemy")
39
+ except ImportError as error:
40
+ raise ImportError(
41
+ "the ClickHouse backend needs clickhouse-connect; install the 'httk-store[clickhouse]' extra "
42
+ "to use Backend.clickhouse()"
43
+ ) from error
44
+ from sqlalchemy.engine import make_url
45
+
46
+ clickhouse_url = make_url(url) if isinstance(url, str) else url
47
+ if clickhouse_url.drivername.split("+")[0] != "clickhousedb":
48
+ raise ValueError("Backend.clickhouse() requires a clickhousedb:// SQLAlchemy URL")
49
+ if database is not None:
50
+ clickhouse_url = clickhouse_url.set(database=database)
51
+ clickhouse_url = clickhouse_url.update_query_dict({"join_use_nulls": "1"})
52
+ engine = sqlalchemy.create_engine(clickhouse_url)
53
+ try:
54
+ result = cls(engine, write_profile="bulk-fenced")
55
+ from httk.store.backend.clickhouse.support import ensure_bootstrap_table
56
+
57
+ ensure_bootstrap_table(result.engine)
58
+ return result
59
+ except BaseException:
60
+ engine.dispose()
61
+ raise