> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trevosdk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Server-side assignment and tracking for Django, Flask, FastAPI, Celery, and scripts.

`trevosdk` runs experiments and records conversions from your Python backend. Zero
dependencies, Python 3.10+, and assignment is byte-identical to every other Trevo SDK — the
same user gets the same variant in the browser and on the backend.

```bash theme={null}
pip install trevosdk
```

## Create a client

You need a **server** key: `tsk_secret_…`, from Settings → SDK keys with platform `Server`.
It is shown once. Never put it in client code.

```python theme={null}
from trevosdk import TrevoClient

trevo = TrevoClient("tsk_secret_...")
```

Create the client once at module scope — one per process, reused across requests. Config
refresh (60-second poll) and event delivery run on background daemon threads; events flush
on interpreter exit, or deterministically via `trevo.flush()` / `trevo.close()`.

Assignment needs config, so wait for the first fetch once at startup:

```python theme={null}
trevo.ready(timeout_s=2)   # True once config loaded; False on timeout
```

Without this, requests in the first moments after a deploy fall back to control.

## Read a variant

```python theme={null}
variant = trevo.get_variant("checkout-cta", user_id="user-42")
if variant == "treatment":
    ...  # alternate experience
```

`get_variant` reads an immutable in-memory config snapshot — no network call, nothing that
blocks — so it is safe anywhere: Django and Flask views, FastAPI handlers, Celery tasks,
cron scripts.

Each call records an **exposure** (deduplicated per identity and variant within a 10-minute
window). To read without recording one, pass `track_exposure=False`.

## Identity is passed per call

A server holds no ambient user state, so every call takes the identity explicitly:

```python theme={null}
variant = trevo.get_variant(
    "pricing-algo-v2",
    user_id=request.user.id if request.user.is_authenticated else None,
    anonymous_id=request.COOKIES.get("trevo_id"),
)
```

**Send both ids whenever you have them.** `user_id` wins for bucketing when present; an
event carrying both is what links a user's anonymous browsing to their conversions. With no
identity at all, `get_variant` returns `"control"` and records nothing.

**Bucket with the identity the browser is using at that moment** — the user id when
identified, otherwise the `trevo_id` cookie. Anything else assigns the same person different
variants on the client and the server.

## Track conversions

```python theme={null}
trevo.track("subscription_started", user_id="user-42", properties={"plan": "pro"})
```

Payment providers replay webhooks, and a conversion counted twice inflates whichever variant
that user was in. Pass a stable `insert_id` derived from the thing that happened, and Trevo
records the event once no matter how many deliveries arrive:

```python theme={null}
trevo.track(
    "subscription_started",
    user_id="user-42",
    properties={"plan": "pro"},
    insert_id=subscription.id,
)
```

## Linking identities without a cookie

Webhooks and cron jobs have no cookie to read. State the link directly:

```python theme={null}
trevo.alias(anonymous_id, user_id)
```

Idempotent and safe to retry. An `anonymous_id` belongs to one user — pointing it at a
different user raises and changes nothing.

## Framework notes

* **Django / Flask** — instantiate once at module scope (a settings or extensions file) and
  call from views directly.

* **FastAPI / async** — the sync client is safe in async handlers (nothing blocks on the
  read path). For an awaitable lifecycle, wrap it:

  ```python theme={null}
  from trevosdk import AsyncTrevoClient, TrevoClient

  trevo = AsyncTrevoClient(TrevoClient("tsk_secret_..."))
  # in lifespan shutdown: await trevo.aclose()
  ```

* **Pre-fork servers (`gunicorn --preload`, uWSGI, Celery prefork)** — a client built before
  the fork keeps working in the child. Its worker threads do not survive a fork, so the SDK
  re-initialises them and its locks via `os.register_at_fork`; the first `get_variant` or
  `track` in the child restarts delivery. No `post_fork` hook is needed.

* **Serverless and short-lived scripts** — background timers do not get a chance to fire, so
  call `trevo.flush()` before returning. The client is also a context manager: `with
  TrevoClient(...) as trevo:` flushes and closes on exit.

## Local development

Pin variants without touching Trevo:

```bash theme={null}
TREVO_FORCE_VARIANTS=checkout-cta:treatment,pricing-v2:control
```

Or in code with `force_variants={"checkout-cta": "treatment"}`. Forced reads never record an
exposure, so local testing never pollutes results. The SDK's own test suite deletes the
variable, so exporting it cannot turn a conformance run red.

## Errors

Everything raised or passed to `on_error` is typed, so you can branch on what happened:
`TrevoConfigError`, `TrevoDeliveryError` (carries `.status`), `TrevoSerializationError`,
`TrevoQueueFullError` and `TrevoEventRejectedError`, all under `TrevoError`.

## Options

| Option | Default | Notes |
| - | - | - |
| `secret_key` | — | Required. `tsk_secret_…` (positional) |
| `bootstrap_config` | — | A list of experiment configs to assign against before the first fetch resolves |
| `poll_interval_s` | `60` | Config refresh cadence |
| `flush_interval_s` | `5` | Background flush cadence; serverless should call `flush()` instead |
| `max_batch_size` | `50` | Capped at 500 by the server |
| `force_variants` | — | Pin variants locally; also read from `TREVO_FORCE_VARIANTS` |
| `transport` | stdlib `urllib` | Override for tests or proxies |
| `on_error` | logs at ERROR on the `trevosdk` logger | Background failures surface here |

## API summary

| Method | Purpose |
| - | - |
| `ready(timeout_s=None)` | Block until initial config loads; returns `bool` |
| `get_variant(key, *, user_id=None, anonymous_id=None, track_exposure=True)` | Deterministic assignment |
| `track(name, *, user_id=None, anonymous_id=None, properties=None, insert_id=None)` | Queue a conversion |
| `alias(anonymous_id, user_id)` | Link an anonymous journey to a user |
| `flush()` | Send everything queued now |
| `close()` | Stop background threads and flush once |

`AsyncTrevoClient` mirrors the same surface with `await`-able `ready()`, `alias()`,
`flush()`, and `aclose()`, plus `async with` support.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.