Skip to content

TypeHints

Usman Abbas edited this page Jun 8, 2026 · 3 revisions

Type Hints

Reference for the dataclasses, Protocols, and enums the Python SDK exposes. All symbols listed here are importable from convert_sdk unless noted otherwise.

This page is the Python analogue of the JavaScript SDK's ConfigTypes and the PHP SDK's ReturnTypes — same role, different idioms.

Public surface (top-level imports)

from convert_sdk import (
    Core,
    Context,
    SDKConfig,
    TransportConfig,
    RefreshConfig,
    # result types
    ExperienceResult,
    FeatureResult,
    FeatureStatus,
    ConversionResult,
    ConversionStatus,
    CustomSegmentsResult,
    # diagnostic types
    DiagnosticReason,
    ExperienceDiagnostic,
    FeatureDiagnostic,
    GoalDiagnostic,
    EntityDiagnostic,
    # lifecycle events
    LifecycleEvent,
    # ports (Protocols for extension)
    DataStore,
    # default adapters
    InMemoryDataStore,
    # errors
    ConvertSDKError,
    ConfigError,
    InvalidConfigError,
    ConfigLoadError,
    TransportError,
    TrackingDeliveryError,
)

Types that are NOT top-level exports but are stable for direct import:

# Distinct lifecycle event payload dataclasses
from convert_sdk.events import ConversionEventPayload, QueueReleasedPayload

# Tracking programmer-misuse errors
from convert_sdk.errors import TrackingError, ConversionDataError

# Port Protocols (for type annotations in custom adapters)
from convert_sdk.ports.transport import Transport
from convert_sdk.ports.event_bus import EventBus, EventHandler

# Internal domain types used by custom DataStore implementations
from convert_sdk.domain.context_state import ContextState
from convert_sdk.domain.config_snapshot import ConfigSnapshot

RefreshConfig is importable from the top-level: from convert_sdk import RefreshConfig.

Result dataclasses

All result types are frozen dataclasses (@dataclass(frozen=True)) — fields cannot be reassigned after construction.

ExperienceResult

Returned by Context.run_experience() (or as elements of Context.run_experiences()).

Field Type Description
experience_id str Internal config id
experience_key str Human-readable key from the dashboard
variation_id str Internal variation id
variation_key str | None Human-readable variation key (may be None if the variation config has no key)
variation Mapping[str, Any] Read-only view of the selected variation's config

FeatureResult

Returned by Context.run_feature() (or elements of Context.run_features()).

Field Type Description
feature_key str Human-readable key
feature_id str Internal config id
status FeatureStatus ENABLED or DISABLED
variables Mapping[str, Any] Type-cast feature variables
experience_key str | None Backing experience key, if any
variation_key str | None Backing variation key

FeatureStatus (enum)

from convert_sdk import FeatureStatus

FeatureStatus.ENABLED   # value: "enabled"
FeatureStatus.DISABLED  # value: "disabled"

Compare against the enum members for type safety:

if feature.status == FeatureStatus.ENABLED:
    ...

ConversionResult

Returned by Context.track_conversion(). Always returned (never raised) for both success and the unknown-goal miss — diagnose the outcome via .status:

Field Type Description
status ConversionStatus QUEUED, DEDUPLICATED, or GOAL_NOT_FOUND
goal_key str The goal key that was requested (always echoed back)
goal_id str | None The resolved goal id, or None on a miss
visitor_id str The visitor the tracking call was made for
event ConversionEvent | None The created in-process conversion event, or 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" on a miss

ConversionStatus (enum)

from convert_sdk import ConversionStatus

ConversionStatus.QUEUED         # goal resolved and event enqueued
ConversionStatus.DEDUPLICATED   # duplicate for already-tracked (visitor, goal)
ConversionStatus.GOAL_NOT_FOUND # goal key absent from config — typed non-exception miss

CustomSegmentsResult

Returned by Context.run_custom_segments().

Field Type Description
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

Diagnostic dataclasses

Returned by Context.diagnose_*() methods. They never raise; they describe the outcome and the reason. All are frozen dataclasses sharing a common base.

Shared fields on all diagnostics

Field Type Description
reason DiagnosticReason Closed enum value describing the outcome
message str Human-readable description
details Mapping[str, Any] Read-only, redaction-safe operational fields
resolved bool (property) True when reason is DiagnosticReason.RESOLVED

DiagnosticReason (enum)

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 the value directly (diag.reason == "resolved") or against the enum member.

ExperienceDiagnostic

Returned by Context.diagnose_experience(key).

FeatureDiagnostic

Returned by Context.diagnose_feature(key). Same shape as ExperienceDiagnostic.

GoalDiagnostic

Returned by Context.diagnose_goal(key).

EntityDiagnostic

