Metadata-Version: 2.4
Name: dhanavega-sdk
Version: 0.1.0
Summary: Official Python client for the Dhanavega API — generated Pydantic models (datamodel-code-generator) over a thin httpx client core. See issue #41, ADR 0037.
Project-URL: Repository, https://github.com/Sysmeadac/dhanavega
Project-URL: Homepage, https://github.com/Sysmeadac/dhanavega/tree/main/packages/sdk-python
License: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2
Description-Content-Type: text/markdown

# dhanavega-sdk

Official Python client for the Dhanavega API. Generated Pydantic v2 models (via
[datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator)) over a thin `httpx`
client core — auth, `Idempotency-Key` autogeneration, RFC 9457 `problem+json` error parsing, `If-Match`
concurrency, and cursor pagination. See issue #41 and
[ADR 0037](../../planning/architecture-docs/adr/0037-sdk-codegen-and-publication.md) for the design
rationale.

> **Status:** buildable and test-covered; **not yet published to PyPI** (issue #41 — publication is a
> manual step performed by the maintainers once the platform reaches its India-GA milestone). Everything
> below documents the eventual `pip install dhanavega-sdk` experience.

## Install

```sh
pip install dhanavega-sdk
# or
uv add dhanavega-sdk
```

## Quickstart

```python
from dhanavega_sdk import DhanavegaClient

client = DhanavegaClient(base_url="https://api.dhanavega.example", token="…")

response = client.request("los", "GET", "/los/leads")
print(response.data)
```

Or with a token getter (refreshed per request):

```python
client = DhanavegaClient(
    base_url="https://api.dhanavega.example",
    get_token=lambda: fetch_short_lived_token(),
)
```

An async client is available too:

```python
from dhanavega_sdk import AsyncDhanavegaClient

async with AsyncDhanavegaClient(base_url="...", token="...") as client:
    response = await client.request("los", "GET", "/los/leads")
```

## Auth

`tenant_id` is **never** sent by the client — the API derives it server-side from the bearer JWT
(`api-docs/02-auth-tenancy-scopes.md`). Supply either a static `token` or a `get_token` callable; the
client sets the `Authorization: Bearer <token>` header on every request.

## Idempotency

Every `POST`/`PUT`/`PATCH`/`DELETE` **requires** an `Idempotency-Key` header
(`api-docs/04-idempotency-concurrency-webhooks.md` §1). The client autogenerates a `uuid4` for every
mutating request unless you pass your own (e.g. to deliberately replay an attempt):

```python
client.request("los", "POST", "/los/applications", json=body, idempotency_key="my-own-key")
```

A stored replay comes back with `response.idempotency_replayed is True`.

## Error handling

Every non-2xx response raises `DhanavegaProblemError` — never a bare `httpx.HTTPStatusError` — parsed
from the platform's RFC 9457 `application/problem+json` envelope
(`api-docs/03-errors-pagination.md` §1). Branch on `error.code` (a `DhanavegaErrorCode`), never on
`error.detail` text:

```python
from dhanavega_sdk import DhanavegaProblemError

try:
    client.request("los", "POST", "/los/applications", json=body)
except DhanavegaProblemError as error:
    if error.code == "VALIDATION_FAILED":
        for field_error in error.errors or []:
            print(field_error.pointer, field_error.rule)
    elif error.code == "RATE_LIMITED":
        print("retry after", error.retry_after)
```

## Concurrency (`If-Match`)

```python
from dhanavega_sdk import with_if_match

client.request(
    "los", "PATCH", f"/los/applications/{app_id}",
    json=patch_body,
    **with_if_match(etag),
)
```

Missing `If-Match` on a mutable-entity mutation → `428 PRECONDITION_REQUIRED`; a stale one → `412
VERSION_CONFLICT`.

## Pagination

```python
from dhanavega_sdk import paginate

for lead in paginate(lambda cursor: client.request(
    "los", "GET", "/los/leads", params={"page[after]": cursor} if cursor else None,
).data):
    print(lead)
```

`paginate` walks `page.has_more` / `page.next_cursor` until the stream ends — cursors are opaque; never
parse or construct them yourself.

## Regenerating models

```sh
uv sync --group codegen
uv run --group codegen python scripts/generate_models.py
```

Regenerates every committed file under `src/dhanavega_sdk/models/` from
`planning/api-docs/openapi/**`. Run this whenever a contract YAML changes — `tests/test_models_drift.py`
fails CI if the committed models fall out of sync with the contract.

## Publish procedure (manual, first release)

Publication is intentionally **not** automated yet (issue #41). CI's `sdk-release.yml` carries a
`publish-sdk-pypi` job gated behind the `PUBLISH_ENABLED` repo variable, which is unset today. To publish
manually once the maintainers decide to:

```sh
cd packages/sdk-python
uv build
uv publish --token "$PYPI_TOKEN"
```

## Development

```sh
uv sync
uv run ruff check .
uv run ruff format --check .
uv run mypy src
uv run pytest
```
