Bursar for Python
Python 3.12 and 3.13 are supported.
pip install bursar[postgres]
DATABASE_URL=postgresql://... bursar migrate
To install trusted host-owned database objects after Bursar, pass one or more repeatable integration files:
DATABASE_URL=postgresql://... \
bursar migrate --post-migrate-sql ./host-integration.sql
The files run in order, after Bursar's pending migrations and in the same transaction. They run on every invocation, so they must be idempotent.
Create the reusable facade after migrations have run:
from bursar import Bursar, PostgresStore
store = PostgresStore(database_url, tenant_id=tenant_id)
bursar = Bursar.create(credit_store=store)
bursar.credits owns account operations:
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. Usage history is available through
list_usage_entries; one entry is available through get_ledger_entry.
PostgresStore is the only store, and both it and the CreditStore base
class are imported directly from the top-level package:
from bursar import PostgresStore— the production, tenant-scoped storefrom bursar import CreditStore— the abstract base for custom implementations
Use bursar.catalog.publish_and_activate(config) for a canonical document with
pricing, credits, entitlements, admission, plans, commerce, and
catalog. The optional billing service and
auto-recharge policy read that same active document.
Stores and Bursar do not install database objects. Deployment is:
DATABASE_URL -> bursar migrate -> publish canonical config -> start app
Optional S3 and ClickHouse storage
create_bursar_runtime is the Python composition root when Bursar should
manage optional storage projections. PostgreSQL remains authoritative for
balances, leases, billing state, and the transactional outbox.
With no extra infrastructure, it creates no background worker and analytics continue to query PostgreSQL:
import os
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
Install bursar[postgres,s3] and add S3 connection settings when an archive is
needed. ClickHouse remains structurally injected:
import os
import clickhouse_connect
from bursar.storage import (
BursarRuntimeOptions,
ClickHouseUsageStoreOptions,
S3BillingArchiveOptions,
S3Credentials,
create_bursar_runtime,
)
clickhouse_client = clickhouse_connect.get_client(
dsn=os.environ["CLICKHOUSE_URL"]
)
runtime = create_bursar_runtime(
BursarRuntimeOptions(
postgres=os.environ["DATABASE_URL"],
s3=S3BillingArchiveOptions(
bucket=os.environ["BURSAR_S3_BUCKET"],
region=os.environ["BURSAR_S3_REGION"],
endpoint=os.getenv("BURSAR_S3_ENDPOINT"),
force_path_style=os.getenv("BURSAR_S3_FORCE_PATH_STYLE") == "true",
credentials=S3Credentials(
access_key_id=os.environ["BURSAR_S3_ACCESS_KEY_ID"],
secret_access_key=os.environ["BURSAR_S3_SECRET_ACCESS_KEY"],
),
),
clickhouse=ClickHouseUsageStoreOptions(
client=clickhouse_client,
# Optional; omit to retain the projection indefinitely.
retention_days=730,
),
)
)
runtime.start()
# Use runtime.bursar in the application.
# On graceful shutdown:
runtime.close()
External writes happen through a leased PostgreSQL outbox, never in a customer request. S3 object keys are deterministic and the ClickHouse projection is replay-safe. ClickHouse analytics are therefore eventually consistent. PostgreSQL payload retention should be at least as long as the outbox retry horizon, which is enforced by the SQL storage configuration.
Set outbox=False only when a separate process consumes the Bursar outbox.
Database retention maintenance remains independent: schedule
bursar.maybe_run_storage_maintenance() and partition maintenance with
pg_cron as described in the SQL README.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bursar-1.0.0.tar.gz.
File metadata
- Download URL: bursar-1.0.0.tar.gz
- Upload date:
- Size: 339.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b66a08fa2733316dcf6be1494a15464c75deee6f388c46d6a53d3a04676cfad1
|
|
| MD5 |
1686e30ea0dc248dbf122a252e78e010
|
|
| BLAKE2b-256 |
8098a79ec8474af1547490299427a065febc13a27d6aaf791e76f2aca2dcad55
|
File details
Details for the file bursar-1.0.0-py3-none-any.whl.
File metadata
- Download URL: bursar-1.0.0-py3-none-any.whl
- Upload date:
- Size: 330.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd105179248ea7f0c8f088a439b867de169a97f13123a1fee4f92b5324e97464
|
|
| MD5 |
c691478cd5665e69b06b3d06a7a34105
|
|
| BLAKE2b-256 |
67a7255c0b48df4719aa36977d5e7bd6c5b0aa012ed46ab87deaf809d858d579
|