"""Author: Sandeep Belgavi. Local transactional actions; no external tools.""" import json import sqlite3 class ActionStore: """One SQLite transaction covers this local effect and its receipt only.""" def __init__(self, path=':memory:'): self.db = sqlite3.connect(path) self.db.executescript(''' CREATE TABLE IF NOT EXISTS actions( tenant TEXT, op TEXT, intent TEXT, state TEXT, PRIMARY KEY(tenant, op)); CREATE TABLE IF NOT EXISTS holds( tenant TEXT, op TEXT, service TEXT, PRIMARY KEY(tenant, op)); ''') def reserve(self, tenant, op, service, authorized=True, fail=False): if not authorized: raise PermissionError('Action scope required') if not all(isinstance(x, str) and x.strip() for x in (tenant, op, service)): raise ValueError('Nonempty identifiers required') with self.db: self.db.execute('BEGIN IMMEDIATE') row = self.db.execute('SELECT intent,state FROM actions WHERE tenant=? AND op=?', (tenant, op)).fetchone() if row: if row[0] != service: raise ValueError('Idempotency key conflicts with prior intent') return row[1] self.db.execute('INSERT INTO holds VALUES(?,?,?)', (tenant, op, service)) if fail: raise RuntimeError('Injected interruption before receipt') self.db.execute('INSERT INTO actions VALUES(?,?,?,?)', (tenant, op, service, 'reserved')) return 'reserved' def compensate(self, tenant, op, authorized=True): if not authorized: raise PermissionError('Compensation scope required') with self.db: self.db.execute('BEGIN IMMEDIATE') row = self.db.execute('SELECT state FROM actions WHERE tenant=? AND op=?', (tenant, op)).fetchone() if row is None: raise KeyError(op) self.db.execute('DELETE FROM holds WHERE tenant=? AND op=?', (tenant, op)) self.db.execute('UPDATE actions SET state=? WHERE tenant=? AND op=?', ('compensated', tenant, op)) return 'compensated' def active(self): return self.db.execute('SELECT count(*) FROM holds').fetchone()[0] def close(self): self.db.close() def main(): store = ActionStore() first = store.reserve('acme', 'incident-42-hold', 'api') retry = store.reserve('acme', 'incident-42-hold', 'api') compensated = store.compensate('acme', 'incident-42-hold') print(json.dumps(dict(first=first, retry=retry, compensated=compensated, active=store.active()), sort_keys=True)) store.close() if __name__ == '__main__': main()