Returned by Context.diagnose_entity(entity_type, key).

Lifecycle event types

LifecycleEvent (enum)

from convert_sdk import LifecycleEvent

LifecycleEvent.READY                        # value: "ready"
LifecycleEvent.CONFIG_UPDATED               # value: "config.updated"
LifecycleEvent.BUCKETING                    # value: "bucketing"
LifecycleEvent.CONVERSION                   # value: "conversion"
LifecycleEvent.API_QUEUE_RELEASED           # value: "api.queue.released"
LifecycleEvent.DATA_STORE_QUEUE_RELEASED    # value: "datastore.queue.released"
LifecycleEvent.DIAGNOSTIC                   # value: "diagnostic"

Lifecycle event payloads

There is no single generic LifecycleEventPayload class. Each event type carries its own distinct frozen dataclass payload. Handlers receive (payload, error=None).

ConversionEventPayload — carried on LifecycleEvent.CONVERSION:

from convert_sdk.events import ConversionEventPayload

# Fields on ConversionEventPayload:
# .visitor_id: str
# .goal_id: str
# .goal_key: str

QueueReleasedPayload — carried on LifecycleEvent.API_QUEUE_RELEASED:

from convert_sdk.events import QueueReleasedPayload

# Fields on QueueReleasedPayload:
# .reason: ReleaseReason   (the release trigger — batch_size, flush, timer, etc.)
# .batch_size: int         (number of events in the released batch)
# .visitor_count: int
# .event_count: int
# .status_code: int | None    (HTTP status on failure, absent on success)
# .retry_attempts: int | None (transport retry count, or None/0 if adapter does not retry)

Subscribing to a lifecycle event:

from convert_sdk import LifecycleEvent
from convert_sdk.events import QueueReleasedPayload

def on_queue_released(payload: QueueReleasedPayload, error=None) -> None:
    print(f"Released {payload.batch_size} events, reason={payload.reason}")

core.on(LifecycleEvent.API_QUEUE_RELEASED, on_queue_released)

Extension Protocols

The SDK uses structural typing (Protocol) for extension points — implementing the methods is enough; no inheritance is required. All Protocols are @runtime_checkable.

Transport

from convert_sdk.ports.transport import Transport

class MyTransport:
    def fetch_config(self, config: SDKConfig) -> dict: ...
    def send_tracking(self, payload: dict, *, sdk_key: str) -> None: ...
    def close(self) -> None: ...
    def __enter__(self): ...
    def __exit__(self, *exc): ...

See Extending — Substituting the transport.

DataStore

The MVP required surface is exactly four methods:

from convert_sdk import DataStore

class MyDataStore:
    def get(self, key: str): ...
    def set(self, key: str, value, ttl=None) -> None: ...
    def has(self, key: str) -> bool: ...
    def delete(self, key: str) -> None: ...

The bundled default is InMemoryDataStore, exportable from convert_sdk. Inject a custom store via SDKConfig(data_store=my_store).

EventBus

from convert_sdk.ports.event_bus import EventBus, EventHandler

class MyEventBus:
    def on(self, event: LifecycleEvent, handler: EventHandler) -> None: ...
    def off(self, event: LifecycleEvent, handler: EventHandler) -> None: ...
    def emit(self, event: LifecycleEvent, payload, error=None) -> None: ...

Error hierarchy

ConvertSDKError                            (base; carries .code and .context)
├── ConfigError                            (config shape / loading failures)
│   ├── InvalidConfigError                 (malformed config, missing fields)
│   └── ConfigLoadError                    (network/HTTP error fetching config)
├── TransportError                         (non-HTTPS URL or transport config failure)
└── TrackingDeliveryError                  (tracking POST delivery failure)

# Not top-level exports — import from convert_sdk.errors:
TrackingError                              (programmer misuse at enqueue time)
└── ConversionDataError                    (invalid conversion_data value)

Every error carries:

Attribute Type Description
code str | None Machine-readable error code
context Mapping[str, Any] Structured metadata, immutable

Goal not found is NOT an exception — it is a typed ConversionResult with status == ConversionStatus.GOAL_NOT_FOUND. Use result.status or context.diagnose_goal(key) to handle missing goals.

Internal types you'll occasionally need

ContextState (frozen dataclass)

convert_sdk.domain.context_state.ContextState — used by custom DataStore implementations.

The stored value in a DataStore is a structured envelope {"attributes": {...}, "segments": {...}}. The ContextState type itself is used by the evaluation layer and context creation.

ConfigSnapshot

convert_sdk.domain.config_snapshot.ConfigSnapshot — returned by core.current_config. Not re-exported because most callers should treat the snapshot as opaque, but indexed accessors are stable for support tooling.

What to read next

Clone this wiki locally