Skip to main content

allowances

Grants and consumable budgets — enforced before the fact, auditable after it. Storage-agnostic, with a Django backend.

📖 Documentation: docs.velis.si/allowances. The source lives in docs/ — npm install && npm run docs:dev serves it locally.


What it does

Applications keep needing to answer the same shape of question:

Can this user send an email right now? How many API calls does this organisation have left this month? Does this device still have a service contract, and does the customer have enough coolant credit to fill it?

allowances answers it, records what the answer cost, and lets you take it back if the operation it paid for never happened.

It is not a payment system — no invoices, no card charges, no money anywhere. The units it counts are whatever you say they are. Use it for rights that are bought, expire, or get spent.

Install

Django is optional. The core is import allowances and depends on nothing at all — not Django, not a database driver, not anything outside the standard library. Everything that matters (the ordering walk, conversion, rounding, overdraft, sliding windows) lives there, and talks to storage through a sixteen-method port.

pip install allowances            # the engine, zero dependencies
pip install "allowances[django]"  # ...and the Django models, store and admin

The Django binding is one extra line, and the rest of this README assumes you took it:

INSTALLED_APPS = ["allowances_django"]

Without it you supply your own Store — a dict-backed one ships in the box for tests, and writing another is an afternoon.

Use

# settings.py
ALLOWANCES = {
    "currencies": {
        "email_send": {},
        "credits": {"general": True, "round_fn": "ceil"},
    },
    "features": {
        "send_email": {
            "consumable": {"currencies": ["email_send", ("credits", 0.5)]},
            "subjects": {"user", "team"},
            "fulfillment_order": [("consumable", "user"), ("consumable", "team")],
        },
    },
}
from allowances import InsufficientBalanceError
from allowances_django.shortcuts import fulfill, attach, unfulfill

try:
    result = fulfill("send_email", 1, {"user": request.user, "team": team})
except InsufficientBalanceError as exc:
    return http_402(f"Only {exc.result.granted} left")

message = send_the_email()
attach(result.guid, message)   # record what the quota bought
# ...or unfulfill(result.guid) if it failed

The shape of it

Two layers, kept apart. Definition — what rights exist at all: features, currencies, conversion rates, priority order. Configuration, not rows. State — what a subject holds and has spent: wallets and a transaction ledger.

The engine sits between them and knows nothing about either's storage. It receives definitions through a provider and state through a sixteen-method store port, which is why the same code runs against Postgres, SQLite or a dict.

   allowances.engine        peek · fulfill · attach · unfulfill · plan_switch
           │
   ┌───────┴────────┐
   ▼                ▼
Store port   DefinitionProvider
   │                │
   ▼                ▼
DjangoStore    SettingsProvider
MemoryStore    DictProvider

Highlights:

  • Grants and consumables in one flow. A boolean entitlement and a metered budget resolve together, in an order you declare.
  • Multi-source payment. One operation can draw from a dedicated budget, then general credits, then a promotional grant, at conversion rates you set.
  • Nothing edited in place. Consumption writes a ledger entry; reversal writes a matching storno; spent-out wallets move to an archive.
  • Sliding windows without a reset job. "200 a day" works by ageing consumption out of a window, not by a nightly job that can fail.
  • Multi-subject. A licence can belong to a user, an organisation, a device, a location — resolved across all of them in one call.

What it could also be

A permissions layer, if you push it. Not the point of the library, but the pieces line up: a grant with no expiry is a permission bit, the fulfillment order is a resolution order across user, team and organisation, and the ledger is the audit trail permission systems usually bolt on afterwards. You get things they rarely offer natively — rights that expire on a date, that may be exercised n times, or that are inherited from an organisation and revoked with it.

Only the plumbing is missing, and NEXT_STEPS.md already scopes it: a grant-only permissions backend, after which the decorators, mixins and checks you have already written keep working unchanged. See the guide.

Development

pip install -e ".[test]"
pytest                                 # 188 tests, both stores, no setup
ruff check . && ruff format --check .

npm install
npm run docs:dev

No database server required: the suite defaults to a file-backed SQLite. Copy tests/env.example.py to tests/env.py to point it at a local Postgres instead, and ALLOWANCES_TEST_DB=sqlite pytest to go back. Both engines are supported and both are covered — including tests/django/test_concurrency.py, which races eight threads for the same wallet and insists it is granted exactly once.

Locally that runs against whichever Django you have installed. CI runs the suite five times — Django 5.2 on Python 3.11, 6.0 on 3.12 and 6.1 on 3.13 against Postgres, plus each end of that range against SQLite — which is what the Django>=5.2,<7 pin is based on.

DECISIONS.md records every judgment call and what it would cost to revisit — read it before changing behaviour.

Licence

MIT.

Metadata

Release files for allowances 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for allowances 0.2.1
File Size Uploaded Provenance Actions
allowances-0.2.1.tar.gz 43.6 kB Without provenance Details for allowances-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for allowances 0.2.1
File Interpreter ABI Platform Actions
allowances-0.2.1-py3-none-any.whl Python 3 none any Details for allowances-0.2.1-py3-none-any.whl

Total release size: 84.2 kB

Release files / allowances-0.2.1.tar.gz

Download URL allowances-0.2.1.tar.gz
Size 43.6 kB
Tags Source
SHA-256 checksum
How to use checksums
bb2dc08a88530788340c46d3b48cca087db3d1ce996810f63a0f9f77dc2310c2
BLAKE2b-256 checksum
How to use checksums
ff2456f89db6c025b022aa6a31f9bd16bb2fbdbc0fb81c505377292fc1406ddd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release files / allowances-0.2.1-py3-none-any.whl

Download URL allowances-0.2.1-py3-none-any.whl
Size 40.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c71a3a44fea931df98f4bb7b2c2a88ccc9ec78c4348e13d1b1b765567ca92b35
BLAKE2b-256 checksum
How to use checksums
09ba4d6ecd96b51e9d9d49b085b5f13ac4f021b1cb0dc5414102f0631752f56e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Security for allowances 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page