FastAPI / SQLAlchemy integration
The SQLAlchemy adapter (pip install "dpdpstack-python-sdk[sqlalchemy]") runs the engine against
your own mapped models, with a database-backed hash-chained audit store — still
zero-egress, since the mutation happens on your instance in your database. It mirrors
the Django adapter; the @pii declaration is shared between the two.
Map the audit store
You own the Base/registry, so map the audit entry once from the provided mixin:
from sqlalchemy.orm import DeclarativeBase
from dpdpstack.contrib.sqlalchemy.models import DpdpAuditEntryMixin
class Base(DeclarativeBase):
...
class DpdpAuditEntry(Base, DpdpAuditEntryMixin):
__tablename__ = "dpdp_audit_entries"
# Base.metadata.create_all(engine) # or an Alembic migrationDeclare PII once with @pii
The @pii(...) decorator records each model's PII fields and how to anonymize them,
so you don't pass pii_fields= on every call.
from dpdpstack import null, redact
from dpdpstack.contrib.sqlalchemy.service import pii
@pii(name=null, email=null, phone=redact(keep_last=4))
class User(Base):
__tablename__ = "users"
...Erase per a retention policy
erase_instance runs the engine against your Session. You commit the transaction.
from dpdpstack import RetentionPolicy, Action, rbi_kyc
from dpdpstack.contrib.sqlalchemy.service import erase_instance
# Hard delete + audit
erase_instance(session, user, audit_model=DpdpAuditEntry, subject=user.external_ref,
policy=RetentionPolicy(purpose="marketing", action=Action.DELETE))
# Anonymize PII, keep the (regulated) row — uses the @pii declaration above
erase_instance(session, user, audit_model=DpdpAuditEntry, subject=user.external_ref,
policy=RetentionPolicy(purpose="profile", action=Action.ANONYMIZE))
# KYC withdrawal → deferred under RBI hold, nothing deleted, basis recorded
erase_instance(session, user, audit_model=DpdpAuditEntry, subject=user.external_ref,
policy=rbi_kyc("kyc"))
session.commit()FastAPI sketch
@app.delete("/users/{user_id}")
def forget_user(user_id: int, session: Session = Depends(get_session)):
user = session.get(User, user_id)
res = erase_instance(
session, user, audit_model=DpdpAuditEntry, subject=user.external_ref,
policy=RetentionPolicy(purpose="profile", action=Action.ANONYMIZE),
)
session.commit()
return {"status": res.status, "action": res.action}The audit chain verifies with audit_log(session, DpdpAuditEntry).verify(), and you can
issue a Certificate of Erasure from it exactly as in the framework-agnostic quickstart.