Skip to main content

CI PyPI version

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 for sdk_key initialization

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, Context
  • SDKConfig, TransportConfig
  • ExperienceResult, FeatureResult, FeatureStatus
  • ConversionResult, 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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

convert_python_sdk-2.0.0.tar.gz (110.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

convert_python_sdk-2.0.0-py3-none-any.whl (133.1 kB view details)

Uploaded Python 3

File details

Details for the file convert_python_sdk-2.0.0.tar.gz.

File metadata

  • Download URL: convert_python_sdk-2.0.0.tar.gz
  • Upload date:
  • Size: 110.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for convert_python_sdk-2.0.0.tar.gz
Algorithm Hash digest
SHA256 fbce2397b1f81b64eb1556fc527b8ef234f061e56086e9b1600b439141666c1a
MD5 90297947a97b4c5e254b7f0eb25aba13
BLAKE2b-256 e7d9b8700aeec08fce2c5e0b4ce2d85e10c02c6477018e3e48b65d4a9ead4a4e

See more details on using hashes here.

Provenance

The following attestation bundles were made for convert_python_sdk-2.0.0.tar.gz:

Publisher: release.yml on convertcom/python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file convert_python_sdk-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for convert_python_sdk-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 94b5aac37d96308b8bbae25955fe67f7c8a568843be344a90a4dbbd3174f08d2
MD5 57d4eb83d2ac577d37707bbee20d3e33
BLAKE2b-256 9c0b3933c136b2f9848dc8e60b3aaafd429570f8f9f90b8e9954ed1c65625823

See more details on using hashes here.

Provenance

The following attestation bundles were made for convert_python_sdk-2.0.0-py3-none-any.whl:

Publisher: release.yml on convertcom/python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page