FastAPI (and Flask)
This guide covers FastAPI (ASGI) and Flask (WSGI): both use the same avsb package with different middleware shims. FastAPI users get full async support via AsyncAvsbServer. Flask users use the synchronous AvsbServer instead. By the end your app will start the SDK once and expose a per-request flag helper: request.state.avsb_user on FastAPI, g.avsb_user on Flask.
FastAPI (ASGI)
Install
Add the avsb package with the async extra. It pulls in httpx, the only optional dependency AsyncAvsbServer needs.
Obtain your SDK key
Open your A vs B project, select Environments in the sidebar, and copy the SDK key. Store it in an environment variable so each deployment can point at its own environment.
Build the server and add ASGI middleware
Build AsyncAvsbServer once, at module load, and start it inside FastAPI's lifespan handler. Add the ASGI middleware so every request gets request.state.avsb_user.
Read a flag in a route
Read flags via the pre-scoped request.state.avsb_user helper inside your route handlers.
Track an event
Record a conversion or custom metric with track.
Identify a user
To re-scope to a different user context mid-request (for example after resolving a JWT to a real user record), call server.for_user.
Install the package:
pip install "avsb[async]"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_...- Environments lives in the sidebar on its own, not inside Settings.
- Click Reveal to see the full key, then Copy to copy it.
Build the server at module load, start it in the lifespan handler, and add the ASGI middleware:
import osfrom contextlib import asynccontextmanagerfrom typing import AsyncIteratorfrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponsefrom avsb import SingleContextfrom avsb.async_client import AsyncAvsbServerfrom avsb.middleware.fastapi import AvsbFastApiMiddlewareserver = AsyncAvsbServer(sdk_key=os.environ['AVSB_SDK_KEY'])@asynccontextmanagerasync def lifespan(app: FastAPI) -> AsyncIterator[None]: await server.on_ready() yield await server.close()app = FastAPI(lifespan=lifespan)app.add_middleware( AvsbFastApiMiddleware, server=server, context_from=lambda scope: SingleContext.user( key=dict(scope['headers']).get(b'x-user-id', b'anon').decode(), plan=dict(scope['headers']).get(b'x-user-plan', b'free').decode(), ),)The middleware takes the server object itself, not a factory function, so it has to exist before add_middleware runs. Building it at module load and only awaiting on_ready() inside lifespan solves that: construction is instant, and the network fetch happens once FastAPI actually starts.
Read a flag in a route:
@app.get('/checkout')async def checkout(request: Request): avsb = request.state.avsb_user # bound to this request's context show_new = avsb.get_bool_flag('checkout_v2', False) theme = avsb.get_string_flag('ui_theme', 'default') return JSONResponse({'showNewCheckout': show_new, 'theme': theme})request.state.avsb_user exists because context_from above always returns a context (it defaults to 'anon' rather than returning None). Evaluating flags is synchronous even on the async client, so there's no await here.
Track an event:
request.state.avsb_user.track('purchase', value=149.99)request.state.avsb_user is a bound client, and every one of its methods, including track, is synchronous. The raw server at request.state.avsb is a different object: on AsyncAvsbServer, track, on_ready, refresh_datafile, flush and close are all coroutines. Call request.state.avsb.track(...) without await and Python neither sends the event nor raises: it silently creates a coroutine that never runs, and logs "coroutine was never awaited" at exit. Prefer the bound avsb_user client for reads and writes; if you do reach for the raw server mid-request, await it.
Re-scope to a different user mid-request:
scoped = server.for_user(SingleContext.user( key=str(user.id), plan=user.plan, org_id=str(user.org_id),))show_new = scoped.get_bool_flag('checkout_v2', False)Flask (WSGI)
Flask runs on the WSGI protocol. The pattern is the same, but with the synchronous AvsbServer and Flask's own extension. The scoped helper lives on Flask's application-context global, g.avsb_user.
pip install "avsb[flask]"import osfrom flask import Flask, g, jsonify, request as flask_requestfrom avsb import AvsbServer, SingleContextfrom avsb.middleware.flask import AvsbFlaskapp = Flask(__name__)server = AvsbServer(sdk_key=os.environ['AVSB_SDK_KEY'])server.on_ready()AvsbFlask( app, server=server, context_from=lambda: SingleContext.user( key=flask_request.headers.get('x-user-id', 'anon'), plan=flask_request.headers.get('x-user-plan', 'free'), ),)@app.route('/checkout')def checkout(): show_new = g.avsb_user.get_bool_flag('checkout_v2', False) return jsonify({'showNewCheckout': show_new})AvsbFlask is a Flask extension, not ASGI middleware: it hooks Flask's before_request and puts the client on flask.g rather than wrapping the WSGI app. Build AvsbServer yourself first (its lifecycle is yours, AvsbFlask never calls on_ready or close on it), then pass it in as server=. g.avsb is the raw server, always present; g.avsb_user is the bound one, present only when context_from returns a context.
Graceful shutdown
For FastAPI the lifespan handler already calls await server.close() on shutdown. For Flask, register an atexit hook against the same server object you built:
import atexitatexit.register(server.close)Testing
avsb.test_utils builds a datafile in memory: no dashboard, no network call. Passing datafile= makes a client ready the moment it's built, on either the sync or the async server.
import pytestfrom avsb import SingleContextfrom avsb.async_client import AsyncAvsbServerfrom 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=True)] ) return AsyncAvsbServer( TEST_SDK_KEY, datafile=datafile, polling_enabled=False, events_enabled=False, )def test_checkout_flag_is_on(mock_server): show_new = mock_server.get_bool_flag( 'checkout_v2', False, SingleContext.user(key='u_power') ) assert show_new is TrueNo await and no running app needed here: a bootstrap datafile= makes the client ready synchronously, and flag evaluation is always synchronous, so the fixture never touches the network.
What's next
- Django integration: synchronous Django with WSGI middleware.
- Python SDK reference: full
AsyncAvsbServerandAvsbServerAPI. - Multi-context targeting: combine user and organization contexts in one evaluation call.