pledge

Reference

Everything lives in the pledge schema. Use the functions and views below. The tables behind them are internal.

Functions #

Function Does
create_account Creates an account and returns it
close_account Closes an account with a zero balance and returns it
create_transfer Books one transfer and returns it
create_transfers Books many transfers, all or nothing, and returns them
request Builds one request for create_transfers
new_transfer_id Makes a new transfer id, for safe retries
verify Checks the whole ledger
id_timestamp When an id was made (any of the three id types)
pledge.create_account(
    name text,
    currency text,
    allow_negative_balance boolean DEFAULT true,
    allow_positive_balance boolean DEFAULT true,
    metadata jsonb DEFAULT NULL
) RETURNS SETOF pledge.accounts_view

pledge.close_account(id pledge.account_id) RETURNS SETOF pledge.accounts_view

pledge.create_transfer(
    from_account_id pledge.account_id,
    to_account_id pledge.account_id,
    amount numeric,
    event_at timestamptz DEFAULT NULL,
    metadata jsonb DEFAULT NULL,
    id pledge.transfer_id DEFAULT NULL
) RETURNS SETOF pledge.transfers_view

pledge.create_transfers(
    requests pledge.transfer_request[],
    event_at timestamptz DEFAULT NULL,   -- for requests without their own
    metadata jsonb DEFAULT NULL          -- likewise
) RETURNS SETOF pledge.transfers_view

pledge.create_transfers(VARIADIC requests pledge.transfer_request[])
    RETURNS SETOF pledge.transfers_view

pledge.request(
    from_account_id pledge.account_id,
    to_account_id pledge.account_id,
    amount numeric,
    id pledge.transfer_id DEFAULT NULL,
    event_at timestamptz DEFAULT NULL,
    metadata jsonb DEFAULT NULL
) RETURNS pledge.transfer_request

pledge.new_transfer_id() RETURNS pledge.transfer_id

pledge.verify() RETURNS TABLE (check_name text, ok boolean, detail text)

pledge.id_timestamp(pledge.account_id | pledge.transfer_id | pledge.entry_id) RETURNS timestamptz

Views #

pledge.accounts_view: id, name, currency, balance, version, allow_negative_balance, allow_positive_balance, metadata, created_at, updated_at, closed_at.

pledge.transfers_view: id, from_account_id, to_account_id, amount, created_at, event_at, metadata.

pledge.entries_view: id, account_id, transfer_id, amount (negative on the sending side), account_previous_balance, account_current_balance, account_version, plus created_at, event_at and metadata from the transfer.

Types #

Type Text form Example
pledge.account_id pla_… pla_01M72Y7TF2EWV94RPD57WD8XJJ
pledge.transfer_id plt_… plt_01M72Y7TF5FRZAAHZ6ZFHEXJVP
pledge.entry_id ple_… ple_01M72Y7TF5FRZAAHZ6ZFHEXJVQ

Ids are UUIDv7 inside, so they sort by creation time. Cast to and from uuid with ::uuid and ::pledge.account_id. Ids of different kinds can’t be compared by mistake: account_id = transfer_id is an error.

pledge.transfer_request is the row type that pledge.request(...) builds.

Errors #

All errors are SQLSTATE P0001. DETAIL is a JSON object with the code and the keys listed under Attributes. In Python these are attributes of the exception, with account_id as an AccountId, transfer_id as a TransferId and balance as a Decimal. See Errors for how to handle them.

Code Python class Attributes Message
pledge.invalid_request InvalidRequest Amount (<amount>) must be a positive finite number, Cannot transfer to the same account (id=<id>), Account ids (from=<id>, to=<id>) must not be null, Transfer requests must not be null
pledge.account_not_found AccountNotFound account_id Account (id=<id>) does not exist
pledge.account_closed AccountClosed account_id Account (id=<id>, name=<name>) is closed
pledge.account_already_closed AccountAlreadyClosed account_id Account (id=<id>) is already closed
pledge.account_not_empty AccountNotEmpty account_id, balance Account (id=<id>, name=<name>) has a non-zero balance (<balance>)
pledge.negative_balance_not_allowed NegativeBalanceNotAllowed account_id Account (id=<id>, name=<name>) does not allow negative balance
pledge.positive_balance_not_allowed PositiveBalanceNotAllowed account_id Account (id=<id>, name=<name>) does not allow positive balance
pledge.currency_mismatch CurrencyMismatch from_currency, to_currency Cannot transfer between different currencies (<from> and <to>)
pledge.transfer_conflict TransferConflict transfer_id Transfer (id=<id>) already exists with different values
pledge.duplicate_transfer_id DuplicateTransferId transfer_id Transfer id (<id>) appears more than once in the request
pledge.immutable Immutable table pledge.<table>: rows cannot be removed (…), pledge.<table>: <columns> cannot be changed

Python #

From pledge:

Name What
AccountId, TransferId, EntryId Typed ids. TransferId.new() makes a new one
LedgerError and the classes above Ledger errors, with .code, .message, .detail (the parsed JSON) and the attributes above
translate(exc) Turns a psycopg or Django database error into a LedgerError, or None
VerificationFailed Raised by assert_verified()

From django_pledge.models:

Call Runs
Account.objects.create(...) create_account
account.transfer(to, amount, id=, event_at=, metadata=) create_transfer
Transfer.objects.create(...) create_transfer
Transfer.objects.bulk_create([...], event_at=, metadata=) create_transfers
account.close() close_account
Account.objects.verify(), assert_verified() verify

Reading: account.history(), account.transfers(), account.balance_at(when), account.is_closed, account.incoming, account.outgoing, account.entries. Async versions work too: acreate, abulk_create, asave.

From django_pledge: AccountIdField, TransferIdField, EntryIdField, NumericField. From django_pledge.json: LedgerJSONEncoder. From django_pledge.admin: ReadOnlyAdmin.