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.
Install
Add the avsb package with the django extra, which also checks that Django itself is installed.
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.
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.
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.
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.
Track an event
Record a conversion or custom metric with track.
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:
pip install "avsb[django]"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:
AVSB_SDK_KEY=sdk_production_...Build the client and register the middleware in settings.py:
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]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:
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})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:
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_fromNow request.avsb_user is a scoped client: already bound to the current visitor, so a view reads flags directly:
class CheckoutView(View): def get(self, request): show_new = request.avsb_user.get_bool_flag('checkout_v2', False) return JsonResponse({'showNewCheckout': show_new})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:
request.avsb_user.track('purchase', value=149.99)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:
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)for_user binds a context once. Every flag read after that reuses it, so you don't pass the identity again.
- Environments lives in the sidebar on its own, not inside Settings.
- 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:
import atexitfrom django.conf import settingsatexit.register(settings.AVSB_SERVER.close)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.
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 Falsepolling_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
- FastAPI integration: async Python with ASGI middleware; Flask usage is covered in the same guide.
- Python SDK reference: full
AvsbServerandAsyncAvsbServerAPI. - Multi-context targeting: combine user and organization attributes in one evaluation.