Metadata-Version: 2.4
Name: bursar
Version: 2.0.1
Summary: Usage metering, credits, subscriptions, and payments for AI applications
License-Expression: AGPL-3.0-only
Project-URL: Homepage, https://zonastery.github.io/bursar/
Project-URL: Source, https://github.com/Zonastery/bursar
Project-URL: Documentation, https://zonastery.github.io/bursar/
Project-URL: Issues, https://github.com/Zonastery/bursar/issues
Keywords: credits,billing,llm,usage-metering,pricing
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: <3.14,>=3.12
Description-Content-Type: text/markdown
Requires-Dist: pydantic<3,>=2.13.4
Requires-Dist: python-dotenv<2,>=1.2.2
Requires-Dist: pyyaml<7,>=6.0.3
Requires-Dist: tenacity<10,>=9.1.4
Provides-Extra: providers
Requires-Dist: stripe>=15.4.0; extra == "providers"
Requires-Dist: dodopayments[webhooks]>=1.111.0; extra == "providers"
Provides-Extra: postgres
Requires-Dist: psycopg2-binary>=2.9.12; extra == "postgres"
Provides-Extra: s3
Requires-Dist: boto3<2,>=1.43.66; extra == "s3"
Provides-Extra: google-adk
Requires-Dist: google-adk<2.7.0,>=2.6.3; extra == "google-adk"
Provides-Extra: test
Requires-Dist: pytest>=9.1.1; extra == "test"
Requires-Dist: pytest-cov>=7.1.0; extra == "test"
Requires-Dist: pytest-timeout>=2.4.0; extra == "test"
Requires-Dist: pytest-repeat>=0.9.4; extra == "test"
Requires-Dist: pytest-asyncio>=1.4.0; extra == "test"
Requires-Dist: ruff>=0.16.1; extra == "test"
Requires-Dist: pyright>=1.1.411; extra == "test"
Requires-Dist: testcontainers[postgres]>=4.15.0; extra == "test"
Requires-Dist: hypothesis>=6.165.2; extra == "test"
Requires-Dist: httpx>=0.28.1; extra == "test"
Requires-Dist: psycopg2-binary>=2.9.12; extra == "test"
Requires-Dist: pyyaml>=6.0.3; extra == "test"
Requires-Dist: stripe>=15.4.0; extra == "test"
Requires-Dist: dodopayments[webhooks]>=1.111.0; extra == "test"
Requires-Dist: boto3<2,>=1.43.66; extra == "test"
Requires-Dist: google-adk<2.7.0,>=2.6.3; extra == "test"

# Bursar for Python

[![PyPI](https://img.shields.io/pypi/v/bursar.svg)](https://pypi.org/project/bursar/)
[![PyPI downloads](https://img.shields.io/pypi/dm/bursar.svg)](https://pypi.org/project/bursar/)

<p align="center">
  <img
    src="https://raw.githubusercontent.com/zonastery/bursar/main/docs/static/img/logo.png"
    alt="Bursar logo"
    width="192"
    height="192"
  />
</p>

The Python SDK for [Bursar](https://github.com/Zonastery/bursar). It meters
usage, prices operations, and manages balances against the shared canonical
PostgreSQL schema and the same versioned configuration document as the
JavaScript SDK. Python 3.12 and 3.13 are supported.

## Installation

```bash
pip install bursar[postgres]
```

Extras: `postgres` (default recommended), `providers` (Stripe, Dodo),
`s3` (optional billing archive), `google-adk` (model-call admission and
settlement plugin), and `test` (dev/test tooling).

Apply the SQL baseline before starting an application:

```bash
export DATABASE_URL=postgresql://...
bursar migrate
```

`bursar migrate` applies the ordered SQL files, records checksums, and is safe
to re-run. Repeat `--post-migrate-sql` to run idempotent host-owned SQL in the
same transaction.

## Usage

```python
from bursar import Bursar, PostgresStore

store = PostgresStore(database_url, tenant_id=tenant_id)
bursar = Bursar.create(credit_store=store)

grant = bursar.credits.add_credits(user_id, 500, entry_type="purchase", idempotency_key="checkout:42")
charge = bursar.credits.deduct_credits(user_id, 20, idempotency_key="job:42")
refund = bursar.credits.refund_credits(charge.entry_id)

page = bursar.credits.list_ledger_entries(user_id, limit=25)
while page.next_cursor is not None:
    page = bursar.credits.list_ledger_entries(user_id, limit=25, cursor=page.next_cursor)
```

`LedgerEntry`, `LedgerCursor`, and `LedgerPage` are exported from `bursar`;
pagination is cursor-only. `PostgresStore` is the production, tenant-scoped
store; `CreditStore` is the abstract base for custom implementations.

Publish one versioned configuration document through the facade — billing and
auto-recharge read the same active document:

```python
bursar.catalog.publish_and_activate(config)
```

## Optional S3 and ClickHouse storage

PostgreSQL remains authoritative. S3 and ClickHouse are optional delivery
targets, managed by `create_bursar_runtime` from `bursar.storage`:

```python
from bursar.storage import BursarRuntimeOptions, create_bursar_runtime

runtime = create_bursar_runtime(
    BursarRuntimeOptions(
        postgres=os.environ["DATABASE_URL"],
        tenant_id=os.environ["BURSAR_TENANT_ID"],
    )
)
runtime.start()
bursar = runtime.bursar
```

With no S3/ClickHouse configuration the runtime creates no background worker
and analytics query PostgreSQL directly. See the
[storage guide](https://zonastery.github.io/bursar/docs/guides/storage-backends)
for the full S3 and ClickHouse setup.

## Development

```bash
cd python
uv sync --group dev        # runtime + dev/test deps
uv run pytest              # full suite; integration tests need Postgres
ruff check src/ tests/
pyright src/
```

Real-Postgres tests resolve `DATABASE_URL`, else spin up a disposable
PostgreSQL 17 + pg_partman 5 + pg_jsonschema 0.3 testcontainer. See
[CONTRIBUTING.md](https://github.com/Zonastery/bursar/blob/main/CONTRIBUTING.md).

## License

AGPL-3.0.
