Metadata-Version: 2.5
Name: brussle
Version: 1.2.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 answers questions about your records that depend on other records. For example, *Will this account cancel?* depends on the account's tickets and invoices, not on the account alone. A judgment is a typed question that Brussle answers for each document. A judgment on accounts can read each account together with its tickets and invoices. When a new ticket arrives, Brussle judges its account again and keeps the answer current. You do not write code to find the account, judge it again or control the cost.

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 accounts that reads each account's tickets and invoices.
ns.judgments.create(
    from_starter="churn_risk.at_risk",
    paths={
        "kind": "attributes.kind",
        "account_kind": "account",
        "ticket_kind": "ticket",
        "invoice_kind": "invoice",
        "account_ref": "attributes.account_id",
        "name": "state.name",
        "ticket_subject": "state.subject",
        "ticket_status": "state.status",
        "invoice_status": "state.status",
    },
    engine={"name": "jev", "version": "current"},
    freshness={"policy": "on_change"},
    confirm=True,
)

# An account, and a ticket that points at it with `account_id`.
ns.write(upsert=[
    {"id": "acct_1", "attributes": {"kind": "account"}, "state": {"name": "Northwind"}},
    {"id": "t_1", "attributes": {"kind": "ticket", "account_id": "acct_1"}, "state": {"subject": "Third outage this week. We are looking at other vendors.", "status": "open"}},
])

# Each new ticket or invoice judges its account again. Query the accounts at risk.
page = ns.query(
    filters=("answers.churn_risk.thresholds.at_risk", "Eq", True),
    rank_by=("answers.churn_risk.p", "desc"),
)
```

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.
- 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).
