Runtime Guides

Package-source copy · Generated into the site so runtime docs are searchable and linkable.

Migration Guide (Python)

This guide helps migrate from raw boto3 DynamoDB usage to tabletheory_py.

Migration: raw put_itemtable.put

Problem

Hand-authored client.put_item(...) payloads are verbose and drift-prone across services.

Solution

Define a model once and use typed helpers.

table = Table(model, client=client)
table.put(Note(pk="A", sk="1", value=123))

Schema migration (auto_migrate)

This mirrors the Go migration guide schema-migration story: move data from a v1 table shape to a v2 shape, optionally backing up the source first and transforming each item on the way. It is reproducible against DynamoDB Local — no AWS account required.

auto_migrate(source_model, client=client, options...) ensures the target table exists and, when data_copy=True, scans the source table and writes each item into the target table through an optional transform. Item-level transforms mirror the Go helpers:

  • rename_field(old_name, new_name)
  • add_field(name, value)
  • remove_field(name)
  • copy_all_fields()
  • chain_transforms(*transforms) composes them left to right

Walkthrough

from __future__ import annotations

import os
from dataclasses import dataclass

import boto3

from tabletheory_py import ModelDefinition, ensure_table, theorydb_field
from tabletheory_py.schema_migration import add_field, auto_migrate, chain_transforms, rename_field

client = boto3.client(
    "dynamodb",
    endpoint_url=os.environ.get("DYNAMODB_ENDPOINT", "http://localhost:8000"),
    region_name=os.environ.get("AWS_REGION", "us-east-1"),
    aws_access_key_id="dummy",
    aws_secret_access_key="dummy",
)


@dataclass(frozen=True)
class UserV1:
    pk: str = theorydb_field(name="PK", roles=["pk"])
    sk: str = theorydb_field(name="SK", roles=["sk"])
    name: str = theorydb_field(default="")


@dataclass(frozen=True)
class UserV2:
    pk: str = theorydb_field(name="PK", roles=["pk"])
    sk: str = theorydb_field(name="SK", roles=["sk"])
    display_name: str = theorydb_field(name="displayName", default="")
    status: str = theorydb_field(default="")


user_v1 = ModelDefinition.from_dataclass(UserV1, table_name="users_migration_v1")
user_v2 = ModelDefinition.from_dataclass(UserV2, table_name="users_migration_v2")

# Seed the v1 table.
ensure_table(user_v1, client=client)
client.put_item(
    TableName=user_v1.table_name,
    Item={"PK": {"S": "USER#1"}, "SK": {"S": "v1"}, "name": {"S": "Ada"}},
)

# Back up v1, then migrate v1 -> v2: rename `name` -> `displayName` and add `status`.
auto_migrate(
    user_v1,
    client=client,
    target_model=user_v2,
    data_copy=True,
    backup_table="users_migration_backup",
    transform=chain_transforms(
        rename_field("name", "displayName"),
        add_field("status", {"S": "active"}),
    ),
)
# users_migration_v2 now holds { PK, SK, displayName: "Ada", status: "active" };
# users_migration_backup holds the original { PK, SK, name: "Ada" }.

A runnable version of this walkthrough lives in py/tests/integration/test_schema_migration.py and runs on the Python integration lane against DynamoDB Local.