Skip to content

Type Checking

pydynox supports type checkers like mypy. This guide covers how to get the best type checking experience.

Supported Type Checkers

Type Checker Status Notes
mypy ✅ Tested Recommended
pyright ✅ Tested Works well
ty (red-knot) ⚠️ Partial Descriptors not fully supported yet

We test with mypy and pyright, but can't guarantee 100% coverage for all edge cases. If you find type issues, please open an issue.

How It Works

pydynox uses Python descriptors to make attributes work like regular values:

"""Basic type checking example - attribute types."""

from pydynox import Model, ModelConfig
from pydynox.attributes import (
    BooleanAttribute,
    NumberAttribute,
    StringAttribute,
)


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

    pk = StringAttribute(partition_key=True)
    name = StringAttribute()
    age = NumberAttribute()
    active = BooleanAttribute()


user = User(pk="USER#1", name="John", age=30, active=True)

# On instance: returns the value (T | None)
pk_value: str | None = user.pk
name_value: str | None = user.name
age_value: float | None = user.age
active_value: bool | None = user.active

# On class: returns the Attribute (for conditions)
# User.pk -> StringAttribute
# User.pk == "USER#1" -> Condition

Attribute Types

Each attribute returns a specific type:

Attribute Returns
StringAttribute str or None
NumberAttribute float or None
BooleanAttribute bool or None
BinaryAttribute bytes or None
ListAttribute list[Any] or None
MapAttribute dict[str, Any] or None
StringSetAttribute set[str] or None
NumberSetAttribute set[int or float] or None
JSONAttribute dict[str, Any] or list[Any] or None
DatetimeAttribute datetime or None
TTLAttribute datetime or None
CompressedAttribute str or None
EncryptedAttribute str or None
S3Attribute S3Value or None
VersionAttribute int or None

CRUD Method Return Types

"""CRUD operations type checking example."""

from typing import TYPE_CHECKING, Any

from pydynox import Model, ModelConfig
from pydynox.attributes import StringAttribute
from pydynox.model import ModelQueryResult, ModelScanResult

# Type-only tests - wrapped in TYPE_CHECKING to avoid runtime execution
# These show what types mypy expects


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

    pk = StringAttribute(partition_key=True)
    sk = StringAttribute(sort_key=True)
    name = StringAttribute()


if TYPE_CHECKING:
    user = User(pk="USER#1", sk="PROFILE", name="John")

    # get() returns M | dict[str, Any] | None
    fetched: User | dict[str, Any] | None = User.get(pk="USER#1", sk="PROFILE")

    # save(), delete(), update() return None
    user.save()
    user.delete()
    user.update(name="Jane")

    # query() returns ModelQueryResult[M]
    query_result: ModelQueryResult[User] = User.query(partition_key="USER#1")

    # Iterating - use isinstance to narrow the type
    for item in User.query(partition_key="USER#1"):
        if isinstance(item, User):
            name: str | None = item.name

    # scan() returns ModelScanResult[M]
    scan_result: ModelScanResult[User] = User.scan()

    # batch_get() returns list[M] | list[dict[str, Any]]
    batch: list[User] | list[dict[str, Any]] = User.batch_get([{"pk": "USER#1", "sk": "PROFILE"}])

    # from_dict() returns M
    user_from_dict: User = User.from_dict({"pk": "USER#1", "sk": "PROFILE", "name": "Test"})

Common Issues

1. "None" in return types

All attributes can be None because:

  • The attribute might not be set
  • The attribute has null=True (default)

If you know a value is not None, use assertion or narrowing:

"""How to handle None values in type checking."""

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


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

    pk = StringAttribute(partition_key=True)
    name = StringAttribute()


# All attributes can be None, so you need to handle it


def get_name_with_default(user: User) -> str:
    """Return name or empty string if None."""
    return user.name or ""


def get_name_with_check(user: User) -> str:
    """Raise if name is None."""
    if user.name is None:
        raise ValueError("User has no name")
    return user.name


def get_name_safe(user: User) -> str | None:
    """Return name as-is (may be None)."""
    return user.name

2. Union types from query/scan

query() and scan() return items that can be Model or dict because of the as_dict parameter. Use isinstance to narrow:

for item in User.query(partition_key="USER#1"):
    if isinstance(item, User):
        # mypy knows item is User
        print(item.name)

Or if you know you're not using as_dict=True:

from typing import cast

for item in User.query(partition_key="USER#1"):
    user = cast(User, item)
    print(user.name)

3. ty (red-knot) doesn't understand descriptors

ty is a new type checker that doesn't fully support Python descriptors yet. If you see errors like:

Object of type `Unknown | str | None` is not assignable to `str`

Use mypy or pyright instead. ty will improve over time.

4. Pydantic models work out of the box

Pydantic models have native type support:

from pydantic import BaseModel
from pydynox.integrations.pydantic import dynamodb_model

@dynamodb_model(table="products", partition_key="pk")
class Product(BaseModel):
    pk: str
    name: str
    price: float

product = Product(pk="PROD#1", name="Widget", price=9.99)
product.pk    # str (not str | None)
product.name  # str
product.price # float

Best Practices

1. Use mypy for type checking

# Install mypy
pip install mypy

# Run type checking
mypy your_code.py --ignore-missing-imports

2. Use TYPE_CHECKING for type-only imports

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pydynox.model import ModelQueryResult

3. Handle None values explicitly

See the examples above for patterns to handle None values.

IDE Support

pydynox includes py.typed marker, so IDEs like VS Code and PyCharm will:

  • Show correct types on hover
  • Autocomplete attribute names
  • Warn about type mismatches

Make sure your IDE is configured to use mypy or pyright for type checking.