graphddb-runtime 1.1.0__tar.gz → 1.2.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.0/PKG-INFO +136 -0
- graphddb_runtime-1.2.0/README.md +124 -0
- graphddb_runtime-1.2.0/graphddb_runtime/__init__.py +58 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/graphddb_runtime/batch.py +5 -15
- graphddb_runtime-1.2.0/graphddb_runtime/errors.py +24 -0
- graphddb_runtime-1.2.0/graphddb_runtime/leaf_handlers.py +340 -0
- graphddb_runtime-1.2.0/graphddb_runtime/limits.py +29 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/graphddb_runtime/per_key_cursor.py +4 -6
- graphddb_runtime-1.2.0/graphddb_runtime/runtime.py +256 -0
- graphddb_runtime-1.2.0/graphddb_runtime/transport.py +133 -0
- graphddb_runtime-1.2.0/graphddb_runtime.egg-info/PKG-INFO +136 -0
- graphddb_runtime-1.2.0/graphddb_runtime.egg-info/SOURCES.txt +20 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/graphddb_runtime.egg-info/requires.txt +1 -1
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/pyproject.toml +3 -6
- graphddb_runtime-1.2.0/tests/test_crosslang_graphddb_cell.py +891 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/tests/test_crosslang_python_cells.py +9 -184
- graphddb_runtime-1.2.0/tests/test_cursor.py +61 -0
- graphddb_runtime-1.2.0/tests/test_leaf_handlers.py +925 -0
- graphddb_runtime-1.1.0/PKG-INFO +0 -161
- graphddb_runtime-1.1.0/README.md +0 -149
- graphddb_runtime-1.1.0/graphddb_runtime/__init__.py +0 -78
- graphddb_runtime-1.1.0/graphddb_runtime/async_runtime.py +0 -134
- graphddb_runtime-1.1.0/graphddb_runtime/behavior_exec.py +0 -1074
- graphddb_runtime-1.1.0/graphddb_runtime/concurrency.py +0 -87
- graphddb_runtime-1.1.0/graphddb_runtime/errors.py +0 -80
- graphddb_runtime-1.1.0/graphddb_runtime/filters.py +0 -208
- graphddb_runtime-1.1.0/graphddb_runtime/handler_registry.py +0 -170
- graphddb_runtime-1.1.0/graphddb_runtime/hydration.py +0 -75
- graphddb_runtime-1.1.0/graphddb_runtime/limits.py +0 -20
- graphddb_runtime-1.1.0/graphddb_runtime/middleware.py +0 -485
- graphddb_runtime-1.1.0/graphddb_runtime/runtime.py +0 -2603
- graphddb_runtime-1.1.0/graphddb_runtime/templates.py +0 -320
- graphddb_runtime-1.1.0/graphddb_runtime/transactions.py +0 -595
- graphddb_runtime-1.1.0/graphddb_runtime.egg-info/PKG-INFO +0 -161
- graphddb_runtime-1.1.0/graphddb_runtime.egg-info/SOURCES.txt +0 -54
- graphddb_runtime-1.1.0/tests/test_atomic_add_command.py +0 -200
- graphddb_runtime-1.1.0/tests/test_concurrency.py +0 -357
- graphddb_runtime-1.1.0/tests/test_contract_runtime.py +0 -427
- graphddb_runtime-1.1.0/tests/test_description.py +0 -78
- graphddb_runtime-1.1.0/tests/test_description_followups.py +0 -65
- graphddb_runtime-1.1.0/tests/test_description_jsdoc.py +0 -99
- graphddb_runtime-1.1.0/tests/test_error_policy.py +0 -183
- graphddb_runtime-1.1.0/tests/test_generated_module.py +0 -293
- graphddb_runtime-1.1.0/tests/test_guard_eval.py +0 -37
- graphddb_runtime-1.1.0/tests/test_handler_registry.py +0 -72
- graphddb_runtime-1.1.0/tests/test_integration.py +0 -456
- graphddb_runtime-1.1.0/tests/test_integration_batch_write.py +0 -263
- graphddb_runtime-1.1.0/tests/test_integration_binding_contract.py +0 -426
- graphddb_runtime-1.1.0/tests/test_integration_command.py +0 -333
- graphddb_runtime-1.1.0/tests/test_integration_compose.py +0 -186
- graphddb_runtime-1.1.0/tests/test_integration_contract.py +0 -374
- graphddb_runtime-1.1.0/tests/test_integration_edge_derive.py +0 -238
- graphddb_runtime-1.1.0/tests/test_integration_edge_write.py +0 -206
- graphddb_runtime-1.1.0/tests/test_integration_events.py +0 -199
- graphddb_runtime-1.1.0/tests/test_integration_maintain.py +0 -337
- graphddb_runtime-1.1.0/tests/test_integration_middleware.py +0 -312
- graphddb_runtime-1.1.0/tests/test_integration_referential.py +0 -156
- graphddb_runtime-1.1.0/tests/test_integration_relations.py +0 -377
- graphddb_runtime-1.1.0/tests/test_integration_unique.py +0 -246
- graphddb_runtime-1.1.0/tests/test_maintain.py +0 -280
- graphddb_runtime-1.1.0/tests/test_middleware.py +0 -1014
- graphddb_runtime-1.1.0/tests/test_relations.py +0 -383
- graphddb_runtime-1.1.0/tests/test_spec_version_guard.py +0 -159
- graphddb_runtime-1.1.0/tests/test_ttl.py +0 -57
- graphddb_runtime-1.1.0/tests/test_unit.py +0 -1332
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/graphddb_runtime/cursor.py +0 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/graphddb_runtime.egg-info/top_level.txt +0 -0
- {graphddb_runtime-1.1.0 → graphddb_runtime-1.2.0}/setup.cfg +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graphddb-runtime
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: The DynamoDB issuance a GraphDDB behavior module binds: the leaf transports, table mapping, AttributeValue codec and cursor codecs.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: boto3>=1.26
|
|
9
|
+
Requires-Dist: behavior-contracts==0.11.16
|
|
10
|
+
Provides-Extra: test
|
|
11
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
12
|
+
|
|
13
|
+
# graphddb-runtime
|
|
14
|
+
|
|
15
|
+
The DynamoDB issuance a [GraphDDB](https://www.npmjs.com/package/graphddb) behavior
|
|
16
|
+
module binds, for Python.
|
|
17
|
+
|
|
18
|
+
An operation is **behavior**: you author it as a `@behavior static` method over
|
|
19
|
+
graphddb's `@leaf` catalog in ordinary TypeScript, `bc generate --lang python` emits
|
|
20
|
+
the module that runs it, and `graphddb-runtime` supplies what one physical DynamoDB
|
|
21
|
+
request needs and a behavior cannot carry — the boto3 client, the logical → physical
|
|
22
|
+
table mapping, the AttributeValue codec, the execution bounds and the error mapping.
|
|
23
|
+
|
|
24
|
+
It interprets no specification document. Code generation and IR belong to
|
|
25
|
+
[behavior-contracts](https://pypi.org/project/behavior-contracts/); the generated
|
|
26
|
+
module embeds its own IR and gates its own spec version when imported.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Leaf transports** — `leaf_handlers(runtime)` supplies the `GetItem` / `Query` /
|
|
31
|
+
`BatchGetItem` / `PutItem` / `UpdateItem` / `DeleteItem` issuance a compiled
|
|
32
|
+
behavior binds. Each handler issues ONE physical request and returns the raw
|
|
33
|
+
row(s): no ordering, no fan-out, no key derivation, no result shaping — those are
|
|
34
|
+
the behavior's, compiled by behavior-contracts.
|
|
35
|
+
- **Cursor codecs** — the opaque base64url pagination cursor, byte-identical to the
|
|
36
|
+
TypeScript and PHP runtimes'.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install graphddb-runtime
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requires Python 3.9+ and boto3.
|
|
45
|
+
|
|
46
|
+
> **Versioning.** `graphddb-runtime` tracks the `graphddb` npm package version: a
|
|
47
|
+
> given runtime release matches the `graphddb` CLI of the same version.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
Construct the runtime with a boto3 client, then hand its leaf handlers to the
|
|
52
|
+
generated module's `bind`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import boto3
|
|
56
|
+
import my_behaviors as gen # bc generate --lang python --from behaviors.ts
|
|
57
|
+
from graphddb_runtime import GraphDDBRuntime, leaf_handlers
|
|
58
|
+
|
|
59
|
+
runtime = GraphDDBRuntime(
|
|
60
|
+
dynamodb_client=boto3.client("dynamodb"),
|
|
61
|
+
# Map logical table names to deployed physical names when they differ.
|
|
62
|
+
table_mapping={"UserPermissions": "UserPermissions-prod"},
|
|
63
|
+
)
|
|
64
|
+
bound = gen.bind(leaf_handlers(runtime))
|
|
65
|
+
|
|
66
|
+
user = bound["getUserByEmail"]({"email": "alice@example.com"})
|
|
67
|
+
groups = bound["listUserGroups"]({"userId": "alice", "limit": 20})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A write behavior returns whether it applied — a DynamoDB
|
|
71
|
+
`ConditionalCheckFailedException` is an expected outcome of a conditional write, so
|
|
72
|
+
it comes back as `False` rather than raising, and the behavior branches on it:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
applied = bound["createMembership"]({"userId": "alice", "groupId": "eng", "role": "admin"})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## AWS Lambda
|
|
79
|
+
|
|
80
|
+
Constructing the boto3 client and importing the generated module are both cold-start
|
|
81
|
+
costs you want to pay **once**, in module scope, so they are reused across warm
|
|
82
|
+
invocations (and frozen by SnapStart).
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# handler.py — module scope runs once per execution environment (cold start).
|
|
86
|
+
import json
|
|
87
|
+
import boto3
|
|
88
|
+
import my_behaviors as gen
|
|
89
|
+
from graphddb_runtime import GraphDDBRuntime, leaf_handlers
|
|
90
|
+
|
|
91
|
+
_bound = gen.bind(
|
|
92
|
+
leaf_handlers(
|
|
93
|
+
GraphDDBRuntime(
|
|
94
|
+
dynamodb_client=boto3.client("dynamodb"),
|
|
95
|
+
table_mapping={"UserPermissions": "UserPermissions-prod"},
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handler(event, context):
|
|
102
|
+
user = _bound["getUserByEmail"]({"email": event["queryStringParameters"]["email"]})
|
|
103
|
+
if user is None:
|
|
104
|
+
return {"statusCode": 404, "body": "not found"}
|
|
105
|
+
return {"statusCode": 200, "body": json.dumps(user)}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### SnapStart
|
|
109
|
+
|
|
110
|
+
Lambda SnapStart snapshots the initialized execution environment after the
|
|
111
|
+
module-scope code runs, so the client, the imported module and the `bind` are
|
|
112
|
+
captured in the snapshot and skipped on restore.
|
|
113
|
+
|
|
114
|
+
- **Bind in module scope** (as above), never inside the handler — that is what gets
|
|
115
|
+
snapshotted.
|
|
116
|
+
- **Do not cache short-lived state across the snapshot** (credentials/tokens with an
|
|
117
|
+
expiry, random seeds). The DynamoDB client and the embedded IR are safe to
|
|
118
|
+
snapshot; refresh anything time-sensitive inside the handler.
|
|
119
|
+
|
|
120
|
+
### Packaging
|
|
121
|
+
|
|
122
|
+
The deployment artifact needs three things: this runtime package, the
|
|
123
|
+
`behavior-contracts` runtime the generated module imports, and the generated module
|
|
124
|
+
itself. boto3/botocore are provided by the Lambda Python runtime, so they need not be
|
|
125
|
+
vendored (pin them only if you require a specific version).
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
mkdir -p build
|
|
129
|
+
pip install graphddb-runtime --target build # pulls in behavior-contracts
|
|
130
|
+
cp my_behaviors.py handler.py build/
|
|
131
|
+
( cd build && zip -r ../function.zip . ) # handler = handler.handler
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# graphddb-runtime
|
|
2
|
+
|
|
3
|
+
The DynamoDB issuance a [GraphDDB](https://www.npmjs.com/package/graphddb) behavior
|
|
4
|
+
module binds, for Python.
|
|
5
|
+
|
|
6
|
+
An operation is **behavior**: you author it as a `@behavior static` method over
|
|
7
|
+
graphddb's `@leaf` catalog in ordinary TypeScript, `bc generate --lang python` emits
|
|
8
|
+
the module that runs it, and `graphddb-runtime` supplies what one physical DynamoDB
|
|
9
|
+
request needs and a behavior cannot carry — the boto3 client, the logical → physical
|
|
10
|
+
table mapping, the AttributeValue codec, the execution bounds and the error mapping.
|
|
11
|
+
|
|
12
|
+
It interprets no specification document. Code generation and IR belong to
|
|
13
|
+
[behavior-contracts](https://pypi.org/project/behavior-contracts/); the generated
|
|
14
|
+
module embeds its own IR and gates its own spec version when imported.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Leaf transports** — `leaf_handlers(runtime)` supplies the `GetItem` / `Query` /
|
|
19
|
+
`BatchGetItem` / `PutItem` / `UpdateItem` / `DeleteItem` issuance a compiled
|
|
20
|
+
behavior binds. Each handler issues ONE physical request and returns the raw
|
|
21
|
+
row(s): no ordering, no fan-out, no key derivation, no result shaping — those are
|
|
22
|
+
the behavior's, compiled by behavior-contracts.
|
|
23
|
+
- **Cursor codecs** — the opaque base64url pagination cursor, byte-identical to the
|
|
24
|
+
TypeScript and PHP runtimes'.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install graphddb-runtime
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Python 3.9+ and boto3.
|
|
33
|
+
|
|
34
|
+
> **Versioning.** `graphddb-runtime` tracks the `graphddb` npm package version: a
|
|
35
|
+
> given runtime release matches the `graphddb` CLI of the same version.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Construct the runtime with a boto3 client, then hand its leaf handlers to the
|
|
40
|
+
generated module's `bind`:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import boto3
|
|
44
|
+
import my_behaviors as gen # bc generate --lang python --from behaviors.ts
|
|
45
|
+
from graphddb_runtime import GraphDDBRuntime, leaf_handlers
|
|
46
|
+
|
|
47
|
+
runtime = GraphDDBRuntime(
|
|
48
|
+
dynamodb_client=boto3.client("dynamodb"),
|
|
49
|
+
# Map logical table names to deployed physical names when they differ.
|
|
50
|
+
table_mapping={"UserPermissions": "UserPermissions-prod"},
|
|
51
|
+
)
|
|
52
|
+
bound = gen.bind(leaf_handlers(runtime))
|
|
53
|
+
|
|
54
|
+
user = bound["getUserByEmail"]({"email": "alice@example.com"})
|
|
55
|
+
groups = bound["listUserGroups"]({"userId": "alice", "limit": 20})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A write behavior returns whether it applied — a DynamoDB
|
|
59
|
+
`ConditionalCheckFailedException` is an expected outcome of a conditional write, so
|
|
60
|
+
it comes back as `False` rather than raising, and the behavior branches on it:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
applied = bound["createMembership"]({"userId": "alice", "groupId": "eng", "role": "admin"})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## AWS Lambda
|
|
67
|
+
|
|
68
|
+
Constructing the boto3 client and importing the generated module are both cold-start
|
|
69
|
+
costs you want to pay **once**, in module scope, so they are reused across warm
|
|
70
|
+
invocations (and frozen by SnapStart).
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# handler.py — module scope runs once per execution environment (cold start).
|
|
74
|
+
import json
|
|
75
|
+
import boto3
|
|
76
|
+
import my_behaviors as gen
|
|
77
|
+
from graphddb_runtime import GraphDDBRuntime, leaf_handlers
|
|
78
|
+
|
|
79
|
+
_bound = gen.bind(
|
|
80
|
+
leaf_handlers(
|
|
81
|
+
GraphDDBRuntime(
|
|
82
|
+
dynamodb_client=boto3.client("dynamodb"),
|
|
83
|
+
table_mapping={"UserPermissions": "UserPermissions-prod"},
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def handler(event, context):
|
|
90
|
+
user = _bound["getUserByEmail"]({"email": event["queryStringParameters"]["email"]})
|
|
91
|
+
if user is None:
|
|
92
|
+
return {"statusCode": 404, "body": "not found"}
|
|
93
|
+
return {"statusCode": 200, "body": json.dumps(user)}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### SnapStart
|
|
97
|
+
|
|
98
|
+
Lambda SnapStart snapshots the initialized execution environment after the
|
|
99
|
+
module-scope code runs, so the client, the imported module and the `bind` are
|
|
100
|
+
captured in the snapshot and skipped on restore.
|
|
101
|
+
|
|
102
|
+
- **Bind in module scope** (as above), never inside the handler — that is what gets
|
|
103
|
+
snapshotted.
|
|
104
|
+
- **Do not cache short-lived state across the snapshot** (credentials/tokens with an
|
|
105
|
+
expiry, random seeds). The DynamoDB client and the embedded IR are safe to
|
|
106
|
+
snapshot; refresh anything time-sensitive inside the handler.
|
|
107
|
+
|
|
108
|
+
### Packaging
|
|
109
|
+
|
|
110
|
+
The deployment artifact needs three things: this runtime package, the
|
|
111
|
+
`behavior-contracts` runtime the generated module imports, and the generated module
|
|
112
|
+
itself. boto3/botocore are provided by the Lambda Python runtime, so they need not be
|
|
113
|
+
vendored (pin them only if you require a specific version).
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
mkdir -p build
|
|
117
|
+
pip install graphddb-runtime --target build # pulls in behavior-contracts
|
|
118
|
+
cp my_behaviors.py handler.py build/
|
|
119
|
+
( cd build && zip -r ../function.zip . ) # handler = handler.handler
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""GraphDDB Python runtime — the DynamoDB issuance a generated module binds.
|
|
2
|
+
|
|
3
|
+
An operation is behavior: the consumer authors it as a ``@behavior`` method over
|
|
4
|
+
graphddb's ``@leaf`` catalog, ``bc generate`` emits the module, and this package
|
|
5
|
+
supplies what one physical request needs::
|
|
6
|
+
|
|
7
|
+
import my_behaviors as gen
|
|
8
|
+
from graphddb_runtime import GraphDDBRuntime, leaf_handlers
|
|
9
|
+
|
|
10
|
+
runtime = GraphDDBRuntime(boto3.client("dynamodb"), {"members": "AppTable"})
|
|
11
|
+
bound = gen.bind(leaf_handlers(runtime))
|
|
12
|
+
page = bound["listUserGroups"]({"userId": "alice"})
|
|
13
|
+
|
|
14
|
+
Public surface:
|
|
15
|
+
|
|
16
|
+
- :class:`GraphDDBRuntime` — the issuance context (client, table mapping, codec,
|
|
17
|
+
bounds, error mapping).
|
|
18
|
+
- :func:`leaf_handlers` — graphddb's DynamoDB leaf transports, for ``bind``.
|
|
19
|
+
- :class:`RuntimeLimits` — execution-time bounds.
|
|
20
|
+
- the :class:`GraphDDBError` family.
|
|
21
|
+
- the cursor codecs, for a paged read's opaque cursor.
|
|
22
|
+
- the transport-middleware vocabulary (:class:`RequestCtx` / :class:`Outcome` /
|
|
23
|
+
:class:`Refuse` / :class:`Guard` / :class:`Respond`), for a host that wraps the
|
|
24
|
+
physical requests — see :mod:`graphddb_runtime.transport`.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from .cursor import decode_cursor, encode_cursor
|
|
30
|
+
from .errors import GraphDDBError, OperationExecutionError
|
|
31
|
+
from .limits import RuntimeLimits
|
|
32
|
+
from .per_key_cursor import (
|
|
33
|
+
decode_per_key_cursor,
|
|
34
|
+
encode_per_key_cursor,
|
|
35
|
+
serialize_contract_key,
|
|
36
|
+
)
|
|
37
|
+
from .runtime import GraphDDBRuntime
|
|
38
|
+
from .leaf_handlers import leaf_handlers
|
|
39
|
+
from .transport import Guard, Outcome, Refuse, RequestCtx, Respond
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"GraphDDBRuntime",
|
|
43
|
+
"leaf_handlers",
|
|
44
|
+
"RuntimeLimits",
|
|
45
|
+
# transport middleware — the host's seam around one physical request
|
|
46
|
+
"RequestCtx",
|
|
47
|
+
"Outcome",
|
|
48
|
+
"Refuse",
|
|
49
|
+
"Guard",
|
|
50
|
+
"Respond",
|
|
51
|
+
"GraphDDBError",
|
|
52
|
+
"OperationExecutionError",
|
|
53
|
+
"encode_cursor",
|
|
54
|
+
"decode_cursor",
|
|
55
|
+
"serialize_contract_key",
|
|
56
|
+
"encode_per_key_cursor",
|
|
57
|
+
"decode_per_key_cursor",
|
|
58
|
+
]
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
Port of the TypeScript batch semantics (``src/operations/batch-retry.ts`` /
|
|
4
4
|
``src/executor/batch-executor.ts``):
|
|
5
5
|
|
|
6
|
-
- keys
|
|
7
|
-
|
|
6
|
+
- keys arrive already **unique**: removing duplicates is the leaf transport's,
|
|
7
|
+
upstream of chunking (:func:`graphddb_runtime.leaf_handlers.leaf_handlers`
|
|
8
|
+
``batch_get_items``);
|
|
8
9
|
- split into chunks of at most :data:`BATCH_GET_MAX_KEYS` (100) keys;
|
|
9
10
|
- each chunk retries ``UnprocessedKeys`` with exponential backoff
|
|
10
11
|
(``50 * 2^(attempt-1)`` ms, capped at 1000ms) up to
|
|
@@ -17,7 +18,7 @@ real delays.
|
|
|
17
18
|
from __future__ import annotations
|
|
18
19
|
|
|
19
20
|
import time
|
|
20
|
-
from typing import Any, Callable, Dict, List,
|
|
21
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
21
22
|
|
|
22
23
|
# Mirrors src/operations/batch-retry.ts.
|
|
23
24
|
BATCH_GET_MAX_KEYS = 100
|
|
@@ -34,19 +35,8 @@ def chunk(items: List[Any], size: int) -> List[List[Any]]:
|
|
|
34
35
|
return [items[i : i + size] for i in range(0, len(items), size)]
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
def serialize_key(key: Mapping[str, Any]) -> str:
|
|
38
|
-
"""Stable string form of a (plain) key for dedup / parent matching.
|
|
39
|
-
|
|
40
|
-
Mirrors the TS ``dedupeDynamoKeys`` / ``serializeQueryKey`` ordering
|
|
41
|
-
(attributes sorted by name).
|
|
42
|
-
"""
|
|
43
|
-
import json
|
|
44
|
-
|
|
45
|
-
return json.dumps(sorted(key.items()), separators=(",", ":"), default=str)
|
|
46
|
-
|
|
47
|
-
|
|
48
38
|
class BatchGetExecutor:
|
|
49
|
-
"""Executes a
|
|
39
|
+
"""Executes a chunked, retrying BatchGetItem against a boto3 client.
|
|
50
40
|
|
|
51
41
|
The client is the low-level ``boto3.client("dynamodb")`` (AttributeValue
|
|
52
42
|
shapes). ``request_extra`` carries the optional ``ProjectionExpression`` /
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Error hierarchy for the GraphDDB Python runtime.
|
|
2
|
+
|
|
3
|
+
Every runtime-raised error derives from :class:`GraphDDBError` so callers can catch
|
|
4
|
+
the whole family with a single ``except``. Only errors this runtime actually raises
|
|
5
|
+
live here — a declared-but-never-raised class reads as a handled condition that is
|
|
6
|
+
in fact unreachable.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GraphDDBError(Exception):
|
|
13
|
+
"""Base class for every error raised by the runtime."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OperationExecutionError(GraphDDBError):
|
|
17
|
+
"""Wraps a boto3 ``ClientError`` (or any boto3 failure) from a DynamoDB call.
|
|
18
|
+
|
|
19
|
+
The original exception is preserved as ``__cause__`` and on ``original``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, message: str, original: BaseException | None = None) -> None:
|
|
23
|
+
super().__init__(message)
|
|
24
|
+
self.original = original
|