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 timestampExpiresInhelper - easy time calculationsis_expiredproperty - check if item expiredexpires_inproperty - get time remainingextend_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:
Trueif the TTL time has passedFalseif 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:
timedeltaif not expiredNoneif 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
TTLAttributestores datetime as Unix epoch timestamp (number)- DynamoDB's TTL process scans for expired items
- Expired items are deleted automatically (usually within 48 hours)
- 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
- Always check is_expired - Don't rely only on DynamoDB deletion
- Use UTC -
ExpiresInreturns UTC times, keep everything in UTC - Enable TTL on table - TTL won't work without table-level configuration
- 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
- Attributes - All attribute types
- Models - Model basics
- Hooks - Run code before/after operations