Skip to content

TTL (Time-To-Live)

Auto-delete items after a set time using DynamoDB's TTL feature.

What is TTL?

DynamoDB TTL lets you set an expiration time on items. When the time passes, DynamoDB automatically deletes the item. This is useful for:

  • Session tokens
  • Temporary data
  • Cache entries
  • Trial periods
  • Audit logs with retention

TTL deletion is free - you don't pay for the delete operation. Items are usually deleted within 48 hours of expiration (often much faster).

Note

TTL must be enabled on your table. See AWS docs for setup.

Key features

  • TTLAttribute - stores datetime as epoch timestamp
  • ExpiresIn helper - easy time calculations
  • is_expired property - check if item expired
  • expires_in property - get time remaining
  • extend_ttl() method - extend expiration

Getting started

Basic usage

Add a TTLAttribute to your model:

import asyncio

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


class Session(Model):
    model_config = ModelConfig(table="sessions")

    pk = StringAttribute(partition_key=True)
    user_id = StringAttribute()
    expires_at = TTLAttribute()


async def main():
    # Create session that expires in 1 hour
    session = Session(
        pk="SESSION#123",
        user_id="USER#456",
        expires_at=ExpiresIn.hours(1),
    )
    await session.save()

    # Check if expired
    if session.is_expired:
        print("Session expired")

    # Get time remaining
    remaining = session.expires_in
    if remaining:
        print(f"Expires in {remaining.total_seconds()} seconds")

    # Extend by 1 hour (sync method - updates TTL in DynamoDB)
    session.extend_ttl(ExpiresIn.hours(1))


asyncio.run(main())

ExpiresIn helper

Use ExpiresIn to calculate expiration times:

"""ExpiresIn helper methods."""

from pydynox.attributes import ExpiresIn

# All methods return a datetime in UTC

# Short-lived items
token_expires = ExpiresIn.minutes(15)  # 15 minutes
session_expires = ExpiresIn.hours(1)  # 1 hour

# Medium-lived items
cache_expires = ExpiresIn.hours(24)  # 24 hours
temp_file_expires = ExpiresIn.days(7)  # 7 days

# Long-lived items
trial_expires = ExpiresIn.weeks(2)  # 2 weeks
Method Example Description
ExpiresIn.seconds(n) ExpiresIn.seconds(30) n seconds from now
ExpiresIn.minutes(n) ExpiresIn.minutes(15) n minutes from now
ExpiresIn.hours(n) ExpiresIn.hours(1) n hours from now
ExpiresIn.days(n) ExpiresIn.days(7) n days from now
ExpiresIn.weeks(n) ExpiresIn.weeks(2) n weeks from now

All methods return a datetime in UTC.

Checking expiration

is_expired

Check if an item has expired:

"""Check expiration status."""

import asyncio

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


class Session(Model):
    model_config = ModelConfig(table="sessions")
    pk = StringAttribute(partition_key=True)
    expires_at = TTLAttribute()


async def main():
    session = await Session.get(pk="SESSION#123")

    if session:
        # Check if expired
        if session.is_expired:
            print("Session has expired")
        else:
            # Get time remaining
            remaining = session.expires_in
            if remaining:
                hours = remaining.total_seconds() / 3600
                print(f"Session expires in {hours:.1f} hours")


asyncio.run(main())

is_expired returns:

  • True if the TTL time has passed
  • False if not expired or no TTL attribute

expires_in

Get time remaining until expiration:

remaining = session.expires_in

if remaining:
    print(f"Expires in {remaining.total_seconds()} seconds")
    print(f"Expires in {remaining.total_seconds() / 60} minutes")
else:
    print("Already expired or no TTL")

expires_in returns:

  • timedelta if not expired
  • None if expired or no TTL attribute

Extending TTL

Use extend_ttl() to push back the expiration:

"""Extend TTL example."""

import asyncio

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


