How it works
DPDPStack has four moving parts. Understanding them is enough to use the whole library.
The erasure engine
ErasureEngine.request_erasure(...) resolves what should happen to a subject's
data for a purpose, runs your mutation, and records the decision.
res = engine.request_erasure(
subject="user_42", # opaque reference - never PII
policy=rbi_kyc("kyc"), # the retention rule for this purpose
reason="consent_withdrawn",
executor=lambda action: do_the_actual_mutation(action), # your code
)It returns an ErasureResult with status ("erased" or "deferred"),
action, legal_basis, and erase_after. The key idea: the engine never
touches your data itself - it calls your executor (zero-egress).
Legal holds: defer, don't refuse
DPDP requires erasure on withdrawal, but other Indian laws require retention.
DPDPStack reconciles this: when a policy carries a legal_hold_days + legal_basis,
erasure is deferred until the hold lapses rather than refused.
res = engine.request_erasure(subject="user_42", policy=rbi_kyc("kyc"), reason="consent_withdrawn")
res.status # "deferred"
res.erase_after # the date it becomes erasableThis is the uncontested wedge - see Retention policies for the presets and how to build your own.
Anonymize vs delete
A policy's action is either Action.DELETE (hard-delete the row) or
Action.ANONYMIZE (irreversibly null/hash the PII fields, keep the row). Anonymize
is how teams satisfy "erase the person" while keeping regulated/relational records.
The anonymize strategies decide how each field is scrubbed:
| Strategy | Effect |
|---|---|
null | set the field to null/empty |
hashed | replace with a one-way hash |
redact(keep_last=4) | keep the last N chars, mask the rest |
constant("[redacted]") | replace with a fixed value |
from dpdpstack import null, hashed, redact, constantThe hash-chained audit log
Every decision is appended to an AuditLog as a tamper-evident entry: each entry's
hash includes the previous entry's hash, so any edit or deletion breaks the chain.
engine.audit.verify() # True if the chain is intactStores are pluggable:
InMemoryAuditStore- default, ephemeral.JsonlAuditStore- append to a JSONL file.- The Django adapter - a model-backed store (see Django integration).
The chain is also the source of truth for the Certificate of Erasure: a certificate is just a verifiable snapshot of the latest erasure event for a subject.