Metadata-Version: 2.5
Name: typefold-client
Version: 0.2.1
Summary: Python client for the Typefold API: render published reports and templates to PDF or XLSX.
Project-URL: Repository, https://github.com/berkerdemirer/typefold
Keywords: pdf,rendering,reporting,xlsx
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Provides-Extra: test
Requires-Dist: mypy>=1.10; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Requires-Dist: ruff>=0.6; extra == 'test'
Description-Content-Type: text/markdown

# typefold-client

Python client for the Typefold API. Needs Python 3.10 or later; its only dependency is
`httpx`. It has a blocking `TypefoldClient` and an asyncio `AsyncTypefoldClient` with the
same methods.

```sh
pip install typefold-client
```

Create one client per API key and reuse it. The key needs `render:create`, `render:read` and
`template:read`, and `render:cancel` to cancel renders.

```python
from typefold import TypefoldClient

client = TypefoldClient("https://pdf.example.com", api_key)
```

Close it with `client.close()` when your application stops, or use it as a context manager.

## Render a report

`render_report` renders a published report or saved template by name or ID and returns the PDF
or XLSX bytes:

```python
from typefold import (
    Orientation,
    PageSize,
    PdfMetadata,
    RenderReportRequest,
    ReportFormat,
    ReportPdfOptions,
    TemplateRef,
)

pdf = client.render_report(
    RenderReportRequest(
        template=TemplateRef.by_name("SoldGoodsForWarehouseReportPdf"),
        data={"Data": {"reportRows": report_rows, "summaryRows": summary_rows}},
        pdf=ReportPdfOptions(
            page_size=PageSize.A4,
            orientation=Orientation.LANDSCAPE,
            metadata=PdfMetadata(title="Sold goods, week 39"),
        ),
    )
)

xlsx = client.render_report(
    RenderReportRequest(
        format=ReportFormat.XLSX,
        template=TemplateRef.by_name("SoldGoodsForWarehouseReportXlsx"),
        data=data,
    )
)
```

`data` is sent as written: dictionary keys keep their names, and dates, decimals, UUIDs and
dataclasses are written as JSON.

Templates imported from jsreport keep their shortids, so stored shortids still work:
`TemplateRef.by_shortid("rkJTnK2ce")`. `list_reports` returns each template's `shortid`.

A retry with the same `idempotency_key` returns the render the key already created.
`get_report_data_schema` returns the JSON Schema of a report's data; `list_reports` lists
templates and the formats each renders.

## Render notification text

`render_string` renders a template string synchronously, without a PDF, for email bodies,
subjects, SMS and push messages. The Handlebars engine runs `#if`, `#each` and your helpers:

```python
from typefold import RenderStringRequest, StringEngine, StringFormat

body = client.render_string(
    RenderStringRequest(
        template="{{#if (isEqual Status 'paid')}}Paid{{else}}Due{{/if}}: {{Booking.Code}}",
        data=model,
        engine=StringEngine.HANDLEBARS,
        helpers="function isEqual(a, b) { return a === b; }",
        format=StringFormat.HTML,
    )
)
```

`StringFormat.HTML` escapes values; `StringFormat.TEXT` (the default) writes them as they are.
Without `engine`, only `{{path}}` placeholders are replaced.

## Render HTML or a template version

`render` creates a render, waits for it and downloads the PDF:

```python
from typefold import DirectRenderRequest, HtmlDocument, PageSize, RenderSettings, TemplateVersionRenderRequest

pdf = client.render(
    DirectRenderRequest(
        document=HtmlDocument(html="<h1>Invoice 42</h1>"),
        settings=RenderSettings(page_size=PageSize.A4),
    )
)

ticket = client.render(
    TemplateVersionRenderRequest(template_version_id=version_id, data={"ticket": {"number": "TK-1"}})
)
```

It gives up after two minutes unless you pass a `timeout` in seconds. To render in the
background, use `create_render`, then `wait_for_render`, `download` or `cancel_render`, or the
API's render webhooks.

## Render a batch

`create_batch` creates up to 100 renders in one request; if any item is invalid, none is created.
Items are `DirectRenderRequest` or `TemplateVersionRenderRequest`; their `wait_ms` is not sent.
`wait_for_batch` polls until no item is queued or processing (10 minutes unless you pass a
`timeout`), and `download_batch` returns one PDF of every item in order:

```python
batch = client.create_batch(
    [TemplateVersionRenderRequest(template_version_id=version_id, data=ticket) for ticket in tickets],
    idempotency_key="tickets-2026-10-12",
)
batch = client.wait_for_batch(batch)

if batch.counts.succeeded == batch.total:
    pdf = client.download_batch(batch.id)
```

A failed item does not stop the others; `batch.items` gives each render's `id` and `state`, to
read, download or cancel it as a single render. `get_batch` reads a batch again.

## Async

`AsyncTypefoldClient` has the same methods as coroutines:

```python
from typefold import AsyncTypefoldClient

async with AsyncTypefoldClient("https://pdf.example.com", api_key) as client:
    pdf = await client.render_report(request)
```

## Errors

A request the API refuses with `429 RATE_LIMITED`, such as a burst of parallel renders, is sent
again after its `Retry-After` up to `max_rate_limit_retries` times (3 by default; pass 0 to turn
this off). Other failures raise `TypefoldError`. `error.code` is the API's code, such as
`NOT_FOUND`, `RATE_LIMITED` or `RENDER_TIMEOUT`, and `error.retryable` says whether a retry can
help. When a render was created but failed or timed out, `error.error.render_id` names it.

## Versions

Use a client no newer than your server: older clients keep working because `/v1` only grows,
but a newer client's fields or endpoints answer `400` or `404` on an older server.

## httpx client

To share an `httpx.Client` (or `httpx.AsyncClient`), pass it as `http_client` with a timeout
above 95 seconds, the longest a report PDF can take. The API key goes on each request, not the
client, and never to download URLs. A client you pass in is not closed with the
`TypefoldClient`.

## Development

```sh
cd clients/python
python -m venv .venv && .venv/bin/pip install -e '.[test]'
.venv/bin/pytest && .venv/bin/mypy src tests && .venv/bin/ruff check . && .venv/bin/ruff format --check .
```
