Metadata-Version: 2.4
Name: bursar
Version: 1.0.1
Summary: Declarative credit calculation engine for AI SaaS platforms
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 :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
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>=2.0.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: tenacity>=9.0.0
Provides-Extra: providers
Requires-Dist: stripe>=10.0.0; extra == "providers"
Requires-Dist: dodopayments>=1.0.0; extra == "providers"
Provides-Extra: postgres
Requires-Dist: psycopg2-binary>=2.9.12; extra == "postgres"
Provides-Extra: s3
Requires-Dist: boto3<2,>=1.35; extra == "s3"
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: pytest-cov>=5.0; extra == "test"
Requires-Dist: pytest-timeout>=2.3; extra == "test"
Requires-Dist: pytest-repeat>=0.9; extra == "test"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
Requires-Dist: ruff>=0.15.0; extra == "test"
Requires-Dist: pyright>=1.1.390; extra == "test"
Requires-Dist: testcontainers[postgres]>=4.8; extra == "test"
Requires-Dist: hypothesis>=6.112; 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>=10.0.0; extra == "test"
Requires-Dist: boto3<2,>=1.35; 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), 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
Postgres 16 + pg_partman 5 testcontainer. See
[CONTRIBUTING.md](https://github.com/Zonastery/bursar/blob/main/CONTRIBUTING.md).

## License

AGPL-3.0.
