A staged inventory update computes a candidate state and commits it only after every transition satisfies the stock invariant.
Python inventory project: validate a batch before replacing stored state
Operation contract
The ledger copies its current stock, applies signed changes to that candidate and rejects any negative result. A failed batch leaves the original state intact; a later valid batch commits both item changes. The snapshot method returns another outer dictionary copy so ordinary caller changes do not edit the ledger directly.
Failure and ownership boundary
This model uses scalar integer values and runs in one thread. It provides process-local all-or-nothing mutation, not durable transactions, authorization or concurrent compare-and-set. A sequence that dips below zero and later recovers is still rejected because each transition must be valid. Python sqlite3 transactions: bind values and roll back failed batches is a separate persistence implementation.
Working program
class InventoryLedger:
def __init__(self):
self._stock = {"paper": 8, "ink": 4}
def apply(self, changes):
if len(changes) > 100:
raise ValueError("batch too large")
candidate = self._stock.copy()
for item, change in changes:
if item not in candidate or type(change) is not int:
raise ValueError("known item and integer change required")
next_quantity = candidate[item] + change
if next_quantity < 0:
raise ValueError("negative stock")
candidate[item] = next_quantity
self._stock = candidate
def snapshot(self):
return self._stock.copy()
ledger = InventoryLedger()
try:
ledger.apply([("paper", -2), ("ink", -9)])
except ValueError:
print("batch rejected")
print(ledger.snapshot())
ledger.apply([("paper", -2), ("ink", 1)])
print(ledger.snapshot())Output
batch rejected
{'paper': 8, 'ink': 4}
{'paper': 6, 'ink': 5}Costs and limits
Copying s stock keys and checking n changes takes expected O(s+n) time and O(s) candidate storage. The fixture starts with two keys; unrestricted state size, concurrency and crash recovery are not addressed.
Common Mistakes
- Commit the candidate only after every transition passes.
- A local dictionary is not durable or thread-safe storage.
Connected lessons
Python dictionaries: insertion order and duplicate-key replacement, Python properties: validate mutation at the public attribute boundary, Python sqlite3 transactions: bind values and roll back failed batches, Java multi-map invariants: one lock for a reservation boundary.
Apply this boundary
Python rollback exercise: reject a later failure without keeping earlier edits, Python locks: protect the complete inventory transition.
Related Python operation checks
Python SQLite job project: reject conflicting replays by request identity.
