graphddb-runtime 1.2.5__tar.gz → 1.4.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.
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/PKG-INFO +1 -1
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/leaf_handlers.py +34 -11
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/runtime.py +2 -3
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime.egg-info/PKG-INFO +1 -1
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/pyproject.toml +1 -1
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/tests/test_leaf_handlers.py +64 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/README.md +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/__init__.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/batch.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/cursor.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/errors.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/limits.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/per_key_cursor.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime/transport.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime.egg-info/SOURCES.txt +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime.egg-info/requires.txt +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime.egg-info/top_level.txt +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/setup.cfg +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/tests/test_crosslang_graphddb_cell.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/tests/test_crosslang_python_cells.py +0 -0
- {graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/tests/test_cursor.py +0 -0
|
@@ -48,6 +48,19 @@ def _key_attrs(index_name: str) -> tuple:
|
|
|
48
48
|
return (f"{index_name}PK", f"{index_name}SK") if index_name else ("PK", "SK")
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
#: The sort-key predicate of each ``rangeOperator`` ``queryItems`` issues on ``key.sk``
|
|
52
|
+
#: (``between`` reaches up to ``rangeUpper``).
|
|
53
|
+
_SORT_KEY_CLAUSE = {
|
|
54
|
+
"eq": "#ks = :ks",
|
|
55
|
+
"begins_with": "begins_with(#ks, :ks)",
|
|
56
|
+
"lt": "#ks < :ks",
|
|
57
|
+
"le": "#ks <= :ks",
|
|
58
|
+
"gt": "#ks > :ks",
|
|
59
|
+
"ge": "#ks >= :ks",
|
|
60
|
+
"between": "#ks BETWEEN :ks AND :ku",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
51
64
|
def _projection_request(projection: List[str]) -> Dict[str, Any]:
|
|
52
65
|
"""``ProjectionExpression`` + names for the declared projection.
|
|
53
66
|
|
|
@@ -107,20 +120,30 @@ def leaf_handlers(runtime: Any) -> Dict[str, Callable[..., Mapping[str, Any]]]:
|
|
|
107
120
|
index_name = ports["indexName"]
|
|
108
121
|
pk_attr, sk_attr = _key_attrs(index_name)
|
|
109
122
|
operator = ports["rangeOperator"]
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
sk_clause = _SORT_KEY_CLAUSE.get(operator)
|
|
124
|
+
if sk_clause is None:
|
|
125
|
+
raise GraphDDBError(
|
|
126
|
+
f"queryItems: unsupported range operator '{operator}' "
|
|
127
|
+
f"(expected {', '.join(_SORT_KEY_CLAUSE)})."
|
|
128
|
+
)
|
|
129
|
+
upper = ports["rangeUpper"]
|
|
130
|
+
if operator != "between" and upper != "":
|
|
131
|
+
raise GraphDDBError(
|
|
132
|
+
"queryItems: rangeUpper is the upper operand of 'between'; "
|
|
133
|
+
f"operator '{operator}' takes none."
|
|
134
|
+
)
|
|
135
|
+
values = {
|
|
136
|
+
":kp": runtime._serializer.serialize(ports["key"]["pk"]),
|
|
137
|
+
":ks": runtime._serializer.serialize(ports["key"]["sk"]),
|
|
138
|
+
}
|
|
139
|
+
if operator == "between":
|
|
140
|
+
values[":ku"] = runtime._serializer.serialize(upper)
|
|
116
141
|
request: Dict[str, Any] = {
|
|
117
142
|
"TableName": runtime._physical_table(ports["table"]),
|
|
118
143
|
"KeyConditionExpression": f"#kp = :kp AND {sk_clause}",
|
|
119
144
|
"ExpressionAttributeNames": {"#kp": pk_attr, "#ks": sk_attr},
|
|
120
|
-
"ExpressionAttributeValues":
|
|
121
|
-
|
|
122
|
-
":ks": runtime._serializer.serialize(ports["key"]["sk"]),
|
|
123
|
-
},
|
|
145
|
+
"ExpressionAttributeValues": values,
|
|
146
|
+
"ScanIndexForward": ports["scanIndexForward"],
|
|
124
147
|
"Limit": ports["limit"],
|
|
125
148
|
}
|
|
126
149
|
projection = _projection_request(list(ports["projectionFields"]))
|
|
@@ -197,7 +220,7 @@ def leaf_handlers(runtime: Any) -> Dict[str, Callable[..., Mapping[str, Any]]]:
|
|
|
197
220
|
# A condition miss is an EXPECTED OUTCOME of a conditional write, not a
|
|
198
221
|
# transport failure, so a write handler returns ``{"ok": applied}`` rather
|
|
199
222
|
# than raising — the behavior branches on it (the two-stage atomic ADD).
|
|
200
|
-
# This mirrors ``src/leaf/ddb.ts
|
|
223
|
+
# This mirrors the write leaves of ``src/leaf/ddb.ts``; the CCF detector is
|
|
201
224
|
# the runtime's single one (``_is_conditional_check_failure``).
|
|
202
225
|
|
|
203
226
|
def _expr_maps(ports: Mapping[str, Any], request: Dict[str, Any]) -> None:
|
|
@@ -51,9 +51,8 @@ def _is_conditional_check_failure(err: BaseException) -> bool:
|
|
|
51
51
|
``original`` is the boto ``ClientError`` (its ``response.Error.Code`` is
|
|
52
52
|
``ConditionalCheckFailedException``). A fake test client may raise a plain
|
|
53
53
|
exception standing in for the boto error; matched by the exception name / its
|
|
54
|
-
string carrying ``ConditionalCheckFailed
|
|
55
|
-
|
|
56
|
-
detected identically."""
|
|
54
|
+
string carrying ``ConditionalCheckFailed``. Unwraps ``__cause__`` / ``original``
|
|
55
|
+
so a wrapped error is detected identically."""
|
|
57
56
|
seen: set = set()
|
|
58
57
|
stack: List[BaseException] = [err]
|
|
59
58
|
while stack:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "graphddb-runtime"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "The DynamoDB issuance a GraphDDB behavior module binds: the leaf transports, table mapping, AttributeValue codec and cursor codecs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -37,6 +37,7 @@ import pytest
|
|
|
37
37
|
from boto3.dynamodb.types import TypeDeserializer
|
|
38
38
|
|
|
39
39
|
from graphddb_runtime import (
|
|
40
|
+
GraphDDBError,
|
|
40
41
|
GraphDDBRuntime,
|
|
41
42
|
Guard,
|
|
42
43
|
OperationExecutionError,
|
|
@@ -181,6 +182,8 @@ def test_binds_every_component_the_corpus_declares(generated_module):
|
|
|
181
182
|
"bulkAdd",
|
|
182
183
|
"bulkRemove",
|
|
183
184
|
"byGroup",
|
|
185
|
+
"latestBefore",
|
|
186
|
+
"joinedSince",
|
|
184
187
|
"pair",
|
|
185
188
|
"create",
|
|
186
189
|
"bumpRole",
|
|
@@ -285,6 +288,67 @@ def test_query_items_eq_through_a_gsi_keys_on_the_index_attributes(generated_mod
|
|
|
285
288
|
}
|
|
286
289
|
|
|
287
290
|
|
|
291
|
+
def test_query_items_between_reads_descending(generated_module):
|
|
292
|
+
client = RecordingClient(responses={"query": {"Items": []}})
|
|
293
|
+
bound = _bind(generated_module, client)
|
|
294
|
+
|
|
295
|
+
assert bound["latestBefore"]({"userId": "alice", "kind": "JOIN", "day": "2026-10-01"}) == []
|
|
296
|
+
|
|
297
|
+
(request,) = client.requests("query")
|
|
298
|
+
# `between` reaches from `key.sk` up to `rangeUpper`, inclusive.
|
|
299
|
+
assert request["KeyConditionExpression"] == "#kp = :kp AND #ks BETWEEN :ks AND :ku"
|
|
300
|
+
assert _plain(request["ExpressionAttributeValues"]) == {
|
|
301
|
+
":kp": "USER#alice",
|
|
302
|
+
":ks": "JOIN#",
|
|
303
|
+
":ku": "JOIN#2026-10-01",
|
|
304
|
+
}
|
|
305
|
+
assert request["ScanIndexForward"] is False
|
|
306
|
+
assert request["Limit"] == 1
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def test_query_items_compares_a_gsi_sort_key(generated_module):
|
|
310
|
+
client = RecordingClient(responses={"query": {"Items": []}})
|
|
311
|
+
bound = _bind(generated_module, client)
|
|
312
|
+
|
|
313
|
+
assert bound["joinedSince"]({"groupId": "g1", "since": "2026-10-01", "limit": 5}) == []
|
|
314
|
+
|
|
315
|
+
(request,) = client.requests("query")
|
|
316
|
+
assert request["IndexName"] == "GSI1"
|
|
317
|
+
assert request["KeyConditionExpression"] == "#kp = :kp AND #ks >= :ks"
|
|
318
|
+
assert request["ExpressionAttributeNames"]["#ks"] == "GSI1SK"
|
|
319
|
+
assert _plain(request["ExpressionAttributeValues"]) == {
|
|
320
|
+
":kp": "GROUP#g1",
|
|
321
|
+
":ks": "JOINED#2026-10-01",
|
|
322
|
+
}
|
|
323
|
+
assert request["ScanIndexForward"] is True
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _query_ports(operator, upper=""):
|
|
327
|
+
return {
|
|
328
|
+
"table": "members",
|
|
329
|
+
"indexName": "",
|
|
330
|
+
"key": {"pk": "USER#alice", "sk": "GROUP#"},
|
|
331
|
+
"cursor": "",
|
|
332
|
+
"rangeOperator": operator,
|
|
333
|
+
"rangeUpper": upper,
|
|
334
|
+
"scanIndexForward": True,
|
|
335
|
+
"limit": 10,
|
|
336
|
+
"projectionFields": [],
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def test_query_items_refuses_an_operator_it_cannot_issue():
|
|
341
|
+
handlers = _handlers(RecordingClient())
|
|
342
|
+
with pytest.raises(GraphDDBError, match="unsupported range operator 'contains'"):
|
|
343
|
+
handlers["queryItems"](_query_ports("contains"), {"component": "queryItems"})
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def test_query_items_refuses_an_upper_operand_without_between():
|
|
347
|
+
handlers = _handlers(RecordingClient())
|
|
348
|
+
with pytest.raises(GraphDDBError, match="rangeUpper is the upper operand of 'between'"):
|
|
349
|
+
handlers["queryItems"](_query_ports("lt", "GROUP#z"), {"component": "queryItems"})
|
|
350
|
+
|
|
351
|
+
|
|
288
352
|
def test_batch_get_items_requests_every_key_in_one_batch(generated_module):
|
|
289
353
|
# A projected read returns the projected fields — which is exactly what the
|
|
290
354
|
# behavior declares its rows to be (`MemberRef`, not the whole `MemberRow`).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphddb_runtime-1.2.5 → graphddb_runtime-1.4.0}/graphddb_runtime.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|