Skip to content
dhanu docs
Browse pages

SDKs

Python SDK

The official dhanu PyPI package, install it, generate images and text, and handle errors with DhanuError.

The dhanu package wraps the public API for Python. It has no dependencies, it uses only the standard library, and needs Python 3.8 or newer.

Install

pip install dhanu

Set up the client

import os
from dhanu import Dhanu

# Server-side only, never ship your secret key to a client.
dhanu = Dhanu(os.environ["DHANU_API_KEY"])

The key is always passed explicitly. There is no base_url argument: the base URL comes from DHANU_BASE_URL if set, and otherwise defaults to https://api.dhanu.ai. Point DHANU_BASE_URL at http://localhost:3000 for local development.

Generate an image

import base64

image = dhanu.images.generate(
    prompt="a red bicycle on a white background",
    aspect_ratio="1:1",  # "1:1" | "3:4" | "9:16" | "4:3" | "16:9"
)

with open("out.png", "wb") as handle:
    handle.write(base64.b64decode(image.b64))

Returns GeneratedImage(b64, mime_type, model, usage, cost). Nothing is stored server-side, see Generate an image.

Generate text

reply = dhanu.text.generate(
    prompt="Write a haiku about Kathmandu",
    system="You are a concise poet.",
    max_tokens=200,
)

print(reply.text)

Returns GeneratedText(text, model, usage, cost).

max_tokens caps total output tokens and the model’s own reasoning counts against it, so a very small value can return empty text. Leave it unset or generous unless you want a hard limit.

Verify a key

me = dhanu.me()

me.app        # App(id, name) when the key covers exactly one project, else None
me.projects   # [App(id, name), ...], every project the key can reach
me.key.name   # the key's name
me.scope      # ApiScope(level, packages, operations)

Keys that cover several projects

When a key covers more than one project, pass project_id on each generation to say which project to meter against. Without it the call fails with project_id_required (422):

project = dhanu.me().projects[0]

image = dhanu.images.generate(prompt="…", project_id=project.id)
reply = dhanu.text.generate(prompt="…", project_id=project.id)

A key is also scoped to part of the API. me.scope reports what it may call; anything outside that fails with api_key_operation_scope (403) before it runs, so a narrowed key never spends credits on a call it was not meant to make.

Typed requests and responses

dhanu.api_types holds a TypedDict for every request body, query and response, generated from the API contract, so a type checker can read the wire shape while the resource methods keep returning dataclasses:

from dhanu import api_types

body: api_types.TasksCreateRequest = {"input": "Summarise the week", "fileIds": file_ids}
task = dhanu.tasks.create(**{"input": body["input"]})

Retries and timeouts

Every call is retried up to twice on a network error, a timeout, 408, 429 or a 5xx, waiting for Retry-After when the server sends one and backing off exponentially (0.5 s, 1 s, 2 s… capped at 8 s) otherwise. JSON POSTs carry a generated Idempotency-Key on every attempt, so a retry can never run the same call twice. Each attempt times out after 60 s. Tune or switch it off per client:

dhanu = Dhanu(os.environ["DHANU_API_KEY"], max_retries=0, timeout_seconds=10)

A request that never reached the server raises DhanuError with status == 0 and code == "network_error" or "timeout".

Handle errors

Every failure raises DhanuError with a machine-readable code, the HTTP status, and the request_id to quote in support reports. The human-readable message is the exception’s own string.

from dhanu import DhanuError

try:
    dhanu.images.generate(prompt="…")
except DhanuError as err:
    if err.code == "rate_limited":
        ...  # back off and retry, see /developer/reference/rate-limits
    print(err.code, err.status, str(err), err.request_id)

The complete list of codes is on Errors.