Metadata-Version: 2.4
Name: graphddb-runtime
Version: 1.2.2
Summary: The DynamoDB issuance a GraphDDB behavior module binds: the leaf transports, table mapping, AttributeValue codec and cursor codecs.
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: boto3>=1.26
Requires-Dist: behavior-contracts==0.11.16
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"

# graphddb-runtime

The DynamoDB issuance a [GraphDDB](https://www.npmjs.com/package/graphddb) behavior
module binds, for Python.

An operation is **behavior**: you author it as a `@behavior static` method over
graphddb's `@leaf` catalog in ordinary TypeScript, `bc generate --lang python` emits
the module that runs it, and `graphddb-runtime` supplies what one physical DynamoDB
request needs and a behavior cannot carry — the boto3 client, the logical → physical
table mapping, the AttributeValue codec, the execution bounds and the error mapping.

It interprets no specification document. Code generation and IR belong to
[behavior-contracts](https://pypi.org/project/behavior-contracts/); the generated
module embeds its own IR and gates its own spec version when imported.

## Features

- **Leaf transports** — `leaf_handlers(runtime)` supplies the `GetItem` / `Query` /
  `BatchGetItem` / `PutItem` / `UpdateItem` / `DeleteItem` issuance a compiled
  behavior binds. Each handler issues ONE physical request and returns the raw
  row(s): no ordering, no fan-out, no key derivation, no result shaping — those are
  the behavior's, compiled by behavior-contracts.
- **Cursor codecs** — the opaque base64url pagination cursor, byte-identical to the
  TypeScript and PHP runtimes'.

## Install

```bash
pip install graphddb-runtime
```

Requires Python 3.9+ and boto3.

> **Versioning.** `graphddb-runtime` tracks the `graphddb` npm package version: a
> given runtime release matches the `graphddb` CLI of the same version.

## Usage

Construct the runtime with a boto3 client, then hand its leaf handlers to the
generated module's `bind`:

```python
import boto3
import my_behaviors as gen          # bc generate --lang python --from behaviors.ts
from graphddb_runtime import GraphDDBRuntime, leaf_handlers

runtime = GraphDDBRuntime(
    dynamodb_client=boto3.client("dynamodb"),
    # Map logical table names to deployed physical names when they differ.
    table_mapping={"UserPermissions": "UserPermissions-prod"},
)
bound = gen.bind(leaf_handlers(runtime))

user = bound["getUserByEmail"]({"email": "alice@example.com"})
groups = bound["listUserGroups"]({"userId": "alice", "limit": 20})
```

A write behavior returns whether it applied — a DynamoDB
`ConditionalCheckFailedException` is an expected outcome of a conditional write, so
it comes back as `False` rather than raising, and the behavior branches on it:

```python
applied = bound["createMembership"]({"userId": "alice", "groupId": "eng", "role": "admin"})
```

## AWS Lambda

Constructing the boto3 client and importing the generated module are both cold-start
costs you want to pay **once**, in module scope, so they are reused across warm
invocations (and frozen by SnapStart).

```python
# handler.py — module scope runs once per execution environment (cold start).
import json
import boto3
import my_behaviors as gen
from graphddb_runtime import GraphDDBRuntime, leaf_handlers

_bound = gen.bind(
    leaf_handlers(
        GraphDDBRuntime(
            dynamodb_client=boto3.client("dynamodb"),
            table_mapping={"UserPermissions": "UserPermissions-prod"},
        )
    )
)


def handler(event, context):
    user = _bound["getUserByEmail"]({"email": event["queryStringParameters"]["email"]})
    if user is None:
        return {"statusCode": 404, "body": "not found"}
    return {"statusCode": 200, "body": json.dumps(user)}
```

### SnapStart

Lambda SnapStart snapshots the initialized execution environment after the
module-scope code runs, so the client, the imported module and the `bind` are
captured in the snapshot and skipped on restore.

- **Bind in module scope** (as above), never inside the handler — that is what gets
  snapshotted.
- **Do not cache short-lived state across the snapshot** (credentials/tokens with an
  expiry, random seeds). The DynamoDB client and the embedded IR are safe to
  snapshot; refresh anything time-sensitive inside the handler.

### Packaging

The deployment artifact needs three things: this runtime package, the
`behavior-contracts` runtime the generated module imports, and the generated module
itself. boto3/botocore are provided by the Lambda Python runtime, so they need not be
vendored (pin them only if you require a specific version).

```bash
mkdir -p build
pip install graphddb-runtime --target build    # pulls in behavior-contracts
cp my_behaviors.py handler.py build/
( cd build && zip -r ../function.zip . )        # handler = handler.handler
```

## License

MIT
