-
Notifications
You must be signed in to change notification settings - Fork 0
TypeHints
Complete reference for every frozen dataclass, enum, Protocol, and error type
in the Python SDK's public surface. All symbols listed as top-level are
importable directly from convert_sdk. Symbols listed as submodule require
the full import path shown.
This is the Python analogue of the JavaScript SDK's ConfigTypes and the PHP
SDK's ReturnTypes — same role, Python idioms.
# ---- top-level (convert_sdk) -------------------------------------------------
from convert_sdk import (
Core, Context, __version__,
# Config
SDKConfig, TransportConfig, RefreshConfig,
# Evaluation results
ExperienceResult,
FeatureResult, FeatureStatus,
ConversionResult, ConversionStatus,
CustomSegmentsResult,
# Diagnostic surface
DiagnosticReason,
ExperienceDiagnostic, FeatureDiagnostic,
GoalDiagnostic, EntityDiagnostic,
# Lifecycle events
LifecycleEvent,
# Persistence boundary
DataStore, InMemoryDataStore,
# Error hierarchy
ConvertSDKError, ConfigError,
InvalidConfigError, ConfigLoadError,
TransportError, TrackingDeliveryError,
)
# ---- submodule paths (stable, not re-exported at top level) ------------------
from convert_sdk.events import ConversionEventPayload, QueueReleasedPayload
from convert_sdk.errors import TrackingError, ConversionDataError
from convert_sdk.ports.transport import Transport
from convert_sdk.ports.event_bus import EventBus, EventHandler
from convert_sdk.ports.storage import DataStore, visitor_state_key
from convert_sdk.domain.context_state import ContextState
from convert_sdk.domain.config_snapshot import ConfigSnapshotAll result types are frozen dataclasses (@dataclass(frozen=True)). Fields
cannot be reassigned after construction. Mapping fields (variation,
variables) are wrapped in types.MappingProxyType so callers cannot mutate
snapshot-owned config through the result.
Returned by Context.run_experience() and as elements of
Context.run_experiences().
| Field | Type | Notes |
|---|---|---|
experience_key |
str |
Human-readable key from the dashboard |
experience_id |
str |
Internal config id |
variation_id |
str |
Internal variation id |
variation_key |
str | None |
Human-readable variation key; None if the variation config has no key |
variation |
Mapping[str, Any] |
Read-only view of the selected variation's raw config |
bucket_value is not a field on ExperienceResult. It is available only
on the details mapping of an ExperienceDiagnostic returned by
Context.diagnose_experience().
from convert_sdk import ExperienceResult
from typing import Optional, Mapping, Any
result: Optional[ExperienceResult] = context.run_experience("checkout-experiment")
if result is not None:
print(result.experience_key) # e.g. "checkout-experiment"
print(result.variation_id) # e.g. "10045"
print(result.variation_key) # e.g. "variation-1" (or None)
title = result.variation.get("page_title") # read-only mappingReturned by Context.run_feature() and as elements of
Context.run_features().
| Field | Type | Notes |
|---|---|---|
feature_key |
str |
Human-readable feature key |
feature_id |
str |
Internal config id |
status |
FeatureStatus |
Always ENABLED when a result is returned |
variables |
Mapping[str, Any] |
Type-cast feature variables, read-only |
experience_key |
str | None |
Backing experience key, or None
|
variation_key |
str | None |
Backing variation key, or None
|
from convert_sdk import FeatureResult, FeatureStatus
from typing import Optional
result: Optional[FeatureResult] = context.run_feature("checkout-banner")
if result is not None:
assert result.status is FeatureStatus.ENABLED
headline = result.variables.get("headline", "")str enum — subclasses str so FeatureStatus.ENABLED == "enabled".
from convert_sdk import FeatureStatus
FeatureStatus.ENABLED # "enabled"
FeatureStatus.DISABLED # "disabled"Normal misses (visitor not bucketed) are represented as None from
run_feature() rather than a DISABLED result, consistent with the
no-result convention for experiences. DISABLED is available for callers
that explicitly model a declared-but-unbucketed feature.
Returned by Context.track_conversion(). Always returned — never raised — for
both success and the unknown-goal miss. Diagnose the outcome via .status:
| Field | Type | Notes |
|---|---|---|
status |
ConversionStatus |
QUEUED, DEDUPLICATED, or GOAL_NOT_FOUND
|
goal_key |
str |
The goal key passed by the caller (always echoed back) |
goal_id |
str | None |
Resolved goal id; None on a miss |
visitor_id |
str |
The visitor the tracking call was made for |
event |
ConversionEvent | None |
In-process conversion event; None on a miss |
tracked |
bool (property) |
True only when status is QUEUED
|
reason |
str | None (property) |
None on success; "deduplicated" or "goal_not_found" otherwise |
from convert_sdk import ConversionResult, ConversionStatus
result = context.track_conversion("purchase_completed", revenue=49.99)
if result.tracked:
print("queued", result.goal_id)
elif result.status is ConversionStatus.GOAL_NOT_FOUND:
print("goal not in config:", result.reason) # "goal_not_found"
elif result.status is ConversionStatus.DEDUPLICATED:
print("already tracked:", result.reason) # "deduplicated"ConversionEvent (the type of result.event) is an internal domain type in
convert_sdk.domain.results; callers rarely need to import it directly.
str enum.
from convert_sdk import ConversionStatus
ConversionStatus.QUEUED # "queued" — event enqueued
ConversionStatus.DEDUPLICATED # "deduplicated" — duplicate for (visitor, goal)
ConversionStatus.GOAL_NOT_FOUND # "goal_not_found" — key absent from configA missing goal key is not an exception. It surfaces as
ConversionStatus.GOAL_NOT_FOUND so callers inspect result.status without
a try/except. There is no GoalNotFoundError.
Returned by Context.run_custom_segments().
| Field | Type | Notes |
|---|---|---|
matched_segment_ids |
tuple[str, ...] |
Segment IDs newly matched by this call; empty tuple = normal no-match |
matched |
bool (property) |
True when at least one segment was matched |
from convert_sdk import CustomSegmentsResult
seg = context.run_custom_segments(["high-value"], rule_data={"orders": 12})
if seg.matched:
print("matched:", seg.matched_segment_ids)Returned by Context.diagnose_*() methods. Never raise on a normal miss;
instead they describe the outcome and its reason. All four are frozen
dataclasses that share a common base with three fields:
| Field | Type | Notes |
|---|---|---|
reason |
DiagnosticReason |
Closed enum value — the machine-readable outcome code |
message |
str |
Short human-readable explanation |
details |
Mapping[str, Any] |
Read-only, redaction-safe operational fields (e.g. visitor_ref, bucket_value) |
resolved |
bool (property) |
True when reason is DiagnosticReason.RESOLVED
|
| Type | Returned by |
|---|---|
ExperienceDiagnostic |
Context.diagnose_experience(key) |
FeatureDiagnostic |
Context.diagnose_feature(key) |
GoalDiagnostic |
Context.diagnose_goal(goal_key) |
EntityDiagnostic |
Context.diagnose_entity(entity_type, key) |
from convert_sdk import (
DiagnosticReason,
ExperienceDiagnostic,
FeatureDiagnostic,
GoalDiagnostic,
EntityDiagnostic,
)
exp_diag: ExperienceDiagnostic = context.diagnose_experience("checkout-experiment")
feat_diag: FeatureDiagnostic = context.diagnose_feature("checkout-banner")
goal_diag: GoalDiagnostic = context.diagnose_goal("purchase_completed")
ent_diag: EntityDiagnostic = context.diagnose_entity("experiences", "checkout-experiment")
# All share the same field access pattern:
print(exp_diag.reason) # DiagnosticReason.RESOLVED (or a miss code)
print(exp_diag.resolved) # bool shortcut
print(dict(exp_diag.details)) # redaction-safe operational fieldsClosed str enum — exactly eight members. The set is frozen; adding or
renaming a member is a breaking change across all Convert SDKs.
from convert_sdk import DiagnosticReason
DiagnosticReason.RESOLVED # "resolved"
DiagnosticReason.AUDIENCE_MISMATCH # "audience_mismatch"
DiagnosticReason.EXPERIENCE_NOT_FOUND # "experience_not_found"
DiagnosticReason.FEATURE_NOT_IN_SELECTED_VARIATIONS # "feature_not_in_selected_variations"
DiagnosticReason.FEATURE_NOT_FOUND # "feature_not_found"
DiagnosticReason.GOAL_NOT_FOUND # "goal_not_found"
DiagnosticReason.ENTITY_NOT_FOUND # "entity_not_found"
DiagnosticReason.PROJECT_MAPPING_REQUIRED # "project_mapping_required"Because DiagnosticReason subclasses str, you can compare against either
the enum member or its string value:
assert diag.reason is DiagnosticReason.RESOLVED
assert diag.reason == "resolved" # both workfrom convert_sdk import LifecycleEvent
LifecycleEvent.READY # "ready"
LifecycleEvent.CONFIG_UPDATED # "config.updated"
LifecycleEvent.BUCKETING # "bucketing"
LifecycleEvent.CONVERSION # "conversion"
LifecycleEvent.API_QUEUE_RELEASED # "api.queue.released"
LifecycleEvent.DATA_STORE_QUEUE_RELEASED # "datastore.queue.released"
LifecycleEvent.DIAGNOSTIC # "diagnostic"LifecycleEvent is a plain enum.Enum (not a str enum); its .value
attribute is the JS-parity wire string.
There is no generic LifecycleEventPayload class. Each event carries its
own typed frozen dataclass. Handlers receive (payload, error=None) — error
is a BaseException | None.
Carried on LifecycleEvent.CONVERSION. Import path:
convert_sdk.events.ConversionEventPayload.
| Field | Type |
|---|---|
visitor_id |
str |
goal_id |
str |
goal_key |
str |
from convert_sdk import LifecycleEvent
from convert_sdk.events import ConversionEventPayload
def on_conversion(payload: ConversionEventPayload, error=None) -> None:
print(f"goal={payload.goal_key} visitor={payload.visitor_id}")
core.on(LifecycleEvent.CONVERSION, on_conversion)Carried on LifecycleEvent.API_QUEUE_RELEASED. Import path:
convert_sdk.events.QueueReleasedPayload.
| Field | Type | Notes |
|---|---|---|
reason |
ReleaseReason |
SIZE, EXPLICIT, TIMEOUT, or ATEXIT — see convert_sdk.tracking.queue.ReleaseReason
|
batch_size |
int |
Number of events in the released batch |
visitor_count |
int |
Number of distinct visitors in the batch |
event_count |
int |
Total conversion events in the batch |
status_code |
int | None |
HTTP status on delivery failure; absent on success |
retry_attempts |
int | None |
Transport retry count, or None/0 if adapter does not retry |
from convert_sdk import LifecycleEvent
from convert_sdk.events import QueueReleasedPayload
def on_released(payload: QueueReleasedPayload, error=None) -> None:
if error is not None:
print(f"delivery failed: status={payload.status_code}")
else:
print(f"released {payload.batch_size} events, reason={payload.reason}")
core.on(LifecycleEvent.API_QUEUE_RELEASED, on_released)Three @runtime_checkable typing.Protocols define the SDK's swappable I/O
seams. No base class is required — any object whose methods match structurally
satisfies the protocol and passes isinstance(obj, Protocol).
See Extending for complete injection examples.
Import path: convert_sdk.ports.transport.Transport
from convert_sdk.ports.transport import Transport
class MyTransport:
def fetch_config(self, config: "SDKConfig") -> dict[str, Any]: ...
def send_tracking(self, payload: dict[str, Any], *, sdk_key: str) -> None: ...
def close(self) -> None: ...
def __enter__(self) -> "Transport": ...
def __exit__(self, *exc: Any) -> None: ...| Method | Signature | Description |
|---|---|---|
fetch_config |
(config: SDKConfig) -> Dict[str, Any] |
Fetch raw config; raise ConfigLoadError on failure |
send_tracking |
(payload: Dict[str, Any], *, sdk_key: str) -> None |
Deliver tracking batch; raise typed ConvertSDKError on failure |
close |
() -> None |
Release held resources (e.g. HTTP client pool) |
__enter__ / __exit__
|
context-manager pair | Required for isinstance(obj, Transport) to return True
|
Injected as a keyword-only argument on Core: Core(config, transport=my_transport).
Import path: convert_sdk.ports.storage.DataStore (also exported from
convert_sdk at top level)
from convert_sdk import DataStore # top-level export
class MyStore:
def get(self, key: str) -> Any: ...
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None: ...
def has(self, key: str) -> bool: ...
def delete(self, key: str) -> None: ...| Method | Signature | Description |
|---|---|---|
get |
(key: str) -> Any |
Return stored value or None if absent/expired |
set |
(key: str, value: Any, ttl: Optional[int] = None) -> None |
Store value; ttl in seconds; None = no expiry |
has |
(key: str) -> bool |
True if key has a present, unexpired value |
delete |
(key: str) -> None |
Remove key; idempotent no-op on absent key |
Injected as a field on SDKConfig: SDKConfig(data_store=my_store).
The built-in default is InMemoryDataStore (top-level export) — thread-safe,
per-process, per-instance. See Persistent DataStore.
The helper visitor_state_key(visitor_id: str) -> str (from
convert_sdk.ports.storage) builds the namespaced DataStore key for a
visitor's persisted state.
Import path: convert_sdk.ports.event_bus.EventBus
from convert_sdk.ports.event_bus import EventBus, EventHandler
# EventHandler type alias:
# EventHandler = Callable[..., None]
# Invoked as handler(payload, error=None)
class MyEventBus:
def on(
self, event: "LifecycleEvent", handler: "EventHandler"
) -> None: ...
def emit(
self,
event: "LifecycleEvent",
payload: Any,
error: Optional[BaseException] = None,
) -> None: ...| Method | Signature | Description |
|---|---|---|
on |
(event: LifecycleEvent, handler: EventHandler) -> None |
Register handler for event |
emit |
(event: LifecycleEvent, payload: Any, error: Optional[BaseException] = None) -> None |
Invoke all handlers; raising handlers are isolated and swallowed |
The SDK owns the bus — you do not inject it. Subscribe via Core.on(event, handler).
See Event System and Extending.
ConvertSDKError (base; .code: str|None, .context: Mapping[str, Any])
├── ConfigError (base for config shape + loading failures)
│ ├── InvalidConfigError (malformed config, missing or ambiguous init fields)
│ └── ConfigLoadError (network/HTTP failure fetching config)
└── TransportError (non-HTTPS base_url or transport config failure)
(TrackingDeliveryError is a direct subclass of ConvertSDKError, not TransportError)
TrackingDeliveryError (tracking POST delivery failure)
# Not top-level exports — import from convert_sdk.errors:
TrackingError (programmer-misuse base for tracking enqueue)
└── ConversionDataError (invalid conversion_data value at enqueue time)
Every ConvertSDKError carries two structured attributes:
| Attribute | Type | Description |
|---|---|---|
code |
str | None |
Stable, machine-readable failure-category string |
context |
Mapping[str, Any] |
Immutable operational metadata (redaction-safe) |
Known codes:
| Class | code |
|---|---|
ConfigLoadError |
"config_load_failed" |
TrackingDeliveryError |
"tracking_delivery_failed" |
ConfigLoadError additionally exposes .endpoint (redacted URL) and
.status_code (HTTP status or None). TrackingDeliveryError additionally
exposes .endpoint, .status_code, .batch_size, and .retry_count.
from convert_sdk import ConfigLoadError, TrackingDeliveryError
try:
core = Core(SDKConfig(sdk_key="my-key")).initialize()
except ConfigLoadError as exc:
print(exc.code) # "config_load_failed"
print(exc.endpoint) # redacted URL — no SDK key in query string
print(exc.status_code) # int or None
print(dict(exc.context)) # structured metadata mapping
# Tracking delivery failure (raised by core.flush() on a POST failure):
from convert_sdk import LifecycleEvent
from convert_sdk.errors import TrackingDeliveryError
try:
core.flush()
except TrackingDeliveryError as exc:
print(exc.code) # "tracking_delivery_failed"
print(exc.batch_size) # int or None
print(exc.retry_count) # int or NoneTrackingError and ConversionDataError are not top-level exports. Import
them from convert_sdk.errors when catching programmer-misuse failures at
tracking enqueue time:
from convert_sdk.errors import ConversionDataError
try:
context.track_conversion("purchase", conversion_data={"items": [1, 2, 3]})
except ConversionDataError as exc:
print(exc.key) # the offending attribute name
print(exc.reason) # short safe reason stringTop-level frozen dataclass. Exactly one of sdk_key or data must be supplied.
| Field | Type | Default | Notes |
|---|---|---|---|
sdk_key |
str | None |
None |
Remote-config mode — fetch over HTTPS |
data |
dict | None |
None |
Direct-config mode — no network call |
environment |
str | None |
None |
Non-default environment |
cache_level |
str | None |
None |
None or "low"
|
transport |
TransportConfig |
TransportConfig() |
HTTPS transport settings |
batch_size |
int |
10 |
Events per batch before auto-release |
auto_flush_interval_ms |
int | None |
None |
Periodic flush interval; None = explicit-only |
data_store |
DataStore | None |
None |
Custom persistence adapter; None = InMemoryDataStore
|
logger |
logging.Logger | None |
None |
Custom logger; None = logging.getLogger("convert_sdk")
|
refresh |
RefreshConfig | None |
None |
Auto-refresh policy for sdk_key mode |
Property: is_direct_config -> bool — True when data mode is active.
Frozen dataclass. All fields have defaults.
| Field | Type | Default | Notes |
|---|---|---|---|
base_url |
str |
"https://cdn-4.convertexperiments.com" |
Must be HTTPS — raises TransportError otherwise |
timeout |
float |
10.0 |
Request timeout in seconds |
auth_secret |
str | None |
None |
Bearer secret for authenticated keys |
headers |
Mapping[str, str] |
{} |
Extra headers on config requests |
verify_tls |
bool |
True |
Whether to verify TLS certificates |
Frozen dataclass for the opt-in auto-refresh policy (Story 5.2). Only applies
in sdk_key mode; ignored (with a diagnostic log) in data mode.
| Field | Type | Default | Notes |
|---|---|---|---|
interval_seconds |
float |
300.0 |
Base period between refresh attempts; must be > 0
|
jitter_seconds |
float |
30.0 |
Max random jitter per interval; 0 <= jitter <= interval
|
backoff_factor |
float |
2.0 |
Multiplier after consecutive failures; must be >= 1.0
|
backoff_max_seconds |
float |
600.0 |
Backoff ceiling; must be >= interval_seconds
|
These are not re-exported at the top level but are stable for direct import.
convert_sdk.domain.context_state.ContextState — frozen dataclass holding
per-visitor state. Fields: visitor_id: str, snapshot: ConfigSnapshot,
visitor_attributes: Mapping[str, Any], default_segments: Mapping[str, Any].
Immutable rebind helpers: with_attributes(...), with_segments(...),
with_overlay(...).
convert_sdk.domain.config_snapshot.ConfigSnapshot — frozen dataclass holding
the loaded config with precomputed O(1) indexes. Fields include account_id,
project_id, experiences, features, goals, audiences, segments.
Read-only accessor methods: get_experience_by_key, get_feature_by_key,
get_goal_by_key, etc.
-
Configuration — field-by-field
SDKConfig/TransportConfig/RefreshConfigreference -
Diagnostics — reason codes and
diagnose_*workflow - Extending — Protocol-based custom adapter injection
- Code Examples — practical usage of every type
Copyrights © 2025 All Rights Reserved by Convert Insights, Inc.
Getting Started
Python SDK
- Quickstart
- Installation
- Initialization
- Configuration
- Code Examples
- Type Hints
- Diagnostics
- Extending
- Testing
- Async & Frameworks
Migration
Core Concepts
- Experiences & Variations
- Feature Flags
- Bucketing Algorithm
- Rule Evaluation
- Segments
- Data Management
- Event System
- API Communication
How-To Guides
- Running Experiences
- Running Features
- Tracking Conversions
- Visitor Context
- Persistent DataStore
- Troubleshooting
Edge & Integrations
Maintainers