Skip to content

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

repository instance-attribute

repository = repository

policy instance-attribute

policy = policy

registry instance-attribute

registry = registry or Registry()

notifier instance-attribute

notifier: Notifier = notifier or NullNotifier()

tenant_id instance-attribute

tenant_id = tenant_id

contact instance-attribute

contact: dict[str, str] = dict(contact or {})

contact_resolver instance-attribute

contact_resolver = contact_resolver

identity_verifier instance-attribute

identity_verifier = identity_verifier

events instance-attribute

events = events or EventBus()

webhooks instance-attribute

webhooks = webhooks

signing_key instance-attribute

signing_key = signing_key.encode() if isinstance(signing_key, str) else signing_key

now

now() -> datetime

for_tenant

for_tenant(tenant_id: str) -> Kit

A view of this kit bound to another tenant. Handlers and collectors are shared.

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"

load_config

load_config(path: str | Path) -> dict[str, Any]

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.

sync_notice

sync_notice(kit: Kit, config: Mapping[str, Any], *, actor: str = 'config') -> list[Notice] | None

Publish the configured notice if it differs from the current one. Returns the new notices or None.

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)

id instance-attribute

id: str

title instance-attribute

title: str

summary instance-attribute

summary: str

text instance-attribute

text: str

The YAML file, comments included, as written by dpdpkit init --template.

config

config() -> dict[str, Any]

The parsed configuration, ready for kit_from_config.

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).

reviewer class-attribute instance-attribute

reviewer: str = ''

firm class-attribute instance-attribute

firm: str = ''

date class-attribute instance-attribute

date: str = ''

scope class-attribute instance-attribute

scope: str = ''

hashes class-attribute instance-attribute

hashes: Mapping[str, str] = field(default_factory=dict)

Notice content hash per locale of the wording that was approved.

describe

describe() -> str

list_templates

list_templates() -> list[PurposeTemplate]

Every shipped template, in a stable order.

load_template

load_template(template_id: str) -> PurposeTemplate

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_checks runs the checks software can make (required links, itemised data and purposes, untranslated text, placeholders) and lists the questions only counsel can answer.
  • notice_hashes gives 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_pack renders 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 = '')

id instance-attribute

id: str

source instance-attribute

source: str

Where the requirement comes from (DPDP Act section or Rule).

requirement instance-attribute

requirement: str

status instance-attribute

status: str

One of CHECK_STATUSES.

detail class-attribute instance-attribute

detail: str = ''

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_workbook writes a blank workbook, or one pre-filled from a purpose template or dpdpkit.yaml.
  • read_workbook reads a filled workbook back and checks every row, listing all problems at once.
  • MappingWorkbook.to_config turns it into dpdpkit.yaml keys that kit_from_config accepts.
  • scan_report renders 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.

engagement instance-attribute

engagement: dict[str, str]

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)

gaps class-attribute instance-attribute

gaps: list[Gap] = 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.

store instance-attribute

store: str

location instance-attribute

location: str

detected_type instance-attribute

detected_type: str

confidence class-attribute instance-attribute

confidence: str = 'high'

rows_sampled class-attribute instance-attribute

rows_sampled: int | None = None

matches class-attribute instance-attribute

matches: int | None = None

data_item class-attribute instance-attribute

data_item: str | None = None

action class-attribute instance-attribute

action: str = ''

notes class-attribute instance-attribute

notes: str = ''

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 = '')

area instance-attribute

area: str

check instance-attribute

check: str

source class-attribute instance-attribute

source: str = ''

feature class-attribute instance-attribute

feature: str = ''

status class-attribute instance-attribute

status: str = 'not checked'

finding class-attribute instance-attribute

finding: str = ''

risk class-attribute instance-attribute

risk: str = ''

owner class-attribute instance-attribute

owner: str = ''

due class-attribute instance-attribute

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).

id property

id: str

overlays property

overlays: list[str]

erasure_pre_notice property

erasure_pre_notice: timedelta

min_log_retention property

min_log_retention: timedelta

grievance_ceiling property

grievance_ceiling: timedelta

request_ceiling property

request_ceiling: timedelta

request_target property

request_target: timedelta
required_links: list[str]

languages property

languages: list[str]

default_language property

default_language: str

age_of_majority property

age_of_majority: int

board_report_window property

board_report_window: timedelta

fiduciary_duties_start property

fiduciary_duties_start: date

load classmethod

load(pack_id: str, overlays: Iterable[str] = ()) -> Policy

from_file classmethod

from_file(path: str | Path, overlays: Iterable[str] = ()) -> Policy

as_dict

as_dict() -> dict[str, Any]

get

get(dotted: str, default: Any = _MISSING) -> Any

third_schedule

third_schedule(fiduciary_class: str) -> tuple[int, timedelta]

(min_users, inactivity_period) for a Third Schedule class such as ecommerce.

diff

diff(other: Policy) -> list[PolicyChange]

Leaf-level differences from self to other.

PolicyChange dataclass

PolicyChange(key: str, old: Any, new: Any)

key instance-attribute

key: str

old instance-attribute

old: Any

new instance-attribute

new: Any

available_packs

available_packs() -> list[str]

Registry

Registry(purposes: Iterable[Purpose] = (), data_items: Iterable[DataItem] = (), processors: Iterable[Processor] = (), data_stores: Iterable[DataStore] = ())

add_data_item

add_data_item(item: DataItem) -> None

add_processor

add_processor(processor: Processor) -> None

add_data_store

add_data_store(store: DataStore) -> None

add_purpose

add_purpose(purpose: Purpose) -> None

from_config classmethod

from_config(config: Mapping[str, Any], tenant_id: str = 'default') -> Registry

Build from the purposes / data_items / processors / data_stores config keys.

purpose

purpose(purpose_id: str) -> Purpose

has_purpose

has_purpose(purpose_id: str) -> bool

purposes

purposes() -> list[Purpose]

consent_purposes

consent_purposes() -> list[Purpose]

data_item

data_item(item_id: str) -> DataItem

data_items

data_items() -> list[DataItem]

data_items_for

data_items_for(purpose_id: str) -> list[DataItem]

processors

processors() -> list[Processor]

processors_for

processors_for(purpose_id: str) -> list[Processor]

data_stores

data_stores() -> list[DataStore]