graphddb-runtime 1.3.0__tar.gz → 1.5.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.3.0 → graphddb_runtime-1.5.0}/PKG-INFO +1 -1
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/leaf_handlers.py +33 -10
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime.egg-info/PKG-INFO +1 -1
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/pyproject.toml +1 -1
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/tests/test_leaf_handlers.py +64 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/README.md +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/__init__.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/batch.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/cursor.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/errors.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/limits.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/per_key_cursor.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/runtime.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime/transport.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime.egg-info/SOURCES.txt +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime.egg-info/requires.txt +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/graphddb_runtime.egg-info/top_level.txt +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/setup.cfg +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/tests/test_crosslang_graphddb_cell.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.0}/tests/test_crosslang_python_cells.py +0 -0
- {graphddb_runtime-1.3.0 → graphddb_runtime-1.5.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"]))
|
|
@@ -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.5.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
|
|
File without changes
|
{graphddb_runtime-1.3.0 → graphddb_runtime-1.5.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
|