Skip to content

Environments

Every key has an environment: "production" (the default), "staging", "development", or "test".

from django_tenant_apikeys.models import Environment

instance, raw_key = OrganizationAPIKey.generate_key(
    name="Staging smoke tests",
    tenant=org,
    environment=Environment.TEST,
)
raw_key               # tak_test_3f9a2c1d.Xk7p...
instance.environment   # "test"

Environment is a TextChoices class — Environment.PRODUCTION, Environment.STAGING, Environment.DEVELOPMENT, Environment.TEST — or just pass the plain string ("test") directly; both work identically since TextChoices members compare equal to their string values.

What it's for

If your test suite, staging environment, and local development all talk to the same Django project (a shared staging database, say), environment lets you tell those keys apart without inventing your own field for it, and lets you write code like:

if api_key.environment != Environment.PRODUCTION:
    # e.g. skip a billing side-effect, log more verbosely, whatever your
    # application actually needs to do differently
    ...

The package itself doesn't branch on environment anywhere except when building the key's prefix (below) — it's plain data for your application to read, the same as metadata, just with a fixed, validated set of allowed values instead of arbitrary JSON.

The prefix segment is a visual cue, not the source of truth

generate_key() encodes a coarser signal into the prefix itself:

environment Prefix segment
production (default) _live_
staging _live_
development _test_
test _test_
OrganizationAPIKey.generate_key(name="k", tenant=org)
# prefix: tak_live_...  (environment defaults to "production")

OrganizationAPIKey.generate_key(name="k", tenant=org, environment=Environment.DEVELOPMENT)
# prefix: tak_test_...

This only has two buckets, not four — production/staging both get _live_, development/test both get _test_ — the same coarse live-vs-test split Stripe and GitHub use in their own key prefixes. It's enough to eyeball a key's blast radius in a log line or an error report at a glance.

It is not authoritative. Nothing in the package parses the prefix back out to decide anything — is_active, is_expired, scope checks, IP restrictions, and rate limiting are all identical regardless of which segment a key's prefix has. If your application code needs to know a key's environment, read instance.environment, never the prefix string.

Backward compatibility

Every key created before this field existed has no environment column to read. On upgrade, the field defaults to "production" — the same access level those keys have always had, just now named explicitly. No migration data backfill is required beyond Django's own AddField default handling.

Next

  • Metadata — arbitrary, unvalidated JSON, for anything environment's fixed set of choices doesn't cover.
  • Authentication — how the prefix is used during lookup (hint: only the part before the dot, and only for indexing).