Frameworks / Django
Django middleware
The Django middleware checks each request's API key, rate limit and quota before your view runs, and counts the request when the response is closed.
pip install usagerailimport os
MIDDLEWARE = [
# ...
"django.contrib.auth.middleware.AuthenticationMiddleware",
"usagerail.django.UsageRailMiddleware",
]
USAGERAIL = {
"app_key": os.environ["USAGERAIL_APP_KEY"],
"auth": "api-key",
}from django.http import JsonResponse
def generate(request):
result = run_model(request.body)
request.usagerail.track(result.tokens) # optional: count tokens
return JsonResponse(result.body)The SDK needs Python 3.10 or newer and has no dependencies of its own. The key comes from the Authorization: Bearer header, or from x-api-key when the request has no bearer token.
Settings
Everything goes in the USAGERAIL dict: app_key, auth, consumer, on_reject, on_key_store_unavailable, stale_key_snapshot, on_quota_unavailable, snapshot, flush_on_exit and logger. They work as on the FastAPI page. consumer and on_reject can be functions or dotted paths, such as "myapp.usage.consumer".
Each process has one SDK instance per app key, however many times Django loads the middleware chain.
Count units instead of requests
In apps that count units, call request.usagerail.track(n) in the view with a whole number from 0 to 10,000,000. Route weights and reservations work as described under quotas.
Use your own authentication
List UsageRail after AuthenticationMiddleware, as in the example, and return the caller's ID:
def consumer(request):
if request.user.is_authenticated:
return str(request.user.pk)
return NoneUSAGERAIL = {
"app_key": os.environ["USAGERAIL_APP_KEY"],
"auth": "custom",
"consumer": "myapp.usage.consumer",
}Returning None makes the request anonymous: it's recorded, but not rate limited, not counted against a quota and never billed.
Rejected requests
Refused requests get the statuses and JSON body listed on the Express page. To send your own response, point on_reject at a function that takes (rejection, request) and returns an HttpResponse; copy rejection.headers onto it so clients can still back off.
What gets counted
- Each request is counted under the URL pattern that matched, such as
/v1/items/<int:pk>, with its method and outcome.re_pathpatterns are recorded as written, without the leading^. - 2xx and 3xx responses are billable; 4xx and 5xx responses and refused requests aren't.
- A redirect added by
APPEND_SLASHdepends on the order of your middleware. WithCommonMiddlewarelisted before UsageRail, as in Django's default settings, UsageRail sees the original 404: it's recorded under_unmatchedand isn't billed. With UsageRail listed first, it sees the 301 and bills it as 1 unit, like any 3xx. - If an outer middleware replaces the response, the request is counted with the status UsageRail's middleware saw.
- Django can't tell when a client disconnects early, so requests are always counted by their status.
Running in production
Counts are sent every 60 seconds and once more when the server shuts down. Under gunicorn that happens on its own: gunicorn exits normally on SIGTERM, and the last counts go out on the way out.
Under uvicorn or another ASGI server, wrap the application in asgi.py:
import os
from django.core.asgi import get_asgi_application
from usagerail.django import asgi
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = asgi(get_asgi_application())Django doesn't handle the server's shutdown event, and uvicorn ends the process as soon as it has shut down, so without the wrapper every restart loses the counts that hadn't gone out yet. The wrapper answers that event: it sends the remaining counts, waiting up to 5 seconds, and then tells uvicorn it's done.
A process killed outright loses the counts it hadn't sent.