Skip to main content

PyScoped

Tenant scoping and transactional audit history for the Django application you already have.

Keep your models, tables, primary keys, authentication, and business logic. Add PyScoped one model at a time. Free and MIT licensed, with no account, API key, paid tier, telemetry, or hosted-service dependency.

2.0.0 is the stable Django release. It is a breaking redesign of 1.x. The new import is pyscoped; the old scoped framework is preserved in legacy/v1/ and excluded from the package.

Add it to an existing model

Install the stable release:

python -m pip install pyscoped==2.0.0

Add "pyscoped" to INSTALLED_APPS, then run python manage.py migrate. Only PyScoped's history tables are added; your model needs no extra columns.

from django.db import models
from pyscoped import scoped
from pyscoped.query import ScopedManager

@scoped(scope_field="organization", fields=["amount", "status"])
class Invoice(models.Model):
    organization = models.ForeignKey("Organization", on_delete=models.PROTECT)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(max_length=32, default="draft")

    objects = ScopedManager()

Use your existing authenticated user and organization after checking membership:

from pyscoped import scope

with scope(actor=request.user, scope=organization.pk):
    invoices = list(Invoice.objects.filter(status="draft"))
    Invoice.objects.filter(pk=invoice_id).update(status="approved")

Reads through the scoped manager are filtered before aggregation or pagination. Supported writes and audit records commit or roll back together. Missing context or an instance write to another scope raises. An explicit field allowlist controls what is recorded; it is your responsibility to exclude sensitive fields.

scope() is an application-level boundary: your application must authenticate the actor and authorize scope membership. It does not grant a user permission merely because a tenant ID was supplied by a client.

Adopt gradually

  • Begin with mode="audit" to retain existing read behavior while adding attributed write history. Writes still require actor context and the supported manager.
  • Use @scoped(...) on a current model, or register(Invoice, ...) in AppConfig.ready().
  • If convenient, inherit pyscoped.models.ScopedModel; it adds a manager, no columns. The registration decorator is still required.
  • Compose existing queryset methods with ScopedQuerySet. Supply ScopedManager for every public manager on a registered model.
  • Baseline existing data, then enable enforcement for that model when its workflows and relationship integrity have been checked.
# Preview first; actor and scope are explicit operator assertions.
python manage.py pyscoped_backfill billing.Invoice --actor migration-operator --scope acme
python manage.py pyscoped_backfill billing.Invoice --actor migration-operator --scope acme --apply

The baseline preserves existing IDs and records the state observed now. It does not invent historical events. Applying is idempotent and resumable per row.

For request context, add pyscoped.middleware.ScopedMiddleware after your existing session/authentication middleware and configure PYSCOPED_CONTEXT_RESOLVER with a trusted callable returning ScopeContext or None. For existing service functions, use @in_scope(resolver) from pyscoped.integration.

History and restoration

from pyscoped.history import history, verify_history, restore

with scope(actor=request.user, scope=organization.pk):
    events = history(Invoice, invoice_id)
    verified = verify_history(Invoice, invoice_id)
    invoice = restore(Invoice, invoice_id, revision=1, expected_revision=3)

Restoration updates selected scalar fields on a live row and records a new event. It checks the expected head revision and detects divergence from recorded state. It does not resurrect deleted rows, restore external effects, or change ownership, primary keys, or relationships. History is hash-linked per resource; rewriting the entire chain and head with database-administrator privileges is outside that guarantee.

Supported scope

Django 5.2 and 6.0, on their supported Python versions. SQLite for local development; PostgreSQL for concurrent transactional use. There are no non-Django adapters in 2.0.

Operation Initial 2.0 behavior
Model save, partial save, expression save Attributed and audited atomically
Scoped query reads, counts, aggregates, pagination Scope predicate enforced at the root queryset
Queryset update Native SQL semantics, row locks and per-row audit snapshots in one transaction
Instance/queryset/cascade delete Django deletion semantics; retained audit events
Async ORM operations Same contract via Django's async methods
Ordinary bulk_create, bulk_update Native Django signal semantics, transactional audit snapshots
Conflict-handling bulk insert, duplicate bulk-update IDs, raw fixture save Explicitly rejected
select_related, joins into enforced models Scope predicates on joined tables; outer-join semantics preserved
FK / one-to-one reads between enforced models, prefetch Scoped target queries and context checks
Implicit many-to-many, multi-table inheritance, proxies Outside initial support; configuration fails
Raw SQL, RawSQL, private ORM APIs, historical migration models Outside enforcement; never use for ordinary registered operations

This is an ORM integration, not database row-level security or a sandbox against untrusted Python code. Unregistered models, external database writers, direct private/base-manager access, and already-returned data do not acquire automatic access controls. Preserve valid same-scope relationships and use scoped querysets for each protected read boundary.

See the full guarantees and limitations, the adoption guide, and the executable existing-app example.

Develop and contribute

python -m pip install -e '.[dev,postgres]'
python -m pytest -q
python -m django check --settings=tests.settings
python -m django makemigrations --check --dry-run --settings=tests.settings
ruff check pyscoped tests examples
python -m build

See CONTRIBUTING.md for PostgreSQL tests, the phased development plan for scope and progress, and the release record for validation evidence.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyscoped-2.0.0.tar.gz (44.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyscoped-2.0.0-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

File details

Details for the file pyscoped-2.0.0.tar.gz.

File metadata

  • Download URL: pyscoped-2.0.0.tar.gz
  • Upload date:
  • Size: 44.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pyscoped-2.0.0.tar.gz
Algorithm Hash digest
SHA256 24261012843dfd7f24aea46468fbef471397db35f9e9d04bfc098ca7438e8d57
MD5 35fc95e09bd8b1a352c8ae3600952caa
BLAKE2b-256 9eca3a73cc9ad184e3bc9c1df5d861ddaef0034fc30381e09898bf793cdb5a30

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyscoped-2.0.0.tar.gz:

Publisher: publish.yml on kwip-info/pyscoped

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyscoped-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: pyscoped-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 23.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pyscoped-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e657c04347daafa444e92f29e8f90bb81e7df3cc6be08f9d2e6b17ad27da90d2
MD5 b3ce3316937d13a4ffe57a8ead814bae
BLAKE2b-256 9e413b970f533a354d4fb0dc85904d90406d93e444e5964e7a2d59f1377fbb22

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyscoped-2.0.0-py3-none-any.whl:

Publisher: publish.yml on kwip-info/pyscoped

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 files

1.8.1

2 files

1.8.0

2 files

1.7.0

2 files

1.6.0

2 files

1.5.0

2 files

1.4.1

2 files

1.4.0

2 files

1.3.0

2 files

1.2.1

2 files

1.2.0

2 files

1.1.0

2 files

1.0.5

2 files

1.0.4

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

0.9.6

2 files

0.9.5

2 files

0.9.4

2 files

0.9.3

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

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