stapel-gdpr
GDPR compliance: staged async data export (Art. 15/20) with status polling and download; subject-scoped erasure (Art. 17) — account, workspace, meeting, recording, document, file — each proven by one receipt per data owner that claims the subject type, with a purge SLA, timeout sweep and a subprocessor ledger behind
fully_erased_by; account closure keeps its cancellable 30-day grace and produces an ErasureRequest at grace end; DSAR intake (Art. 12) with automated acknowledgement and both statutory clocks; data-owner liveness probing; inactivity closure, retention cleanup, LegalHold and ReRegistrationHash; orchestrated through an in-process GDPRProvider registry (stapel_core.gdpr.gdpr_registry) plus comm confirmation (gdpr.section.erased) for remote owners.
Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.
Install
pip install stapel-gdpr
At a glance
| Fact | Value |
|---|---|
| Version | 0.6.0 |
| Python | >=3.11 (3.11, 3.12, 3.13, 3.14) |
| HTTP operations | 15 |
| Config axes | 3 |
| Usage surface | 18 |
| Extension points | 4 |
| Error codes | 60 |
| Fleet dependencies | stapel-core |
Documentation
OpenAPI · capabilities.json · llms.txt (for agents)
Quick start
# settings.py
INSTALLED_APPS = [
...
'stapel_gdpr',
]
MIDDLEWARE = [
...
# After authentication: refuses every request of an account being erased,
# whatever a still-valid token claims.
'stapel_gdpr.guards.AccountClosureGuardMiddleware',
]
STAPEL_GDPR = {
# Every store holding personal data, mapped to the subjects it holds it
# about. Erasure is only ever reported complete when each of these
# returned a deletion receipt, so an owner missing here is a store that
# quietly keeps the data. `manage.py check` fails while this is empty.
#
# These are the names the LIBRARIES declare, not app labels: the `cdn`
# app owns `media`, the `profiles` app owns `profile`. A name no
# installed library declares is inferred remote and times out in
# silence, so `manage.py check` refuses it (gdpr.E009), and an installed
# owner missing from this map — a store no erasure ever waits for — is
# gdpr.E010.
'DATA_OWNERS': {
'auth': ['account'],
'profile': ['account'],
'media': {'subject_types': ['account', 'workspace', 'file'],
'kind': 'remote'},
},
'DATA_OWNERS_VERSION': '2026-09-07.1',
# How the user's sessions are revoked at closure. Auto-detected when
# stapel-auth is installed; without any seam, closure is refused rather
# than performed with live tokens left behind.
'SESSION_REVOKER': 'stapel_auth.sessions.services.SessionService.revoke_all',
}
Run manage.py check after wiring: a missing or stale data-owner inventory,
an owner name no installed library declares, an installed owner the inventory
omits, hash rows written outside store_hashes, and every open escape hatch
are reported there rather than discovered in an audit.
Closing an account, from the client's side
POST user/account/close revokes every session of the subject — including the
one that made the call. Its 202 therefore carries closure_token, the
capability that survives the revocation:
// 202 Accepted
{
"status": "grace",
"grace_ends_at": "2026-10-07T09:12:00Z",
"can_cancel": true,
// Issued ONCE. Store it; there is no way to ask for it again.
"closure_token": "eyJjaWQiOjQyfQ:1uL9Wq:0S3n..."
}
Send it back as a header — never in a URL — to poll or to undo:
GET /gdpr/api/v1/user/account/close/status
POST /gdpr/api/v1/user/account/cancel-close
X-Closure-Token: eyJjaWQiOjQyfQ:1uL9Wq:0S3n...
The token is signed with the project SECRET_KEY, nothing is stored, it is
scoped to that one closure, and it expires with the grace period
(error.401.gdpr.closure_token_expired after that). A live session still works
wherever the host's auth backend authenticates a deactivated user; Django's
default backend does not, which is what the token is for.
Bus events
Emits
| user.deleted | schema | All user PII permanently deleted after grace period. Every package storing user |
| user.deletion_cancelled | schema | Account closure cancelled during the grace period; every reversible reaction to user.deletion_initiated must be lifted. |
| user.deletion_initiated | schema | Account closure started. 30-day grace period begins; account is deactivated. |
| user.export_ready | schema | Data export archive is ready for download. |
| user.sessions_revoked | schema | Closure revoked every session and access JTI of the user. |
License
MIT — see LICENSE.
This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.
Release files for stapel-gdpr 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stapel_gdpr-0.6.0.tar.gz | 186.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stapel_gdpr-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 345.1 kB
Release files / stapel_gdpr-0.6.0.tar.gz
| Download URL | stapel_gdpr-0.6.0.tar.gz |
|---|---|
| Size | 186.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a9ddf9272c5e47f5d42b4af7c9a957175982bba765a67edcb5c65288989bed2f
|
|
BLAKE2b-256 checksum How to use checksums |
b749026ff37912f18f7433b703eb7754dd99ebe0ba4bcbc642c3591d4797a0fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / stapel_gdpr-0.6.0-py3-none-any.whl
| Download URL | stapel_gdpr-0.6.0-py3-none-any.whl |
|---|---|
| Size | 158.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4cf52c7a9d60a053e55a546cd1830ca12aa246622fb872306bc99a95d5cde7a
|
|
BLAKE2b-256 checksum How to use checksums |
e555da93f3fc38a6c89a4a7629be4dca41953745e5334e08a71aa1e3f7ec2d3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log