Metadata-Version: 2.4
Name: django-api-usage
Version: 0.1.2
Summary: Lightweight, privacy-first usage metering for Django APIs, with an optional deprecation lifecycle layer.
Author-email: CodeSyntax <teknika@codesyntax.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/codesyntax/django-api-usage
Project-URL: Source, https://github.com/codesyntax/django-api-usage
Project-URL: Issues, https://github.com/codesyntax/django-api-usage/issues
Keywords: django,api,usage,metrics,deprecation,drf
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=4.2
Provides-Extra: drf
Requires-Dist: djangorestframework>=3.14; extra == "drf"
Provides-Extra: celery
Requires-Dist: celery>=5.2; extra == "celery"
Provides-Extra: redis
Requires-Dist: redis>=4.0; extra == "redis"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-django; extra == "dev"
Requires-Dist: djangorestframework>=3.14; extra == "dev"
Requires-Dist: black==26.5.1; extra == "dev"
Requires-Dist: ruff==0.16.10; extra == "dev"
Dynamic: license-file

![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-api-usage)
![Django versions](https://img.shields.io/badge/django-4.2%20%7C%205.2-0C4B33)
![Status](https://img.shields.io/badge/status-alpha-orange)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/codesyntax/django-api-usage/ci.yml)
![PyPI - Version](https://img.shields.io/pypi/v/django-api-usage)

# django-api-usage

Lightweight, privacy-first **usage metering for Django APIs**, with an optional
**deprecation lifecycle** layer on top.

It answers two questions that decide whether an endpoint can be changed or removed:

1. **How much is this endpoint (or this whole Django app) used?**
2. **Who is calling it, so they can be contacted before it changes?**

The package is deliberately generic: the core is plain Django middleware (works
with or without Django REST Framework), it never breaks a request because
metering failed, and it stores no raw personal data by default.

## Why not just `drf-api-tracking`?

`drf-api-tracking` stores one database row per request (including bodies), which
is heavy and privacy-sensitive. `django-api-usage` accumulates **counters** and
keeps consumer attribution **hashed and opt-in**. That makes it suitable both for
continuous usage analytics and for the concrete decision of deprecating an
endpoint.

## Install

```bash
pip install django-api-usage
```

```python
INSTALLED_APPS = [
    # ...
    "django_api_usage",
]

MIDDLEWARE = [
    # ...
    "django_api_usage.middleware.ApiUsageMiddleware",
]
```

Metering starts immediately. Counters are buffered in the cache and written to
the database in batches:

```bash
python manage.py api_usage_flush      # from cron, or a Celery beat task
python manage.py api_usage_report --days 30
python manage.py api_usage_report --days 90 --sunset-candidates
```

With Celery (and the `celery` extra) two tasks are provided:
`django_api_usage.tasks.flush_api_usage` and
`django_api_usage.tasks.prune_api_usage`.

## Configuration

```python
API_USAGE = {
    "ENABLED": True,
    "FAIL_OPEN": True,
    "BUFFER_BACKEND": "cache",       # "cache" (batched) or "db" (write per request)
    "RETENTION_DAYS": 90,
    "TRACK_CONSUMERS": False,        # opt-in; stores hashed callers only
    "CONSUMER_SALT": "a-rotatable-secret",
}
```

All resolvers are overridable, so no project-specific knowledge leaks into the
package:

```python
API_USAGE = {
    "APP_LABEL_RESOLVER": "myproject.api.resolvers.app_label",
    "SITE_RESOLVER": "myproject.api.resolvers.site_id",
    "ROLE_SCOPES_RESOLVER": "myproject.api.resolvers.role_scopes",
}
```

## Deprecation lifecycle

`Endpoint` carries `deprecated`, `sunset_date`, `replacement` and `owner`, so the
same data that measures usage also drives a deprecation plan. See the consuming
project's migration guide for the full workflow (measure → notify → enforce).

## DRF shadow mode

`django_api_usage.drf.HasAPIScope` enforces scopes declared on a view:

```python
class AllUserViewSet(viewsets.ReadOnlyModelViewSet):
    permission_classes = [HasAPIScope]
    required_scopes = ["user:read_all"]
```

With `API_ENFORCEMENT = "report"` (the default) nothing is blocked yet: a
`X-Api-Would-Deny` header is returned instead, so you can deploy a permission and
measure what it *would* have denied before enforcing it.

## Status

**Alpha.** The core is implemented: metering middleware, models and migrations,
management commands, the deprecation layer and a DRF shadow-mode permission.
Tested on Python 3.10-3.12 and Django 4.2/5.2. MIT licensed.

## Development

```bash
python -m django test tests --settings=tests.settings
ruff check .
black --check .
```
