A Python decorator for logging attribute changes in class. Track every modification to your object's attributes with automatic logging and changelog.
Tracking attribute changes helps me understand how a complex class behaves and speeds up debugging when issues occur. Having said this, I need a simple way to log all attribute changes without writing boilerplate code for each attribute.
-
Automatic attribute change tracking: Decorated class automatically log all attribute changes
-
Container support: Tracks value modifications inside built-in containers (dict, list, set, tuple)
-
Flexible logging: Use good ol
printstatement or any custom logger function -
View changelog: Query complete change history for any attribute
-
Exclude specific attributes: Optionally exclude specific attributes from tracking
-
Custom actions: Execute custom functions when specific attributes change
pip install selv
Or using mighty uv:
uv add selv
from selv import selv @selv class Counter: def __init__(self): self.value = 0 def increment(self): self.value += 1 def decrement(self): self.value -= 1 # Create counter and use it counter = Counter() counter.increment() counter.decrement() # Changes are automatically logged: # [Counter] value: 0 -> 1 # [Counter] value: 1 -> 0
# View all changes all_changes = counter.view_changelog() print(all_changes) # [ # {'time': datetime, 'attr': 'value', 'from': None, 'to': 0}, # {'time': datetime, 'attr': 'value', 'from': 0, 'to': 1}, # {'time': datetime, 'attr': 'value', 'from': 1, 'to': 0} # ] # View changes for specific attribute value_changes = counter.view_changelog("value") print(value_changes) # [ # {'time': datetime, 'from': None, 'to': 0}, # {'time': datetime, 'from': 0, 'to': 1}, # {'time': datetime, 'from': 1, 'to': 0} # ] # View changes grouped by attribute grouped = counter.view_changelog(format="attr") print(grouped) # { # 'value': [ # {'time': datetime, 'from': None, 'to': 0}, # {'time': datetime, 'from': 0, 'to': 1}, # {'time': datetime, 'from': 1, 'to': 0} # ] # }
from selv import selv def log_inventory_change(inventory): total = sum(inventory.values()) print(f"Total items in inventory: {total}") @selv(actions={"inventory": log_inventory_change}) class Store: def __init__(self): self.inventory = {"apples": 10, "bananas": 5} store = Store() store.inventory["oranges"] = 8 # [Store] inventory = {'apples': 10, 'bananas': 5} (initialized) # Total items in inventory: 15 # [Store] inventory: {'apples': 10, 'bananas': 5} -> {'apples': 10, 'bananas': 5, 'oranges': 8} # Total items in inventory: 23
The @selv decorator has a few parameters to customize its behavior.
-
track_private(bool, default:True)- When
True: Tracks all attributes including those starting with_(private attributes) - When
False: Only tracks public attributes (those not starting with_)
- When
-
logger(Callable[[str], None], default:print)- Function to use for logging change messages (e.g.,
logging.info,logging.debug) - Can be any function that accepts a string argument
- Function to use for logging change messages (e.g.,
-
exclude(List[str], default:None)- List of attribute names to exclude from tracking
- Useful for exclude sensitive data or unimportant attributes
-
actions(Dict[str, Callable[[Any], None]], default:None)- Dictionary mapping attribute names to functions that are called when the attribute changes
- Each function receives the new value of the attribute as its argument
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.