Conditions
Conditions let you add rules to save() and delete() operations. The operation only happens if the condition is true. If not, DynamoDB raises an error.
This is useful for:
- Preventing overwrites (only save if item doesn't exist)
- Optimistic locking (only update if version matches)
- Business rules (only withdraw if balance is sufficient)
Key features
- Use Python operators (
==,>,<) on model attributes - Combine with
&(AND),|(OR),~(NOT) - Functions like
exists(),begins_with(),between() - Build conditions dynamically from user input
Getting started
Prevent overwriting existing items
The most common use case. Only save if the item doesn't exist yet:
"""Prevent overwriting existing items."""
from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
email = StringAttribute()
name = StringAttribute()
age = NumberAttribute()
# Only save if the item doesn't exist yet
user = User(pk="USER#123", email="john@example.com", name="John", age=30)
user.save(condition=User.pk.does_not_exist())
# If USER#123 already exists, this raises ConditionCheckFailedError
Without this condition, save() would silently overwrite any existing item with the same key. With the condition, you get a ConditionCheckFailedError if the item already exists.
Safe delete
Only delete if certain conditions are met:
"""Safe delete - only delete if conditions are met."""
from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
class Order(Model):
model_config = ModelConfig(table="orders")
pk = StringAttribute(hash_key=True)
status = StringAttribute()
total = NumberAttribute()
# Only delete if order is in "draft" status
order = Order.get(pk="ORDER#123")
order.delete(condition=Order.status == "draft")
# Can't delete orders that are already processed
This prevents accidental deletion of orders that are already being processed.
Advanced
Optimistic locking
When multiple processes might update the same item, use a version field to prevent lost updates:
"""Optimistic locking - only update if version matches."""
from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
class Product(Model):
model_config = ModelConfig(table="products")
pk = StringAttribute(hash_key=True)
name = StringAttribute()
price = NumberAttribute()
version = NumberAttribute()
# Get current product
product = Product.get(pk="PROD#123")
current_version = product.version
# Update with version check
product.price = 29.99
product.version = current_version + 1
product.save(condition=Product.version == current_version)
# If someone else updated the product, version won't match
# and ConditionCheckFailedError is raised
How it works:
- Read the item and note the current version
- Make your changes and increment the version
- Save with a condition that the version still matches
- If someone else updated it first, the condition fails
This is safer than "last write wins" because you know when conflicts happen.
Complex business rules
Combine multiple conditions for complex validation:
"""Complex business rules with combined conditions."""
from pydynox import Model, ModelConfig
from pydynox.attributes import BooleanAttribute, NumberAttribute, StringAttribute
class Account(Model):
model_config = ModelConfig(table="accounts")
pk = StringAttribute(hash_key=True)
balance = NumberAttribute()
status = StringAttribute()
verified = BooleanAttribute()
# Only allow withdrawal if:
# - Account is active AND verified
# - Balance is sufficient
account = Account.get(pk="ACC#123")
withdrawal = 100
condition = (
(Account.status == "active")
& (Account.verified == True) # noqa: E712
& (Account.balance >= withdrawal)
)
account.balance = account.balance - withdrawal
account.save(condition=condition)
The withdrawal only happens if all three conditions are true. If any fails, the whole operation is rejected.
Dynamic filters
Build conditions at runtime based on user input:
"""Build conditions dynamically from user input."""
from pydynox import Model, ModelConfig
from pydynox.attributes import NumberAttribute, StringAttribute
from pydynox.conditions import And
class Product(Model):
model_config = ModelConfig(table="products")
pk = StringAttribute(hash_key=True)
category = StringAttribute()
price = NumberAttribute()
brand = StringAttribute()
def build_product_filter(
category: str | None = None,
max_price: float | None = None,
brand: str | None = None,
):
"""Build filter from optional parameters."""
conditions = []
if category:
conditions.append(Product.category == category)
if max_price:
conditions.append(Product.price <= max_price)
if brand:
conditions.append(Product.brand == brand)
if len(conditions) == 0:
return None
if len(conditions) == 1:
return conditions[0]
return And(*conditions)
# User searches for electronics under $500
filter_cond = build_product_filter(category="electronics", max_price=500)
Use And() and Or() from pydynox.conditions when you have a list of conditions to combine.
Operators
Comparison operators
| Operator | Example | Description |
|---|---|---|
== |
User.status == "active" |
Equal |
!= |
User.status != "deleted" |
Not equal |
> |
User.age > 18 |
Greater than |
>= |
User.age >= 21 |
Greater or equal |
< |
User.age < 65 |
Less than |
<= |
User.age <= 30 |
Less or equal |
Combining operators
| Operator | Example | Description |
|---|---|---|
& |
(a > 1) & (b < 2) |
Both must be true |
\| |
(a == 1) \| (a == 2) |
Either can be true |
~ |
~a.exists() |
Negates the condition |
Warning
Always use parentheses when combining conditions. Python's operator precedence may not work as expected.
Function conditions
| Function | Example | Description |
|---|---|---|
exists() |
User.email.exists() |
Attribute exists |
does_not_exist() |
User.pk.does_not_exist() |
Attribute doesn't exist |
begins_with() |
User.sk.begins_with("ORDER#") |
String starts with prefix |
contains() |
User.tags.contains("vip") |
List contains value |
between() |
User.age.between(18, 65) |
Value in range (inclusive) |
is_in() |
User.status.is_in("a", "b") |
Value in list |
Nested attributes
Access nested map keys and list indexes:
# Map access
User.address["city"] == "NYC"
# List access
User.tags[0] == "premium"
# Deep nesting
User.metadata["preferences"]["theme"] == "dark"
Error handling
When a condition fails, DynamoDB raises ConditionCheckFailedError:
from pydynox.exceptions import ConditionCheckFailedError
try:
user.save(condition=User.pk.does_not_exist())
except ConditionCheckFailedError:
print("User already exists")
Type hints
Use Condition for type hints: