Skip to content

Field aliases

Use readable Python names while storing short names in DynamoDB.

Why use aliases?

DynamoDB charges for storage. Attribute names are stored with every item. Long names add up fast.

Without aliases, you have two bad options:

  1. Use short names in code (em, fn) - hard to read
  2. Use long names in DynamoDB (email, first_name) - costs more

Aliases give you both: readable code and cheap storage.

Key features

  • Use alias parameter on any attribute
  • Automatic translation on save and load
  • Works with conditions, updates, queries, and scans
  • Works with indexes (GSI and LSI)
  • Zero performance cost - translation happens in Python at setup time

Getting started

Add alias to any attribute. The alias is the name stored in DynamoDB.

from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute

# Use readable Python names, store short names in DynamoDB


class User(Model):
    model_config = ModelConfig(table="users")

    pk = StringAttribute(partition_key=True)
    sk = StringAttribute(sort_key=True)
    email = StringAttribute(alias="em")
    first_name = StringAttribute(alias="fn")
    last_name = StringAttribute(alias="ln")
    age = NumberAttribute(alias="a")


# Code is readable
user = User(
    pk="USER#1",
    sk="PROFILE",
    email="john@example.com",
    first_name="John",
    last_name="Doe",
    age=30,
)

# to_dict() uses alias names (what DynamoDB stores)
d = user.to_dict()
print(d)
# {"pk": "USER#1", "sk": "PROFILE", "em": "john@example.com", "fn": "John", "ln": "Doe", "a": 30}

assert d["em"] == "john@example.com"
assert d["fn"] == "John"
assert "email" not in d  # Python name is NOT in the dict

The alias parameter is optional. Fields without it use the Python name as-is.

CRUD operations

All CRUD operations work with Python names. The alias translation is automatic.

import asyncio

from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute


class Product(Model):
    model_config = ModelConfig(table="products")

    pk = StringAttribute(partition_key=True)
    product_name = StringAttribute(alias="pn")
    price = NumberAttribute(alias="pr")
    stock = NumberAttribute(alias="stk")


async def main():
    # Save — Python names in code, short names in DynamoDB
    laptop = Product(pk="PROD#1", product_name="Laptop", price=999, stock=10)
    await laptop.save()

    # Get — alias names are translated back to Python names
    loaded = await Product.get(pk="PROD#1")
    assert loaded is not None
    print(f"Name: {loaded.product_name}")  # "Laptop"
    print(f"Price: {loaded.price}")  # 999

    # Update — use Python names
    await loaded.update(price=899)

    # Verify
    updated = await Product.get(pk="PROD#1")
    assert updated is not None
    print(f"New price: {updated.price}")  # 899


asyncio.run(main())

You never need to think about alias names in your code. Use Python names everywhere.

Conditions and updates

Conditions and atomic updates also use aliases automatically.

import asyncio

from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute


class Product(Model):
    model_config = ModelConfig(table="products")

    pk = StringAttribute(partition_key=True)
    product_name = StringAttribute(alias="pn")
    price = NumberAttribute(alias="pr")
    stock = NumberAttribute(alias="stk")


async def main():
    product = Product(pk="PROD#COND", product_name="Mouse", price=25, stock=100)
    await product.save()

    # Conditions use Python names — alias is handled automatically
    await product.update(
        stock=99,
        condition=Product.stock > 0,
    )
    # DynamoDB sees: SET #n0 = :v0 WHERE #n1 > :v1
    # Where #n0 = "stk", #n1 = "stk"

    # exists / not_exists
    await product.save(condition=Product.product_name.exists())

    # begins_with
    products = [
        p
        async for p in Product.scan(
            filter_condition=Product.product_name.begins_with("Mo"),
        )
    ]
    print(f"Found {len(products)} products starting with 'Mo'")


asyncio.run(main())

When you write Product.stock > 0, pydynox translates stock to stk in the DynamoDB expression. You always use the Python name.

Key aliases

You can alias key attributes too (partition key and sort key):

class Event(Model):
    model_config = ModelConfig(table="events")

    pk = StringAttribute(partition_key=True, alias="p")
    sk = StringAttribute(sort_key=True, alias="s")
    event_type = StringAttribute(alias="et")

This saves bytes on every item since keys are always present.

Warning

If you alias key attributes, the DynamoDB table must use the alias names as key definitions. When using Model.create_table(), this is handled automatically.

Indexes

Aliases work with GSI and LSI definitions. The index key definitions use the alias name automatically.

from pydynox.indexes import GlobalSecondaryIndex

class User(Model):
    model_config = ModelConfig(table="users")

    pk = StringAttribute(partition_key=True)
    sk = StringAttribute(sort_key=True)
    email = StringAttribute(alias="em")

    email_index = GlobalSecondaryIndex(
        index_name="email-index",
        partition_key="email",  # Use Python name here
    )

When creating the table, the GSI key schema uses em (the alias). When querying, you use the Python name email.

How it works

At class definition time, pydynox builds two lookup dicts:

  • _py_to_dynamo - maps Python names to DynamoDB names (e.g., {"email": "em"})
  • _dynamo_to_py - maps DynamoDB names back to Python names (e.g., {"em": "email"})

These dicts are used by:

  • to_dict() - translates Python to DynamoDB (on save)
  • from_dict() - translates DynamoDB to Python (on load)
  • Conditions - uses alias in expression paths
  • Atomic updates - uses alias in expression paths
  • Projections - translates field names to aliases

Fields without an alias are not in these dicts. They use the Python name directly.

Parameters

Parameter Type Default Description
alias str | None None DynamoDB attribute name. If set, this name is used in DynamoDB instead of the Python name.

Testing

Use MemoryBackend to test models with aliases. No DynamoDB needed.

from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
from pydynox.testing import MemoryBackend


class User(Model):
    model_config = ModelConfig(table="users")

    pk = StringAttribute(partition_key=True)
    sk = StringAttribute(sort_key=True)
    email = StringAttribute(alias="em")
    age = NumberAttribute(alias="a")


@MemoryBackend()
def test_alias_roundtrip():
    user = User(pk="USER#1", sk="PROFILE", email="test@example.com", age=25)
    user.sync_save()

    loaded = User.sync_get(pk="USER#1", sk="PROFILE")
    assert loaded is not None
    assert loaded.email == "test@example.com"
    assert loaded.age == 25


@MemoryBackend()
def test_alias_to_dict():
    user = User(pk="USER#1", sk="PROFILE", email="test@example.com", age=25)

    d = user.to_dict()
    # Alias names in the dict
    assert d["em"] == "test@example.com"
    assert d["a"] == 25
    # Python names NOT in the dict
    assert "email" not in d
    assert "age" not in d


test_alias_roundtrip()
test_alias_to_dict()
print("All alias tests passed!")

Tips

  • Pick short but meaningful aliases: em for email, fn for first_name
  • Be consistent: use the same alias pattern across models
  • Document your aliases in a comment or table for your team
  • Aliases are most useful for high-volume tables where storage costs matter

Next steps