Skip to main content

Convert Experiences FullStack SDK for Python — server-side A/B testing, feature flags, and personalizations.

Project description

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

Project details


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-1.0.1.tar.gz (95.7 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-1.0.1-py3-none-any.whl (117.4 kB view details)

Uploaded Python 3

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

Hashes for convert_python_sdk-1.0.1.tar.gz
Algorithm Hash digest
SHA256 7ddcfce19c915e56a5f045c2b7625497e9f2d5af1802ce3b7256a8a5d9495927
MD5 ecfa3de541d1067965b5f2a5e4c1b36b
BLAKE2b-256 9658e76387c73454495e53c6ea224e79091c462f4606952283d44a461b85ca19

See more details on using hashes here.

Provenance

The following attestation bundles were made for convert_python_sdk-1.0.1.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-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for convert_python_sdk-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8665c531ff86ec719c1ace28723070c40f0257f2fdb4080d33613514f0919aee
MD5 7043f5afeb44e39275eec1d00a52f0f7
BLAKE2b-256 8a5a1b7bb804ce597f2e78576cee1a39b2d1a015eb3c5e2ef370a7735b431a3f

See more details on using hashes here.

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

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 Pingdom Monitoring Sentry Error logging StatusPage Status page