Metadata-Version: 2.5
Name: brussle
Version: 1.3.0
Summary: Python SDK for the Brussle API
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.27
Requires-Dist: typing-extensions>=4.7
Description-Content-Type: text/markdown

# brussle (Python)

Brussle is a database where a column, a judgment, can be an AI model's answer about a document. The answer can read other documents. For example, *Which open invoice does this payment settle?* reads the payment beside its customer's open invoices. When an invoice changes, Brussle judges the payments that read it again. Each answer says whether it is current, and records the judgment version and engine version behind it. You can measure each answer against the outcomes that you record. If an answer reads only its own document, a column in your own database may be enough.

This package is the Python client for the Brussle API.

```python
import os
from brussle import Client

db = Client(api_key=os.environ["BRUSSLE_API_KEY"])
ns = db.namespace("acme/prod/tenant_123")

# A judgment on unmatched payments that reads the open invoices in the same block.
ns.judgments.create(
    from_starter="payments.settles",
    paths={
        "kind": "attributes.kind",
        "payment_kind": "payment",
        "invoice_kind": "invoice",
        "status": "attributes.status",
        "unmatched": "unmatched",
        "open": "open",
        "block": "attributes.block",
        "payment_amount": "state.amount",
        "reference": "state.reference",
        "invoice_number": "state.number",
        "invoice_amount": "state.amount",
    },
    engine={"name": "jev", "version": "current"},
    freshness={"policy": "on_change"},
    confirm=True,
)

# An open invoice, and a payment in the same block.
ns.write(upsert=[
    {"id": "inv_1002", "attributes": {"kind": "invoice", "status": "open", "block": "northwind-gbp"}, "state": {"number": "INV-1002", "amount": 1200}},
    {"id": "pay_1", "attributes": {"kind": "payment", "status": "unmatched", "block": "northwind-gbp"}, "state": {"amount": 1200, "reference": "Northwind INV-1002"}},
])

# A change to the block's open invoices judges its payments again. Query the payments that wait for a person,
# with a current answer only.
page = ns.query(
    filters=("answers.settles.thresholds.unmatched", "Eq", True),
    answers="fresh_only",
    include={"answers": ["settles"]},
)
```

The [quickstart](https://docs.brussle.com/quickstart) runs this example step by step. [Relations](https://docs.brussle.com/concepts/relations) lists what else a judgment can read.

To load many documents, `import_documents` takes any iterable and writes it in batches, 4 at a time on a thread pool, with retries:

```python
summary = ns.import_documents(records(), concurrency=4)
print(summary.documents, summary.failures)
```

How the client works:

- Methods mirror the API's routes.
- Every route that changes something takes `idempotency_key=`. A call that would not be safe to repeat without one, such as a create, gets a key the SDK generates when you pass none, the same on each retry of that call.
- Calls are retried after network errors, timeouts, `502`, `503` and `504`, and after a `429` or `503` with `Retry-After`, 2 times by default (`max_retries`). A `500` is retried only for reads, writes and the other calls that are safe to repeat without a key.
- `ns.judgments.create`, `update` and `backfill` can take more than 20 s. The API then answers with a job, and the call waits for it: it checks the job every 1 to 10 s, or after the API's `Retry-After`, for up to `wait_timeout` seconds (10 minutes by default). It then returns or raises what the call would have. After the timeout it raises `JobWaitTimeoutError`, whose `job["id"]` you can pass to `db.jobs.wait`. With `wait=False` the call returns `{"accepted": False, "result": ...}` or `{"accepted": True, "job": ...}`.
- Requests and responses are plain JSON, typed in `brussle.types`. One exception: `ns.outcomes.append` takes the list of outcomes, not the whole request body.
- Filters and `rank_by` are tuples.
- Errors raise `ApiError` with the API's `code` and `details`. The `code` is `unknown` for an error response that is not the API's JSON for example a proxy's error page, and for a success whose body is not JSON.
- A call that your organization's plan does not include raises `plan_required` (402). `details["required_plan"]` names the plan that includes it.

See [the SDK reference](https://docs.brussle.com/sdks).
