Metadata-Version: 2.4
Name: sammatigrid
Version: 0.1.0
Summary: SammatiGrid Python SDK — DPDP Act 2023 consent logging, hash-chain verification and PII inventory.
License: MIT
Keywords: dpdp,consent,compliance,privacy,india
Author: SammatiGrid
Author-email: engineering@sammatigrid.in
Requires-Python: >=3.9,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: httpx (>=0.27,<1.0)
Requires-Dist: pydantic (>=2.7,<3.0)
Project-URL: Homepage, https://sammatigrid.in
Project-URL: Repository, https://github.com/sammatigrid/sammatigrid
Description-Content-Type: text/markdown

# sammatigrid (Python)

DPDP Act 2023 consent logging for Python. Built on `httpx` and `pydantic`,
typed throughout, Python 3.9+.

## Quickstart

```bash
pip install sammatigrid
```

```python
from sammatigrid import SammatiGrid

cg = SammatiGrid()                                      # reads SAMMATIGRID_API_KEY
receipt = cg.track("user_123", "KYC_VERIFICATION")      # -> TrackConsentResponse
cg.verify(receipt.hash)                                 # -> valid=True chain_intact=True
```

## What it does

Every `track()` appends an entry to an append-only, SHA-256 hash-chained ledger.
The returned `hash` is a receipt proving that exactly this consent, for exactly
this purpose, existed at this time. `verify()` proves it has not been altered
since. That receipt is the thing you hand a regulator.

## Your users' ids never leave the process

`user_id` is hashed before the request is sent, salted per project:

```python
cg.track("ananya@acme.co", "MARKETING")
# transmitted: {"userId": "cgu_3f9a…", "purpose": "MARKETING", "granted": True}
```

SammatiGrid therefore never holds your user table, and because the salt is
per-project the same person hashes differently in two projects — so two ledgers
cannot be joined on the digest to re-identify anyone.

Already-pseudonymised ids (`cgu_…`) pass through untouched. If you genuinely
need raw ids server-side, pass `hash_user_id=False` — a decision for your DPO
rather than a config default.

## Usage

```python
from sammatigrid import SammatiGrid, ConsentPurpose, ErasureProof, RequestType

with SammatiGrid(api_key="cg_live_…", timeout=10.0, max_retries=3) as cg:
    # Consent — call BEFORE collecting the data it covers (DPDP s.6)
    receipt = cg.track(
        "user_123",
        ConsentPurpose.KYC_VERIFICATION,
        ip="103.21.45.12",
        user_agent="Mozilla/5.0",
        consent_string="I consent to KYC verification",
    )

    # A refusal is evidence too — recorded, not raised
    cg.track("user_123", ConsentPurpose.MARKETING, granted=False)

    cg.verify(receipt.hash)
    cg.list_consents(user_id="user_123", page=1, limit=50)

    # PII inventory
    cg.find_pii()
    cg.scan("conn_pg_prod")

    # Data principal requests
    request = cg.create_request("user_123", RequestType.DELETE)
    cg.list_requests(status="NEW")
    cg.complete_request(
        request.id,
        ErasureProof(
            tables_affected=3,
            columns_nulled=["users.email", "users.phone"],
            note="hard deleted, backups purged",
        ),
    )

    cg.generate_audit_report()
```

`SammatiGrid` is thread-safe — create one per process and share it.

## Django / Flask

```python
# settings.py or app factory — one client for the process
consent = SammatiGrid()

# in a view, before writing the personal data
consent.track(
    str(request.user.pk),
    ConsentPurpose.ORDER_FULFILLMENT,
    ip=request.META.get("REMOTE_ADDR"),
    user_agent=request.META.get("HTTP_USER_AGENT"),
)
```

## Errors

| Exception | Raised when |
| --- | --- |
| `ConfigurationError` | Missing/malformed key, invalid arguments |
| `AuthenticationError` | 401 / 403 |
| `InvalidRequestError` | 400 / 422 |
| `NotFoundError` | 404 |
| `RateLimitError` | 429, after the retry budget is spent |
| `APIError` | 5xx |
| `NetworkError` | Connection failure or timeout |

All inherit `SammatiGridError`, carry a stable `.code`, and expose `.retryable`.
Retries (429 and 5xx) happen automatically with exponential backoff and full
jitter, honouring `Retry-After`.

No exception ever contains a user id, consent string, IP or user agent, so they
are safe to log verbatim.

```python
from sammatigrid import SammatiGridError

try:
    cg.track("user_123", "SUPPORT")
except SammatiGridError as exc:
    logger.warning("consent write failed: %s (%s)", exc.code, exc.request_id)
```

## Development

```bash
poetry install
poetry run pytest
poetry run mypy src
poetry run ruff check .
```

`tests/test_contract.py` diffs the models against
`packages/api/Consentgrid-Openapi.yaml`, so contract drift fails the suite.

## Licence

MIT

