Your first undo¶
Use Undoable when several model changes should form one reversible edit. Start with a single managed value, then add collections and children as needed.
Installation¶
python -m pip install yidl-undoable
The package targets CPython 3.12–3.15. It depends on YIDL, Astichi, Lifecycle and YIDL Cache. This is not a claim that every OS/Python combination has dependency wheels: Astichi 1.1.3 lacks a Windows CPython 3.15 wheel, and that source-build route is not qualified here. Use an available dependency-wheel combination. Development checkout verification and published-package qualification are separate; see contributor guidance.
Declare a model and its baseline¶
from yidl_undoable import UndoRedoManager, managed, undoable
@undoable
class Document:
title: str = managed(default="Draft")
history = UndoRedoManager()
document = Document(transaction_manager=history)
history.initialize(document)
assert document.title == "Draft"
assert history.cursor == 0 and not history.can_undo()
managed means the value belongs to transactional history. Construction associates
the object with a manager; initialize selects one or more roots and freezes their
starting managed graph as frame zero. Call it once, after setup construction.
All roots and managed descendants must belong to the same manager.
Commit, undo and redo¶
with history.begin():
document.title = "Ready"
assert document.title == "Ready"
assert history.undo() and document.title == "Draft"
assert history.redo() and document.title == "Ready"
A normal outer scope exit commits one frame. Managed setters are available only
while editing; outside a transaction they raise TransactionRequiredError.
undo() and redo() return False when there is no step in that direction.
Navigation is unavailable during an edit.
Discard an unfinished edit¶
try:
with history.begin():
document.title = "Discard me"
raise ValueError("cancel the edit")
except ValueError:
pass
assert document.title == "Ready"
An exceptional scope exit rolls back the joined edit. Rollback creates no frame;
undo navigates a frame that was already committed. For explicit completion use
history.begin(), then history.commit() or history.rollback().
What happens if you undo, then commit a different title? The new frame replaces the redo tail. See frames and views, then edit collections.