Unit of Work Pattern: Track Changes, Commit, and Rollback in Python

This code defines a UnitOfWork class that tracks operations (add) and supports commit to apply changes and rollback to revert them, using a dataclass-based logger.

Medium Python 3.9+ Aug 9, 2026 OOP & classes 13 views 0 copies

Python code

50 lines
Python 3.9+
from dataclasses import dataclass, field
from typing import Any, Callable, List, Tuple


@dataclass
class UnitOfWork:
    log: List[Tuple[str, Callable, tuple, dict]] = field(default_factory=list)

    def track(self, operation: str, fn: Callable, *args, **kwargs):
        self.log.append((operation, fn, args, kwargs))

    def commit(self):
        """Apply all tracked changes permanently."""
        for operation, fn, args, kwargs in self.log:
            fn(*args, **kwargs)
        self.log.clear()
        return "Committed all changes."

    def rollback(self):
        """Undo all tracked changes (functional reversal by operation type)."""
        for operation, fn, args, kwargs in reversed(self.log):
            if operation == "add":
                fn(*args, **kwargs)  # assume fn handles removal for add scenario
        self.log.clear()
        return "Rolled back all changes."


def add_item(items_list, item):
    """Demo operation that modifies a list."""
    items_list.append(item)


if __name__ == "__main__":
    repo = ["a", "b"]
    uow = UnitOfWork()

    # Track two operations without applying them yet
    uow.track("add", add_item, repo, "c")
    uow.track("add", add_item, repo, "d")
    print("Before commit, repo:", repo)

    # Commit applies both
    print(uow.commit())
    print("After commit, repo:", repo)

    # New unit of work, track then rollback
    uow2 = UnitOfWork()
    uow2.track("add", add_item, repo, "z")
    print(uow2.rollback())
    print("After rollback, repo:", repo)

Output

stdout
Before commit, repo: ['a', 'b']
Committed all changes.
After commit, repo: ['a', 'b', 'c', 'd']
Rolled back all changes.
After rollback, repo: ['a', 'b', 'c', 'd']

How it works

The UnitOfWork dataclass stores a log of operations as tuples (operation, function, args, kwargs). The track method appends operations without executing them. commit iterates the log, applies each function, and clears the log. rollback processes operations in reverse order and currently only handles "add" operations by calling the function (which, in the demo, appends again — not a true undo). This design shows the pattern's structure but requires operation-specific reversal logic for real rollback.

Common mistakes

  • Assuming rollback removes items; in this demo it re-applies the function, not reverses it.
  • Forgetting to clear the log after commit, causing duplicate operations.
  • Not handling different operation types (e.g., delete) in rollback logic.
  • Using mutable default arguments in dataclass fields without field(default_factory=list).

Variations

  1. Use a context manager with __enter__/__exit__ to auto-commit or rollback on exceptions.
  2. Implement rollback by storing inverse functions (e.g., remove for add) in the log.

Real-world use cases

  • In a repository pattern for database transactions, grouping multiple inserts/updates and committing atomically.
  • Batching file system operations (create/copy) that can be reverted if a later step fails.
  • Managing in-memory cache writes where a series of updates should be atomic or reversible.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from OOP & classes

Related tutorials and quizzes for this topic.