Metadata-Version: 2.5
Name: quirepdf
Version: 0.2.0
Summary: Python SDK for the Quire API: send JSON, get back a polished PDF.
Project-URL: Homepage, https://quirepdf.dev
Project-URL: Documentation, https://quirepdf.dev/docs/sdk-python
Project-URL: Pricing, https://quirepdf.dev/pricing
Author-email: Quire PDF <support@quirepdf.dev>
License-Expression: MIT
License-File: LICENSE
Keywords: invoice,json-to-pdf,pdf,pdf-generation,quire,receipt
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business
Classifier: Topic :: Printing
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# quirepdf (Python)

Send JSON, get back a polished PDF. Python 3.9+, standard library only, synchronous.

## Install

```sh
pip install quirepdf
```

## Quickstart

Any JSON object renders. Quire picks the best-matching template, or a clean generic layout:

```python
from quirepdf import Quire

client = Quire()  # reads QUIRE_API_KEY (and QUIRE_API_URL)

order = {
    "order_id": "ORD-88213",
    "customer": {"name": "Lena Fischer", "email": "lena@example.de"},
    "lines": [{"sku": "TS-BLK-M", "name": "Organic tee", "qty": 2, "price": 29.0}],
    "currency": "€",
}
result = client.render(order)
result.save("order.pdf")
print(result.template, result.template_source, result.pages)  # document fallback 1
if result.hint:
    print("almost matched:", result.hint)  # e.g. "invoice (seller is required)"
```

Name a template and use the generated types so your editor checks the fields:

```python
from quirepdf.types import InvoiceData

invoice: InvoiceData = {
    "number": "INV-2026-0142",
    "issued": "1 Oct 2026",
    "due": "15 Oct 2026",
    "seller": {"name": "Northwind Studio", "address": ["221 Market Street", "San Francisco, CA 94105"]},
    "customer": {"name": "Ravi Kumar", "address": ["14 Residency Road", "Bengaluru 560025"]},
    "items": [{"description": "Pro plan", "qty": 1, "unit_price": 49}],
    "status": "due",  # Literal["draft", "due", "paid", "overdue", "void"]
}

pdf = client.render(invoice, template="invoice")
png = client.render(invoice, template="invoice", format="png", page=1)  # preview one page
print(pdf.content[:5], len(png.content), pdf.render_ms, pdf.quota)    # Quota(limit=100, remaining=97)
```

`RenderResult` fields: `content` (the file as `bytes`), `content_type`, `pages`, `template`,
`template_source` (`explicit` | `detected` | `fallback`), `hint`, `render_ms`, `quota`, and `save(path)`.

Other calls:

```python
check = client.validate(invoice, template="invoice")   # free, nothing rendered
check.valid, check.errors                               # False, [{"path": "...", "message": "..."}]

client.templates.list()            # [{"name": "invoice", "title": "Invoice", ...}, ...]
client.templates.get("invoice")    # {"template": {...}, "schema": {...}, "sample": {...}}
client.usage()                     # {"plan": "free", "used": 3, "limit": 100, "remaining": 97, ...}

new = client.keys.create(name="ci")   # new["api_key"] is shown only once
client.keys.list()                    # [{"id", "prefix", "name", "current": bool, ...}]
client.keys.revoke(new["key"]["id"])
```

## Errors

Every failure raises `QuireError` with `status`, `type`, `message` and `fields`:

```python
from quirepdf import QuireError

try:
    client.render({"number": "INV-1"}, template="invoice")
except QuireError as err:
    print(err.status, err.type)  # 422 invalid_data
    print(err)                   # data has 5 problems
                                 #   - issued is required
                                 #   - due is required ...
    for f in err.fields:
        print(f["path"], f["message"])
```

Common types: `invalid_data`, `unknown_template`, `unauthorized`, `quota_exceeded`, `bad_request`,
`payload_too_large`, `timeout`. Client-side failures have `status == 0`: `type == "network"`
(can't connect) or `type == "timeout"` (no reply within `timeout` seconds). The SDK never retries.

## Configuration

| Argument   | Environment     | Default                 |
|------------|-----------------|-------------------------|
| `api_key`  | `QUIRE_API_KEY` | none (no `Authorization` header is sent) |
| `base_url` | `QUIRE_API_URL` | `https://api.quirepdf.dev` |
| `timeout`  |                 | `30.0` seconds          |

```python
client = Quire(api_key="qk_...", base_url="http://127.0.0.1:8787", timeout=10)
```

A local engine in open mode (`make serve`) needs no key.

## Development

```sh
python3 scripts/gen_types.py          # regenerate src/quirepdf/types.py after a schema change
python3 scripts/gen_types.py --check  # fail if it is stale
PYTHONPATH=src python3 -m unittest discover -s tests -v   # runs against engine/target/release/quire-engine (make build)
```