class Session(Model):
    model_config = ModelConfig(table="sessions")
    pk = StringAttribute(partition_key=True)
    expires_at = TTLAttribute()


async def main():
    session = await Session.get(pk="SESSION#123")

    if session and not session.is_expired:
        # Extend by 1 hour from now (sync method)
        session.extend_ttl(ExpiresIn.hours(1))
        print("Session extended")


asyncio.run(main())

This updates both the local instance and DynamoDB in one call.

Real-world example

Session management with TTL:

from uuid import uuid4

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


class Session(Model):
    model_config = ModelConfig(table="sessions")

    pk = StringAttribute(partition_key=True)
    user_id = StringAttribute()
    expires_at = TTLAttribute()


def create_session(user_id: str) -> Session:
    """Create a session that expires in 24 hours."""
    session = Session(
        pk=f"SESSION#{uuid4()}",
        user_id=user_id,
        expires_at=ExpiresIn.hours(24),
    )
    session.save()
    return session


def validate_session(session_id: str) -> bool:
    """Check if session is valid."""
    session = Session.get(pk=session_id)
    if not session or session.is_expired:
        return False
    return True


def refresh_session(session_id: str) -> None:
    """Extend session by 24 hours."""
    session = Session.get(pk=session_id)
    if session and not session.is_expired:
        session.extend_ttl(ExpiresIn.hours(24))

How it works

  1. TTLAttribute stores datetime as Unix epoch timestamp (number)
  2. DynamoDB's TTL process scans for expired items
  3. Expired items are deleted automatically (usually within 48 hours)
  4. No cost for TTL deletions

Warning

Items may remain readable for up to 48 hours after expiration. Always check is_expired in your code if you need strict expiration.

Best practices

  1. Always check is_expired - Don't rely only on DynamoDB deletion
  2. Use UTC - ExpiresIn returns UTC times, keep everything in UTC
  3. Enable TTL on table - TTL won't work without table-level configuration
  4. Choose the right duration - Too short = bad UX, too long = wasted storage

Testing your code

Test TTL without DynamoDB using the built-in memory backend:

"""Testing TTL with pydynox_memory_backend."""

from datetime import datetime, timedelta, timezone

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


class Session(Model):
    model_config = ModelConfig(table="sessions")
    pk = StringAttribute(partition_key=True)
    user_id = StringAttribute()
    expires_at = TTLAttribute()


def test_create_session_with_ttl(pydynox_memory_backend):
    """Test creating a session with TTL."""
    session = Session(
        pk="SESSION#123",
        user_id="USER#1",
        expires_at=ExpiresIn.hours(1),
    )
    session.save()

    found = Session.get(pk="SESSION#123")
    assert found is not None
    assert found.user_id == "USER#1"
    assert found.expires_at is not None


def test_session_not_expired(pydynox_memory_backend):
    """Test that session is not expired."""
    session = Session(
        pk="SESSION#123",
        user_id="USER#1",
        expires_at=ExpiresIn.hours(1),
    )
    session.save()

    found = Session.get(pk="SESSION#123")
    assert not found.is_expired


def test_session_expired(pydynox_memory_backend):
    """Test that session is expired."""
    # Create session that expired 1 hour ago
    past = datetime.now(timezone.utc) - timedelta(hours=1)
    session = Session(
        pk="SESSION#123",
        user_id="USER#1",
        expires_at=past,
    )
    session.save()

    found = Session.get(pk="SESSION#123")
    assert found.is_expired


def test_expires_in_helper(pydynox_memory_backend):
    """Test ExpiresIn helper methods."""
    now = datetime.now(timezone.utc)

    # Test various durations
    assert ExpiresIn.seconds(30) > now
    assert ExpiresIn.minutes(5) > now
    assert ExpiresIn.hours(1) > now
    assert ExpiresIn.days(7) > now
    assert ExpiresIn.weeks(2) > now

No setup needed. Just add pydynox_memory_backend to your test function. See Testing for more details.

Next steps