Accounting¶
FastAPIAccounting, its quotas, usage reports and records, and their sinks.
In fastapi_alcazaba.accounting.
In the guide: Accounting. In an app: the example.
fastapi_alcazaba.accounting.FastAPIAccounting
¶
FastAPIAccounting(*, subject: Subject, quotas: Iterable[Quota] = (), period: Period = 'month', sinks: Iterable[UsageSink] | None = None, cache: Cache | None = None, fail_open: bool = True, challenge: str = 'Bearer', headers: bool = True, timeout: float = 5.0, metrics: Metrics | None = None)
Meters what each subject uses, per period, and holds it to its quotas.
meter(name) is a dependency that charges each request: 1 unit by default, checked against
the quota and counted in one atomic operation, so concurrent requests never go past it. A
request over its quota gets a 429 with Retry-After (when the period ends), or a 402, and
isn't counted. meter(name, amount=None) gives the endpoint a Metering to add what it
uses as it goes (tokens, bytes, seconds), charged when it ends; the quota is checked when it
starts, so the requests already running may together go past it.
Responses carry the RateLimit and RateLimit-Policy headers of the IETF draft, the
quota's name being the meter's, for clients to see what they have left.
What's charged: a request that gets a client error (a 4xx, a validation error) was served
and stays charged; one that fails on the server (an exception, a 5xx) or whose client went
away is refunded, and has no record. What an endpoint added to its Metering is charged
whatever happens next: it was used.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
Subject
|
a dependency that returns who uses it: a user's, an API key's or a tenant's
ID, None when nobody is authenticated (a 401, |
required |
quotas
|
Iterable[Quota]
|
a |
()
|
period
|
Period
|
the period of the meters without a quota. |
'month'
|
sinks
|
Iterable[UsageSink] | None
|
where the usage records go; a LoggingUsageSink by default, |
None
|
cache
|
Cache | None
|
the Cache whose backend keeps the counters; the app's ( |
None
|
fail_open
|
bool
|
when the backend fails, let requests through uncounted (the default: the records still have them) or refuse them with a 503. |
True
|
challenge
|
str
|
the |
'Bearer'
|
headers
|
bool
|
send the |
True
|
timeout
|
float
|
seconds each sink has to write a record. |
5.0
|
metrics
|
Metrics | None
|
where to count the requests ( |
None
|
meter
¶
meter(meter: str, *, amount: int | None = 1) -> Callable[..., AsyncIterator[Any]]
A dependency that charges amount units of meter to the request's subject before
the endpoint runs, within its quota, and returns the subject's ID. With amount=None it
returns a Metering, to which the endpoint adds what it uses; the total is charged when
it ends.
record
async
¶
record(subject: str | int | UUID, meter: str, amount: int, *, request: HTTPConnection | None = None) -> UsageRecord
Charges amount units of meter to subject without checking its quota (it's been
used already): for what happens outside a metered request, such as a job or a webhook.
Counted in the backend (when it answers) and sent to the sinks; returns the record.
usage
async
¶
usage(subject: str | int | UUID, meter: str, *, at: datetime | None = None, request: HTTPConnection | None = None) -> UsageReport
What subject has used of meter in the period that holds at (now by default),
and what its quota leaves. The counters keep the current period and the one before; the
records have the older ones. Raises BackendUnavailable when the backend fails.
fastapi_alcazaba.accounting.Quota
dataclass
¶
Quota(meter: str, limit: Limit, period: Period = 'month', status: int = HTTP_429_TOO_MANY_REQUESTS)
How much of meter a subject may use per period.
Quota("requests", limit=10_000) # a month, the same for everybody
Quota("tokens", limit=tokens_of_plan, period="day") # each subject's plan says
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
meter
|
str
|
what it bounds, as |
required |
limit
|
Limit
|
the units allowed per period, 0 for none; or |
required |
period
|
Period
|
a calendar hour, day or month, in UTC: the counter starts again at 0 when it ends. |
'month'
|
status
|
int
|
what a request over the quota gets: 429 (Too Many Requests, with
|
HTTP_429_TOO_MANY_REQUESTS
|
fastapi_alcazaba.accounting.Metering
¶
Metering(meter: str, subject: str, remaining: int | None)
What meter(name, amount=None) gives the endpoint: it adds what it uses as it goes
(add(tokens)), and the total is charged when it ends.
fastapi_alcazaba.accounting.UsageReport
dataclass
¶
UsageReport(subject: str, meter: str, period: str, used: int, limit: int | None, resets: datetime)
What a subject has used of a meter in a period, and what its quota leaves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
the subject's ID. |
required |
meter
|
str
|
the meter. |
required |
period
|
str
|
the period's label: |
required |
used
|
int
|
the units counted. |
required |
limit
|
int | None
|
the quota's limit; None without a quota, or when its limit gives None. |
required |
resets
|
datetime
|
when the period ends, and the counter starts again. |
required |
fastapi_alcazaba.accounting.UsageRecord
dataclass
¶
UsageRecord(id: str, time: datetime, subject: str, meter: str, amount: int, period: str, request_id: str | None = None, trace_id: str | None = None, method: str | None = None, route: str | None = None)
A charge, as the sinks get it; to_dict() and to_json() give its JSON form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
str
|
random, unique: billing counts each record once, should a sink deliver it twice. |
required |
time
|
datetime
|
when it was charged (UTC). |
required |
subject
|
str
|
who used it: a user's, an API key's or a tenant's ID. |
required |
meter
|
str
|
what was used, such as "requests" or "tokens". |
required |
amount
|
int
|
how much, in the meter's units. |
required |
period
|
str
|
the period it was charged to, as its counter keeps it: |
required |
request_id
|
str | None
|
the request's ID ( |
None
|
trace_id
|
str | None
|
the OpenTelemetry trace it belongs to. |
None
|
method
|
str | None
|
the request's HTTP method. |
None
|
route
|
str | None
|
the template of the route that matched, e.g. "/answers/{model}". |
None
|
fastapi_alcazaba.accounting.UsageSink
¶
Bases: Protocol
Where usage records go. write gets each one when it's charged, within the
FastAPIAccounting's timeout: queue it if writing takes longer. What it raises is logged,
with the record.
fastapi_alcazaba.accounting.LoggingUsageSink
¶
LoggingUsageSink(log: str | Logger = 'fastapi_alcazaba.accounting.usage', level: int = logging.INFO)
Writes each record as one line of JSON to the logger log (a name or a Logger), at
level, for the log pipeline to keep and add up; handlers that format their own JSON find
the record in the log record's usage attribute too. When the logger outputs nothing at that
level (no handler, or a higher level), it says so once, as a warning.
fastapi_alcazaba.accounting.MemoryUsageSink
¶
MemoryUsageSink(maxlen: int | None = 1000)
Keeps the last maxlen records in records: for tests, and to look at them while
developing.
fastapi_alcazaba.accounting.period_of
¶
period_of(period: Period, at: datetime) -> tuple[str, datetime, datetime]
The period that holds at: its label (2026-11, 2026-11-01, 2026-11-01T10), when it
starts and when it ends, in UTC.