Type-Safe DynamoDB, the Pythonic Way

A Pythonic ORM for DynamoDB

Prismarine defines DynamoDB models with plain Python TypedDict (or Pydantic) classes and generates fully type-safe client code — CRUD, secondary indexes, streams, and TTL — with almost no boilerplate.

$ pip install prismarine

Why Prismarine?

Model your data in Python and let Prismarine generate the type-safe client for you.

🧬

Model with Python Types

Define tables as TypedDict or pydantic.BaseModel classes decorated with @c.model to declare partition and sort keys.

⚙️

Auto-Generated Client

Run prismarine generate-client to emit a prismarine_client.py with typed get, put, update, list, scan, and delete methods.

🔎

Secondary Indexes

Declare GSIs with the @c.index decorator and get generated, type-safe query methods scoped to each index.

🛡️

Named-Argument Safety

Operations require named arguments, so renaming a key surfaces as a compile-time error in your IDE and linter — no silent failures.

🌊

Streams & TTL

Configure DynamoDB stream triggers and Time-To-Live expiration declaratively on the model via the trigger and ttl options.

💠

TypedDict or Pydantic

Ship with lightweight TypedDict by default, or opt into Pydantic models for runtime validation — the API surface stays the same.

Quick Start

From a model definition to type-safe CRUD in three steps.

1

Define a Model

In models.py, describe your table with a decorated TypedDict:

from typing import TypedDict, NotRequired
from prismarine import Cluster

c = Cluster('TapgameExample')

@c.model(PK='Foo', SK='Bar')
class Team(TypedDict):
    Foo: str
    Bar: str
    Baz: NotRequired[str]
2

Generate the Client

Emit a fully typed prismarine_client.py alongside your models:

# TypedDict (default)
prismarine generate-client --base

# Or with Pydantic models
prismarine generate-client --model-library pydantic --base
3

Use Type-Safe CRUD

Call the generated model methods with named arguments:

# Create
TeamModel.put({'Foo': 'foo', 'Bar': 'bar'})

# Query by partition key
teams = TeamModel.list(foo='foo')

# Get / update / delete
team = TeamModel.get(foo='foo', bar='bar')
TeamModel.update({'Baz': 'new'}, foo='foo', bar='bar')
TeamModel.delete(foo='foo', bar='bar')

Works Great with EasySAM

Prismarine is standalone, but pairs naturally with EasySAM for modular serverless apps.

When used alongside EasySAM, Prismarine models become the source of truth for your DynamoDB tables. Declare a trigger or ttl right on the model, and EasySAM generates the matching CloudFormation — enabling streams, wiring an EventSourceMapping to your Lambda, and configuring TTL and IAM permissions automatically.

@c.model(PK='Foo', SK='Bar', trigger='itemlogger', ttl='ExpireAt')
class Item(TypedDict):
    Foo: str
    Bar: str
    ExpireAt: int  # Unix timestamp

Contributors

Built and maintained by these humans.