Convert Experiences FullStack SDK for Python — server-side A/B testing, feature flags, and personalizations.
Project description
Convert Python SDK
The Convert Experiences FullStack SDK for Python — server-side A/B testing, feature flags, and personalizations for Python 3.9+ applications (Django, Flask, FastAPI, and plain Python services).
The SDK is framework-agnostic and sync-first: you can reach your first experiment value in plain Python, with no web framework and — using direct config — no network call.
Installation
pip install convert-python-sdk
- Distribution name (PyPI):
convert-python-sdk - Import package:
convert_sdk
The two differ by design — the hyphenated name is the discoverability surface on PyPI, the snake_case name is the ergonomic import path.
Compatibility
- Python 3.9+
- No required web framework
- No JavaScript runtime dependency
- One runtime dependency (
httpx), used only forsdk_keyinitialization
Quickstart
The fastest path to a first successful run uses direct config — a preloaded config payload, no network call:
from convert_sdk import Core, SDKConfig
config_data = {
"account_id": "100123",
"project": {"id": "200456"},
"experiences": [
{
"id": "e1",
"key": "checkout-experiment",
"variations": [
{"id": "v1", "key": "control", "traffic_allocation": 50.0},
{"id": "v2", "key": "treatment", "traffic_allocation": 50.0},
],
}
],
}
# Initialize from direct config — ready immediately, no network.
core = Core(SDKConfig(data=config_data)).initialize()
# Create a visitor-scoped context.
context = core.create_context("visitor-001")
# Evaluate an experience.
result = context.run_experience("checkout-experiment")
if result is not None:
print("Bucketed into:", result.variation_key)
core.close()
Initialization
Core is the entry point. Construct it with an SDKConfig, then call
initialize(). Provide exactly one of data (direct config) or sdk_key
(remote config).
Direct config (offline, no network)
from convert_sdk import Core, SDKConfig
core = Core(SDKConfig(data=config_data)).initialize()
assert core.is_ready
Direct-config initialization makes no network call and is ideal for local development, tests, and environments that load config out of band.
sdk_key (fetch config over HTTPS)
import os
from convert_sdk import Core, SDKConfig
# Read the key from the environment — never hard-code credentials.
core = Core(SDKConfig(sdk_key=os.environ["CONVERT_SDK_KEY"])).initialize()
sdk_key initialization fetches config over HTTPS through the built-in
transport. Inject the key from an environment variable or your secret store;
do not embed real keys in source.
Core is also a context manager, so it releases transport resources cleanly:
with Core(SDKConfig(data=config_data)).initialize() as core:
context = core.create_context("visitor-001")
...
Creating a visitor context
create_context binds a visitor identity (and optional visitor attributes) to
the current immutable config snapshot:
context = core.create_context(
"visitor-001",
visitor_attributes={"country": "US", "plan": "pro"},
)
Visitor attributes are used for audience qualification. They are copied
defensively — later mutations to the dict you pass never affect the context.
Keep and reuse the returned context to evaluate the same visitor repeatedly;
the SDK does not cache contexts for you.
Experience evaluation
run_experience evaluates a single experience for the visitor. It returns a
typed ExperienceResult when the visitor qualifies and buckets into a
variation, or None for any normal miss (missing experience, unqualified
visitor, no active variation). It never raises for normal outcomes and performs
no network I/O.
result = context.run_experience("checkout-experiment")
if result is not None:
print(result.experience_key, result.variation_key, result.variation_id)
# Evaluate all applicable experiences at once:
for result in context.run_experiences():
print(result.experience_key, "->", result.variation_key)
You can overlay request-time attributes for a single call without mutating the stored context:
result = context.run_experience(
"checkout-experiment",
attributes={"country": "DE"},
)
Feature evaluation
run_feature resolves a feature flag and its typed variables for the visitor.
It reads the feature change from the visitor's selected variation and casts each
variable using the feature's declared types. It returns a typed FeatureResult
when the feature is enabled for the visitor, or None for a normal miss
(undeclared, unavailable, or disabled feature). It never raises for normal
outcomes and performs no network I/O.
feature = context.run_feature("checkout-banner")
if feature is not None:
print(feature.status.value) # "enabled"
print(feature.variables["enabled"]) # typed per the feature definition (bool)
print(feature.variables["headline"]) # str
# Resolve all applicable features:
for feature in context.run_features():
print(feature.feature_key, feature.variables)
Conversion tracking
track_conversion records a goal conversion for the visitor. It is lightweight
and synchronous — it deduplicates by (visitor_id, goal_id) and appends to an
in-process batch queue. No network call happens on track_conversion;
queued events are delivered when the queue is released via core.flush(),
batch-size release (SDKConfig.batch_size, default 10), an opt-in periodic
timer, or a best-effort atexit hook.
result = context.track_conversion("purchase_completed", revenue=49.99)
print(result.tracked, result.reason) # True None
# A default duplicate for the same (visitor, goal) is suppressed:
again = context.track_conversion("purchase_completed")
print(again.tracked, again.reason) # False "deduplicated"
# force_multiple re-tracks (e.g. repeated revenue/transactions):
context.track_conversion("purchase_completed", revenue=10.0, force_multiple=True)
# Deliver queued events explicitly (the canonical control point):
core.flush()
Runtime Integration
Choosing when to flush depends on your runtime (Lambda, Cloud Run, gunicorn,
uvicorn, Celery, CLI). The default lifecycle is explicit-flush-only, which is
safe everywhere. See the project wiki
for per-runtime decision tables and copy-pasteable flush snippets, including the
opt-in daemonic periodic timer (SDKConfig.auto_flush_interval_ms), the
best-effort atexit hook, and the documented SIGTERM pattern.
Runnable examples
Self-contained, framework-agnostic examples live in examples/ and
run locally with no external services:
python examples/direct_config.py # direct-config initialization
python examples/basic_experience.py # bucket a visitor into a variation
python examples/basic_feature.py # resolve a feature and read typed variables
They share a small sample config (examples/_sample_config.py) and read any
sdk_key from the CONVERT_SDK_KEY environment variable rather than embedding
credentials.
Documentation
The advanced guides live on the project wiki:
- Topic guides: Initialization, Evaluation, Tracking, Queue control, Debugging, Extending, Support workflows, Runtime integration
- Migration guides: Migrating from raw REST, Migrating from the JavaScript SDK
Public API
Importable from convert_sdk:
Core,ContextSDKConfig,TransportConfigExperienceResult,FeatureResult,FeatureStatusConversionResult,ConversionStatus- error types:
ConvertSDKError,ConfigError,InvalidConfigError,ConfigLoadError,TransportError,TrackingDeliveryError __version__
Development
This project uses uv and the hatchling build
backend.
# Install dev tooling (pytest, ruff, mypy, coverage)
uv sync --group dev
# Run the test suite
uv run pytest
# Lint and type-check (the CI gates)
uv run ruff check src tests scripts
uv run mypy --strict
# Build wheel and sdist
uv build
Every change runs through CI (.github/workflows/ci.yml): Ruff lint, mypy
--strict, a 15-cell test matrix (Python 3.9–3.13 × {ubuntu, macos, windows})
with an 85% project / 95% evaluation/ coverage floor, a release-blocking parity
suite, and a dependency-bounds check.
Reproduce all the release gates locally in one command:
python scripts/verify_release.py
Releasing
The SDK publishes to PyPI as convert-python-sdk via a workflow_run-
triggered GitHub Actions workflow using OIDC Trusted Publishing — there are
no long-lived PyPI tokens in repository secrets.
Releases are fully automatic: merge a Conventional-Commit PR to main and the
pipeline handles the rest. See RELEASE.md for the full
maintainer workflow, one-time setup, dry-run instructions, and troubleshooting.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file convert_python_sdk-1.0.1.tar.gz.
File metadata
- Download URL: convert_python_sdk-1.0.1.tar.gz
- Upload date:
- Size: 95.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ddcfce19c915e56a5f045c2b7625497e9f2d5af1802ce3b7256a8a5d9495927
|
|
| MD5 |
ecfa3de541d1067965b5f2a5e4c1b36b
|
|
| BLAKE2b-256 |
9658e76387c73454495e53c6ea224e79091c462f4606952283d44a461b85ca19
|
Provenance
The following attestation bundles were made for convert_python_sdk-1.0.1.tar.gz:
Publisher:
release.yml on convertcom/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
convert_python_sdk-1.0.1.tar.gz -
Subject digest:
7ddcfce19c915e56a5f045c2b7625497e9f2d5af1802ce3b7256a8a5d9495927 - Sigstore transparency entry: 1871770280
- Sigstore integration time:
-
Permalink:
convertcom/python-sdk@ab273ec095e900390218e2cdeb260abcfa7b740d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/convertcom
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ab273ec095e900390218e2cdeb260abcfa7b740d -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file convert_python_sdk-1.0.1-py3-none-any.whl.
File metadata
- Download URL: convert_python_sdk-1.0.1-py3-none-any.whl
- Upload date:
- Size: 117.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8665c531ff86ec719c1ace28723070c40f0257f2fdb4080d33613514f0919aee
|
|
| MD5 |
7043f5afeb44e39275eec1d00a52f0f7
|
|
| BLAKE2b-256 |
8a5a1b7bb804ce597f2e78576cee1a39b2d1a015eb3c5e2ef370a7735b431a3f
|
Provenance
The following attestation bundles were made for convert_python_sdk-1.0.1-py3-none-any.whl:
Publisher:
release.yml on convertcom/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
convert_python_sdk-1.0.1-py3-none-any.whl -
Subject digest:
8665c531ff86ec719c1ace28723070c40f0257f2fdb4080d33613514f0919aee - Sigstore transparency entry: 1871770348
- Sigstore integration time:
-
Permalink:
convertcom/python-sdk@ab273ec095e900390218e2cdeb260abcfa7b740d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/convertcom
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ab273ec095e900390218e2cdeb260abcfa7b740d -
Trigger Event:
workflow_run
-
Statement type: