Skip to content

Lifecycle hooks

Run code before or after model operations. Hooks let you add validation, logging, or any custom logic without cluttering your main code.

Key features

  • Validation before save
  • Logging after operations
  • Data transformation
  • Side effects like sending emails

Getting started

Hooks are methods decorated with special decorators. When you call save(), delete(), or update(), pydynox automatically runs the matching hooks.

Available hooks

Hook When it runs
@before_save Before save()
@after_save After save()
@before_delete Before delete()
@after_delete After delete()
@before_update Before update()
@after_update After update()
@after_load After get() or query

Basic usage

Here's a common pattern: validate and normalize data before saving, then log after:

from pydynox import Model, ModelConfig
from pydynox.attributes import StringAttribute
from pydynox.hooks import after_save, before_save


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

    pk = StringAttribute(hash_key=True)
    email = StringAttribute()
    name = StringAttribute()

    @before_save
    def validate_email(self):
        if not self.email or "@" not in self.email:
            raise ValueError("Invalid email")

    @before_save
    def normalize(self):
        self.email = self.email.lower().strip()
        self.name = self.name.strip()

    @after_save
    def log_save(self):
        print(f"Saved user: {self.pk}")


# Hooks run automatically
user = User(pk="USER#1", email="JOHN@TEST.COM", name="john doe")
user.save()  # Validates, normalizes, then logs

# Skip hooks if needed
user.save(skip_hooks=True)

In this example:

  1. validate_email runs first and raises an error if the email is invalid
  2. normalize runs next and cleans up the data
  3. The item is saved to DynamoDB
  4. log_save runs last and prints a message

If any before_* hook raises an exception, the operation stops and the item is not saved.

Advanced

Multiple hooks of the same type

You can have multiple hooks of the same type. They run in the order they're defined in the class:

class User(Model):
    @before_save
    def first_hook(self):
        print("This runs first")

    @before_save
    def second_hook(self):
        print("This runs second")

All hooks example

Here's a model with all available hooks:

from pydynox import Model, ModelConfig
from pydynox.attributes import StringAttribute
from pydynox.hooks import (
    after_delete,
    after_load,
    after_save,
    after_update,
    before_delete,
    before_save,
    before_update,
)


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

    pk = StringAttribute(hash_key=True)
    name = StringAttribute()

    @before_save
    def on_before_save(self):
        print("Before save")

    @after_save
    def on_after_save(self):
        print("After save")

    @before_delete
    def on_before_delete(self):
        print("Before delete")

    @after_delete
    def on_after_delete(self):
        print("After delete")

    @before_update
    def on_before_update(self):
        print("Before update")

    @after_update
    def on_after_update(self):
        print("After update")

    @after_load
    def on_after_load(self):
        print("After load (get or query)")

Skipping hooks

Sometimes you need to bypass hooks. For example, during data migration or when fixing bad data.

Skip hooks for a single operation:

user.save(skip_hooks=True)
user.delete(skip_hooks=True)
user.update(skip_hooks=True, name="Jane")

Or disable hooks for all operations on a model:

class User(Model):
    class Meta:
        table = "users"
        skip_hooks = True  # All hooks disabled by default

Warning

Be careful when skipping hooks. If you have validation in before_save, skipping it means invalid data can be saved.

Common patterns

Pattern Hook Example
Validation @before_save Check email format, required fields
Normalization @before_save Lowercase email, trim whitespace
Timestamps @before_save Set updated_at field
Logging @after_save Log saved item ID
Audit @after_save Write to audit table
Cleanup @after_delete Delete related data, files
Transformation @after_load Format dates, compute fields

Hooks and transactions

Hooks run for each item in a transaction. If you're saving 10 items in a transaction, before_save runs 10 times.

If a hook raises an exception, the entire transaction fails and nothing is saved.