Keys, values, and prefixes are strings; empty strings are valid. Values are never None. Everything stays in memory. There are no clocks, threads, persistence, or input type conversions.
Prototype: implement KeyValueStore() with independent storage. set(key, value) inserts or replaces and returns None; get(key) returns its string value or None; delete(key) returns True if it removed a key and False otherwise. Deleting and recreating a key is valid.
Final: implement TransactionalStore() with the same set, get, and delete behavior plus:
begin() -> int: start a nested transaction from the current visible state and return the new depth.commit() -> int: merge only the innermost transaction into its immediate parent, close that transaction, and return the new depth. An outer rollback must still undo an inner commit.rollback() -> int: discard only the innermost transaction, close it, and return the new depth.depth() -> int: return zero for the base view, one after a single unmatchedbegin(), and so on.snapshot(prefix="") -> dict[str, str]: return a fresh dictionary of the currently visible keys starting with the case-sensitive prefix, inserted into the result in ascending key order. An empty prefix includes every key. Mutating the result must not affect any transaction or base view.
Calling commit() or rollback() at depth zero raises RuntimeError and preserves the visible state and depth. Reads see all edits in the innermost view, including uncommitted writes and deletions. Duplicate set replaces a value. Separate instances share no state. Run python main.py --stage prototype, then python main.py --stage final (the default). Edit exercise.py; leave main.py unchanged.
Complete the starter code, then run the checks.
Results and errors will appear here.