Django

This guide targets Django 4.2+ running on Python 3.11 or later. By the end, your app starts the A vs B SDK once. Every request then gets a flag helper, and the SDK shuts down cleanly when the process exits.

1

Install

Add the avsb package with the django extra, which also checks that Django itself is installed.

2

Obtain your SDK key

Open your A vs B project, select Environments in the sidebar, and copy the SDK key for the target environment. Save it as an environment variable rather than writing it into settings.py, so each deployment can point at its own A vs B environment.

3

Configure Django settings

Build the SDK client once in settings.py and register the middleware. Django loads settings at startup, so this runs once per process, not once per request.

4

Read a flag in a view

The middleware puts the client on every request as request.avsb. Build a context for the current visitor and pass it to a flag read.

5

Configure context_from (optional)

Set AVSB_CONTEXT_FROM to a function that builds a context from the request. Once it is set, request.avsb_user is a client already bound to that context, so a view never rebuilds it.

6

Track an event

Record a conversion or custom metric with track.

7

Identify a user mid-request

If a user logs in partway through the request, build a new bound client for their real identity instead of the anonymous one.

Install the package:

Shell
pip install "avsb[django]"
Shell1 line
Publishing in progress

Version 1.0.1 of avsb is on its way to PyPI with this release. Until it arrives there, pip install reports that it cannot find a matching version.

Store your SDK key in .env:

Shell
AVSB_SDK_KEY=sdk_production_...
Shell1 line

Build the client and register the middleware in settings.py:

Python
import osfrom avsb import AvsbServerAVSB_SERVER = AvsbServer(sdk_key=os.environ['AVSB_SDK_KEY'])AVSB_SERVER.on_ready()MIDDLEWARE = [    'django.middleware.security.SecurityMiddleware',    'avsb.middleware.django.AvsbDjangoMiddleware',   # <-- add this    'django.contrib.sessions.middleware.SessionMiddleware',    # ... rest of your middleware]
Python12 lines
on_ready() blocks briefly, on purpose

on_ready() waits for the first datafile fetch to finish (or time out) before returning, so the first real request already has real flag values instead of defaults. It never raises: a failed fetch still lets your app start, serving defaults until the next successful poll.

The middleware reads AVSB_SERVER from your settings and attaches it to every request. Leave the middleware in MIDDLEWARE but forget to set AVSB_SERVER, and Django raises a clear error at startup instead of silently serving every flag's default forever.

Read a flag in a view:

Python
from django.http import JsonResponsefrom django.views import Viewfrom avsb import SingleContextclass CheckoutView(View):    def get(self, request):        ctx = SingleContext.user(key=str(request.user.pk))        show_new = request.avsb.get_bool_flag('checkout_v2', False, ctx)        theme    = request.avsb.get_string_flag('ui_theme', 'default', ctx)        return JsonResponse({'showNewCheckout': show_new, 'theme': theme})
Python11 lines

request.avsb is the client the middleware attached. SingleContext.user(key=...) builds a context: who you're evaluating for. Pass it to every flag read, so A vs B knows who is asking.

Configure AVSB_CONTEXT_FROM so you don't build that context by hand on every view. Add a function to settings.py:

Python
from avsb import SingleContextdef avsb_context_from(request):    if not request.user.is_authenticated:        return None    return SingleContext.user(        key=str(request.user.pk),        plan=getattr(request.user, 'plan', 'free'),    )AVSB_CONTEXT_FROM = avsb_context_from
Python13 lines

Now request.avsb_user is a scoped client: already bound to the current visitor, so a view reads flags directly:

Python
class CheckoutView(View):    def get(self, request):        show_new = request.avsb_user.get_bool_flag('checkout_v2', False)        return JsonResponse({'showNewCheckout': show_new})
Python4 lines

request.avsb_user only exists when your function returns a context. Return None for a request you want to skip, such as an anonymous visitor, and read request.avsb with an explicit context instead.

Track an event:

Python
request.avsb_user.track('purchase', value=149.99)
Python1 line
The one mistake: properties on track() go nowhere

track() accepts a properties argument for symmetry with flag reads, but a conversion event has no properties column to store them in. Pass properties={'currency': 'usd'} and the SDK logs one warning per process and drops it silently, so the value never shows up in results. Put a number in value, or add the dimension as a targeting attribute on the context so it rides the flag's exposure event instead.

Re-scope to a real identity mid-request, for example right after a login view resolves the user:

Python
from django.conf import settingsfrom avsb import SingleContextscoped = settings.AVSB_SERVER.for_user(    SingleContext.user(key=str(user.id), plan=user.plan))show_new = scoped.get_bool_flag('checkout_v2', False)
Python7 lines

for_user binds a context once. Every flag read after that reuses it, so you don't pass the identity again.

Environments is its own item in the sidebar. Click Reveal, then Copy, to get the SDK key for this environment.
  1. Environments lives in the sidebar on its own, not inside Settings.
  2. Click Reveal to see the full key, then Copy to copy it.

Graceful shutdown

Register an atexit hook next to where you built AVSB_SERVER, so it runs once when Django starts:

Python
import atexitfrom django.conf import settingsatexit.register(settings.AVSB_SERVER.close)
Python4 lines

close() stops the background threads and delivers any events still queued.

Testing

avsb.test_utils builds a datafile in memory: no dashboard, no network call. Passing datafile= to AvsbServer makes the client ready the moment it's built.

Python
import pytestfrom avsb import AvsbServer, SingleContextfrom avsb.test_utils import TEST_SDK_KEY, build_boolean_flag_entry, build_minimal_datafile@pytest.fixturedef mock_server():    datafile = build_minimal_datafile(        flags=[            build_boolean_flag_entry(                'checkout_v2',                default_value=False,                overrides=[{'userId': 'u_1', 'variationId': 'var_on'}],            )        ]    )    return AvsbServer(        TEST_SDK_KEY,        datafile=datafile,        polling_enabled=False,        events_enabled=False,    )def test_override_wins_for_u1(mock_server):    scoped = mock_server.for_user(SingleContext.user(key='u_1'))    assert scoped.get_bool_flag('checkout_v2', False) is Truedef test_default_for_everyone_else(mock_server):    scoped = mock_server.for_user(SingleContext.user(key='other'))    assert scoped.get_bool_flag('checkout_v2', False) is False
Python32 lines

polling_enabled=False and events_enabled=False keep the test off the network entirely. The overrides entry pins u_1 to the "on" variation regardless of the flag's default, the same way a dashboard override works.

What's next

Was this helpful?