Not legal advice
dpdpkit is software that helps you implement obligations under India's Digital Personal Data Protection Act, 2023 and the DPDP Rules, 2025. It does not provide legal advice and does not guarantee compliance.
Kit and configuration¶
Kit
¶
Kit(repository: Repository, policy: Policy | str, *, registry: Registry | None = None, notifier: Notifier | None = None, overlays: Iterable[str] = (), tenant_id: str = DEFAULT_TENANT, signing_key: bytes | str | None = None, contact: Mapping[str, str] | None = None, contact_resolver: ContactResolver | None = None, identity_verifier: IdentityVerifier | None = None, clock: Clock | None = None, events: EventBus | None = None, webhooks: WebhookDispatcher | None = None)
Entry point to dpdpkit.
kit = Kit(repository=InMemoryRepository(), policy="dpdp-rules-2025.v1") # doctest: +SKIP kit.consent.grant("u_42", "marketing") # doctest: +SKIP
signing_key
instance-attribute
¶
signing_key = signing_key.encode() if isinstance(signing_key, str) else signing_key
config
¶
dpdpkit.yaml: purposes, notice text, contact details and policy selection.
Adapters and the server build a Kit from this file and call sync_notice
at start-up, which publishes a new notice version only when the configured text changed.
STARTER_CONFIG
module-attribute
¶
STARTER_CONFIG = "# dpdpkit configuration.\n# Purposes, notice wording and retention periods are decisions for you and your counsel.\n# dpdpkit records and enforces what you configure; it does not decide what is lawful.\npolicy: dpdp-rules-2025.v1\noverlays: []\n\ncontact:\n name: Example Pvt Ltd\n email: privacy@example.in\n rights_url: https://example.in/privacy/rights\n\ndata_items:\n - {id: email, name: Email address}\n - {id: phone, name: Mobile number}\n - {id: name, name: Full name}\n - {id: order_history, name: Order history}\n\nprocessors:\n - {id: mailer, name: Email delivery provider, country: IN}\n\npurposes:\n - id: account\n title: Create and run your account\n description: We use these details to sign you in and provide the service.\n data_items: [email, phone, name, order_history]\n required: true\n retention: {trigger: last_activity, period_days: 1095}\n - id: marketing\n title: Send you offers\n description: Occasional emails about products and discounts.\n data_items: [email]\n processors: [mailer]\n retention: {trigger: withdrawal, period_days: 0}\n\nnotice:\n title: How we use your personal data\n body: Choose what you agree to. You can change your choices at any time.\n links:\n withdraw: https://example.in/privacy/preferences\n rights: https://example.in/privacy/rights\n board_complaint: https://example.in/privacy/board-complaint # replace with the Board's complaint page\n translations:\n hi:\n title: เคนเคฎ เคเคชเคเฅ เคตเฅเคฏเคเฅเคคเคฟเคเคค เคกเฅเคเคพ เคเคพ เคเคชเคฏเฅเค เคเฅเคธเฅ เคเคฐเคคเฅ เคนเฅเค\n body: เคเฅเคจเฅเค เคเคฟ เคเคช เคเคฟเคธ เคฌเคพเคค เคธเฅ เคธเคนเคฎเคค เคนเฅเคเฅค เคเคช เคเคญเฅ เคญเฅ เค
เคชเคจเฅ เคชเคธเคเคฆ เคฌเคฆเคฒ เคธเคเคคเฅ เคนเฅเคเฅค\n data_items:\n email: เคเคฎเฅเคฒ เคชเคคเคพ\n phone: เคฎเฅเคฌเคพเคเคฒ เคจเคเคฌเคฐ\n name: เคชเฅเคฐเคพ เคจเคพเคฎ\n order_history: เคเคฐเฅเคกเคฐ เคเคพ เคเคคเคฟเคนเคพเคธ\n purposes:\n account:\n title: เคเคชเคเคพ เคเคพเคคเคพ เคฌเคจเคพเคจเคพ เคเคฐ เคเคฒเคพเคจเคพ\n description: เคนเคฎ เคเคจ เคตเคฟเคตเคฐเคฃเฅเค เคเคพ เคเคชเคฏเฅเค เคเคชเคเฅ เคธเคพเคเคจ เคเคจ เคเคฐเคพเคจเฅ เคเคฐ เคธเฅเคตเคพ เคฆเฅเคจเฅ เคเฅ เคฒเคฟเค เคเคฐเคคเฅ เคนเฅเคเฅค\n marketing:\n title: เคเคชเคเฅ เคเคซเคผเคฐ เคญเฅเคเคจเคพ\n description: เคเคคเฅเคชเคพเคฆเฅเค เคเคฐ เคเฅเค เคเฅ เคฌเคพเคฐเฅ เคฎเฅเค เคเคญเฅ-เคเคญเคพเคฐ เคเคฎเฅเคฒเฅค\n"
kit_from_config
¶
kit_from_config(config: Mapping[str, Any] | str | Path, repository: Repository, **kwargs: Any) -> Kit
Build a Kit from a config mapping or file. kwargs override or extend Kit arguments.
templates
¶
Industry purpose templates: complete starter dpdpkit.yaml files for common sectors.
Each template lists the data items, processors, purposes, retention rules and an itemised notice (English and
Hindi) that a typical business in the sector starts from. They are a starting point for the data-mapping
workshop: every purpose, legal basis, retention period and line of notice wording is an assumption for you
and your counsel to confirm. dpdpkit init --template ID writes one to disk.
Each template's notice wording goes through a lawyer review, recorded in reviews.yaml next to the templates
and bound to the notice's content hashes: PurposeTemplate.notice_review reports whether the shipped
wording is reviewed, still awaiting review, or changed since it was reviewed.
PurposeTemplate
dataclass
¶
PurposeTemplate(id: str, title: str, summary: str, text: str)
text
instance-attribute
¶
text: str
The YAML file, comments included, as written by dpdpkit init --template.
notice_hashes
¶
notice_hashes() -> dict[str, str]
Content hash per locale of the notice this template publishes.
notice_review
¶
notice_review() -> NoticeReview
The recorded lawyer review of this template's notice, checked against the current wording.
notes
¶
notes() -> str
The comment block at the top of the file: the sector notes and questions for counsel.
NoticeReview
dataclass
¶
NoticeReview(status: str, reviewer: str = '', firm: str = '', date: str = '', scope: str = '', hashes: Mapping[str, str] = dict())
The lawyer review of a template's notice wording.
status
instance-attribute
¶
status: str
pending (not reviewed yet), reviewed (approved, and the wording is unchanged since) or stale
(approved, but the wording has changed since, so the approval no longer covers it).
hashes
class-attribute
instance-attribute
¶
hashes: Mapping[str, str] = field(default_factory=dict)
Notice content hash per locale of the wording that was approved.
list_templates
¶
list_templates() -> list[PurposeTemplate]
Every shipped template, in a stable order.
review
¶
Legal review packs for notice wording.
dpdpkit records and enforces the notice you configure; whether its wording meets DPDP Act s.5 and Rule 3 is a decision for counsel. This module prepares that review:
notice_checksruns the checks software can make (required links, itemised data and purposes, untranslated text, placeholders) and lists the questions only counsel can answer.notice_hashesgives the content hash of each locale. It is the same hash dpdpkit records in the ledger when the notice is published (notice.published), so a signed review can be matched to the exact published version.review_packrenders the notice, the checks and a sign-off table as Markdown for the reviewer.
dpdpkit notice review runs it from the command line, for a dpdpkit.yaml or a purpose template.
NoticeCheck
dataclass
¶
NoticeCheck(id: str, source: str, requirement: str, status: str, detail: str = '')
source
instance-attribute
¶
source: str
Where the requirement comes from (DPDP Act section or Rule).
notice_checks
¶
notice_checks(kit: Kit, translations: Mapping[str, NoticeContent]) -> list[NoticeCheck]
Automated checks of the notice against the active policy pack, then the questions for counsel.
notice_hashes
¶
notice_hashes(translations: Mapping[str, NoticeContent]) -> dict[str, str]
Content hash per locale, as recorded when the notice is published.
notice_translations_for
¶
notice_translations_for(config: Mapping[str, Any]) -> tuple[Kit, dict[str, NoticeContent]]
Build a throwaway in-memory kit for config and the notice content it would publish, per locale.
review_pack
¶
review_pack(config: Mapping[str, Any], *, source: str = 'dpdpkit.yaml', notes: str = '', prepared_by: str = '', today: date | None = None) -> str
The review pack (Markdown) for the notice in config: summary, sign-off with content hashes, checks and
the full notice in every locale. notes is shown as context for the reviewer (for example the comment block
at the top of a purpose template).
workbook
¶
Data-mapping workbook and scan report for the readiness sprint (pip install "dpdpkit-core[workbook]").
The workbook is an .xlsx spreadsheet filled in during the data-mapping workshop with the client's tech lead
and whoever owns compliance. One sheet per registry table (data items, data stores, processors, purposes), plus
the PII scan findings and a gap list of readiness checks mapped to dpdpkit features.
new_workbookwrites a blank workbook, or one pre-filled from a purpose template ordpdpkit.yaml.read_workbookreads a filled workbook back and checks every row, listing all problems at once.MappingWorkbook.to_configturns it intodpdpkit.yamlkeys thatkit_from_configaccepts.scan_reportrenders the client-facing scan report (Markdown) from the findings and gap list.
The workbook and report hold types, locations and counts only. Sampled values are never written to either.
MappingWorkbook
dataclass
¶
MappingWorkbook(engagement: dict[str, str], data_items: list[dict[str, Any]] = list(), data_stores: list[dict[str, Any]] = list(), processors: list[dict[str, Any]] = list(), purposes: list[dict[str, Any]] = list(), findings: list[ScanFinding] = list(), gaps: list[Gap] = list(), unconfirmed_purposes: list[str] = list(), unscanned_stores: list[str] = list())
A filled data-mapping workbook, checked row by row.
data_items
class-attribute
instance-attribute
¶
data_items: list[dict[str, Any]] = field(default_factory=list)
data_stores
class-attribute
instance-attribute
¶
data_stores: list[dict[str, Any]] = field(default_factory=list)
processors
class-attribute
instance-attribute
¶
processors: list[dict[str, Any]] = field(default_factory=list)
purposes
class-attribute
instance-attribute
¶
purposes: list[dict[str, Any]] = field(default_factory=list)
findings
class-attribute
instance-attribute
¶
findings: list[ScanFinding] = field(default_factory=list)
unconfirmed_purposes
class-attribute
instance-attribute
¶
unconfirmed_purposes: list[str] = field(default_factory=list)
Purpose IDs whose "Counsel confirmed" cell is not yes.
unscanned_stores
class-attribute
instance-attribute
¶
unscanned_stores: list[str] = field(default_factory=list)
Data store IDs whose "Scanned" cell is not yes.
to_config
¶
to_config(base: Mapping[str, Any] | None = None) -> dict[str, Any]
dpdpkit.yaml keys from the workbook. Keys the workbook does not cover (notice...) come from base.
ScanFinding
dataclass
¶
ScanFinding(store: str, location: str, detected_type: str, confidence: str = 'high', rows_sampled: int | None = None, matches: int | None = None, data_item: str | None = None, action: str = '', notes: str = '')
One row of the Scan findings sheet. Counts only; the sampled values are never recorded.
unregistered
property
¶
unregistered: bool
Personal data with no data item in the registry (false positives excluded).
Gap
dataclass
¶
Gap(area: str, check: str, source: str = '', feature: str = '', status: str = 'not checked', finding: str = '', risk: str = '', owner: str = '', due: str = '')
new_workbook
¶
new_workbook(config: Mapping[str, Any] | None = None, *, engagement: Mapping[str, str] | None = None) -> bytes
A data-mapping workbook as .xlsx bytes, pre-filled from config (a template or dpdpkit.yaml).
read_workbook
¶
read_workbook(source: str | Path | bytes) -> MappingWorkbook
Read and check a filled workbook. Raises ConfigError listing every problem with its sheet and row.
scan_report
¶
scan_report(book: MappingWorkbook, *, today: date | None = None) -> str
The client-facing scan report (Markdown) for a filled workbook. Types, locations and counts only.
Policy
¶
Policy(data: Mapping[str, Any])
An immutable, validated policy pack (with any overlays already merged).
third_schedule
¶
third_schedule(fiduciary_class: str) -> tuple[int, timedelta]
(min_users, inactivity_period) for a Third Schedule class such as ecommerce.
PolicyChange
dataclass
¶
PolicyChange(key: str, old: Any, new: Any)
Registry
¶
Registry(purposes: Iterable[Purpose] = (), data_items: Iterable[DataItem] = (), processors: Iterable[Processor] = (), data_stores: Iterable[DataStore] = ())