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.
- httk/registry/cli/store/__init__.py +5 -0
- httk/registry/entries/store/__init__.py +13 -0
- httk/registry/entries/store/py.typed +0 -0
- httk/store/__init__.py +171 -0
- httk/store/backend/__init__.py +24 -0
- httk/store/backend/clickhouse/__init__.py +8 -0
- httk/store/backend/clickhouse/engine.py +61 -0
- httk/store/backend/clickhouse/support.py +1177 -0
- httk/store/backend/codecs.py +443 -0
- httk/store/backend/duckdb/__init__.py +7 -0
- httk/store/backend/duckdb/engine.py +60 -0
- httk/store/backend/duckdb/hooks.py +51 -0
- httk/store/backend/mongo/__init__.py +101 -0
- httk/store/backend/mongo/database.py +109 -0
- httk/store/backend/mongo/documents.py +244 -0
- httk/store/backend/mongo/entry_provider.py +508 -0
- httk/store/backend/mongo/evaluator.py +579 -0
- httk/store/backend/mongo/fsck.py +535 -0
- httk/store/backend/mongo/leases.py +219 -0
- httk/store/backend/mongo/mapping.py +510 -0
- httk/store/backend/mongo/optimade.py +269 -0
- httk/store/backend/mongo/results.py +508 -0
- httk/store/backend/mongo/searcher.py +1717 -0
- httk/store/backend/mongo/store.py +2825 -0
- httk/store/backend/mongo/stored_properties.py +1072 -0
- httk/store/backend/postgresql/__init__.py +8 -0
- httk/store/backend/postgresql/compiler.py +66 -0
- httk/store/backend/postgresql/engine.py +55 -0
- httk/store/backend/postgresql/hooks.py +21 -0
- httk/store/backend/schema.py +820 -0
- httk/store/backend/sql/__init__.py +132 -0
- httk/store/backend/sql/bulk.py +2729 -0
- httk/store/backend/sql/bulk_deferred.py +975 -0
- httk/store/backend/sql/bulk_parallel.py +1874 -0
- httk/store/backend/sql/engine.py +444 -0
- httk/store/backend/sql/entry_provider.py +667 -0
- httk/store/backend/sql/fsck.py +496 -0
- httk/store/backend/sql/graph.py +269 -0
- httk/store/backend/sql/layout.py +379 -0
- httk/store/backend/sql/mapping.py +411 -0
- httk/store/backend/sql/optimade.py +313 -0
- httk/store/backend/sql/paging.py +10 -0
- httk/store/backend/sql/provenance_edges.py +320 -0
- httk/store/backend/sql/results.py +897 -0
- httk/store/backend/sql/rows.py +605 -0
- httk/store/backend/sql/searcher.py +1443 -0
- httk/store/backend/sql/store.py +4195 -0
- httk/store/backend/sql/stored_federation.py +1565 -0
- httk/store/backend/sql/stored_properties.py +1856 -0
- httk/store/backend/sqlite/__init__.py +6 -0
- httk/store/backend/sqlite/engine.py +42 -0
- httk/store/backend/sqlite/hooks.py +52 -0
- httk/store/entry_providers.py +631 -0
- httk/store/export.py +285 -0
- httk/store/federated_store.py +1045 -0
- httk/store/id_ledger.py +1271 -0
- httk/store/py.typed +0 -0
- httk/store/query/__init__.py +20 -0
- httk/store/query/optimade_filters.py +993 -0
- httk/store/query/paging_tokens.py +278 -0
- httk/store/query/portable.py +145 -0
- httk/store/query/protocols.py +487 -0
- httk/store/query/slicer.py +483 -0
- httk/store/served_specs.py +88 -0
- httk/store/storage_layout.py +840 -0
- httk/store/store_common.py +336 -0
- httk/store/store_timestamp.py +104 -0
- httk/store/validation.py +150 -0
- httk_store-2.1.0.dist-info/METADATA +90 -0
- httk_store-2.1.0.dist-info/RECORD +73 -0
- httk_store-2.1.0.dist-info/WHEEL +5 -0
- httk_store-2.1.0.dist-info/licenses/LICENSE +661 -0
- httk_store-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -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
|