Configuration Options
Field-by-field reference for the SDKConfig, TransportConfig, and RefreshConfig frozen dataclasses
Field-by-field reference for every config dataclass the SDK accepts:
SDKConfig, TransportConfig, and RefreshConfig. For the narrative on init
modes and the refresh lifecycle, see Initialization.
All three are plain frozen dataclasses — no Pydantic dependency. Validation
runs in __post_init__ and raises typed errors from the ConvertSDKError
hierarchy. Import them from convert_sdk:
from convert_sdk import SDKConfig, TransportConfig, RefreshConfigNote: There is no
TrackingConfigclass. Tracking queue and batch settings (batch_size,auto_flush_interval_ms) are fields directly onSDKConfig.
SDKConfig
Top-level config passed to Core(SDKConfig(...)). Exactly one of sdk_key
or data must be provided; passing both or neither raises InvalidConfigError.
from convert_sdk import Core, SDKConfig, TransportConfig, RefreshConfig
import logging
core = Core(
SDKConfig(
sdk_key="your-sdk-key", # remote mode — XOR with data=
# data={"account_id": ...}, # direct/offline mode — XOR with sdk_key=
environment="production",
cache_level=None,
transport=TransportConfig(),
batch_size=10,
auto_flush_interval_ms=None,
data_store=None,
logger=None,
refresh=None,
)
).initialize()Field reference
| Field | Type | Default | Purpose |
|---|---|---|---|
sdk_key | str | None | None | Convert project SDK key. Used when fetching config remotely over HTTPS. Mutually exclusive with data. |
data | dict | None | None | Inline project-config payload for direct/offline initialization. No network call is made. Mutually exclusive with sdk_key. |
environment | str | None | None | Optional environment filter. When set, the config route appends environment={environment} (JS SDK parity). |
cache_level | str | None | None | Optional cache hint. "low" appends _conv_low_cache=1 to the config route (JS SDK parity). Any other non-None value raises InvalidConfigError. |
transport | TransportConfig | TransportConfig() | Network settings for config-fetch and tracking delivery. See TransportConfig below. |
batch_size | int | 10 | Number of queued tracking events that triggers an automatic batch-size queue release. Must be a positive integer. Mirrors the JS SDK DEFAULT_BATCH_SIZE. |
auto_flush_interval_ms | int | None | None | Opt-in periodic-flush interval in milliseconds. None keeps the lifecycle explicit-flush-only — safe in every runtime. When set, a daemonic threading.Timer periodically releases the queue. Must be a positive integer when provided. |
data_store | DataStore | None | None | Optional shared persistence adapter implementing the DataStore protocol. None selects the default per-process InMemoryDataStore. Affects deduplication state and visitor-state persistence. |
logger | logging.Logger | None | None | Optional caller-supplied logging.Logger. None uses the "convert_sdk" namespace logger (logging.getLogger("convert_sdk")). The SDK never calls logging.basicConfig(), adds handlers, or changes the level — your application owns handler/level configuration. |
refresh | RefreshConfig | None | None | Opt-in background config refresh policy. None (default) keeps MVP behavior: no daemon thread, no refresh events, zero added cost. Only effective in sdk_key (remote) mode. |
Validation rules
All validation runs in SDKConfig.__post_init__ and raises InvalidConfigError:
| Rule | Error message |
|---|---|
Exactly one of sdk_key or data must be provided | "SDKConfig requires exactly one of 'sdk_key' or 'data'; neither was provided" / "both were provided" |
sdk_key, when supplied, must be a non-empty string | "'sdk_key' must be a non-empty string" |
data, when supplied, must be a dict / mapping | "'data' must be a mapping/dict" |
cache_level must be None or "low" | "'cache_level' must be one of (None, 'low')" |
batch_size must be a positive integer | "'batch_size' must be a positive integer" |
auto_flush_interval_ms, when provided, must be a positive integer | "'auto_flush_interval_ms' must be a positive integer or None" |
Queue and tracking settings
The two tracking-queue settings live on SDKConfig — there is no separate
TrackingConfig:
batch_size (default 10): controls when the in-process queue
self-releases. Once the queue accumulates batch_size events, a batch-size
release fires automatically. Set lower for low-latency workloads; set higher to
amortize delivery cost in high-throughput services.
auto_flush_interval_ms (default None): opts into a daemonic timer that
releases the queue on a fixed interval. Leave as None (explicit-flush-only)
for Lambda, cron jobs, and other short-lived runtimes where a background thread
is inappropriate. A value of 2000 flushes every 2 seconds.
Both settings interact: whichever trigger fires first releases the queue. A
core.flush() call always works regardless of either setting.
See Queue Control for the full release-trigger matrix and per-runtime guidance.
is_direct_config property
is_direct_config propertySDKConfig.is_direct_config is True when data= was provided (no network).
The SDK uses this internally to skip transport construction; you can use it in
your own config helpers.
TransportConfig
Network settings for the SDK's bundled httpx-backed transport. Used only when
SDKConfig.sdk_key is set — direct-config (data) initialization never
constructs or uses a transport.
from convert_sdk import TransportConfig
transport = TransportConfig(
base_url="https://cdn-4.convertexperiments.com",
timeout=10.0,
auth_secret=None,
headers={},
verify_tls=True,
)Field reference
| Field | Type | Default | Purpose |
|---|---|---|---|
base_url | str | "https://cdn-4.convertexperiments.com" | HTTPS base URL of the config-serving endpoint. The transport appends /api/v1/config/{sdkKey} to form the full config route. Must use the https scheme — a non-HTTPS URL raises TransportError at construction, before any network I/O (NFR8: TLS-only transport). |
timeout | float | 10.0 | Per-request timeout in seconds, applied to both config-fetch and tracking delivery. |
auth_secret | str | None | None | Optional bearer secret sent as an Authorization: Bearer <secret> header on config-fetch and tracking requests. Never logged. |
headers | Mapping[str, str] | {} | Optional extra headers appended to every config-fetch request. |
verify_tls | bool | True | Whether the httpx client verifies TLS certificates. Set to False only for development against a local HTTPS proxy. |
TLS enforcement
A non-HTTPS base_url raises TransportError at TransportConfig
construction — before Core.initialize() is called and before any network I/O
is possible. This is a hard requirement (NFR8) that cannot be bypassed by
supplying a custom transport at the SDKConfig level.
from convert_sdk import TransportConfig, TransportError
try:
t = TransportConfig(base_url="http://insecure.example.com")
except TransportError as exc:
print("rejected:", exc)
# TransportError: transport base_url must use HTTPS (TLS-only transport, NFR8); got scheme='http'RefreshConfig
Background config-refresh policy for long-running remote (sdk_key) instances.
Opt-in: SDKConfig.refresh=None (the default) keeps the SDK in MVP behavior
— no daemon thread, no periodic re-fetch.
from convert_sdk import RefreshConfig
refresh = RefreshConfig(
interval_seconds=300.0, # base poll period (5 minutes)
jitter_seconds=30.0, # max random jitter per cycle
backoff_factor=2.0, # exponential backoff multiplier on failure
backoff_max_seconds=600.0, # backoff ceiling (10 minutes)
)Field reference
| Field | Type | Default | Purpose |
|---|---|---|---|
interval_seconds | float | 300.0 | Base period (in seconds) between successful refresh attempts. Mirrors the JS SDK dataRefreshInterval default of 300 000 ms expressed in seconds. Must be > 0. |
jitter_seconds | float | 30.0 | Maximum uniform random jitter added to each scheduled wait, so a fleet of processes does not synchronize on the same refresh instant ("thundering herd" mitigation). Must satisfy 0 <= jitter_seconds <= interval_seconds. |
backoff_factor | float | 2.0 | Multiplier applied to the wait after each consecutive transient failure (exponential backoff). Must be >= 1.0. 1.0 means no backoff (constant retry interval). |
backoff_max_seconds | float | 600.0 | Ceiling on the backed-off wait. Ensures a persistently failing endpoint is retried at a bounded cadence rather than with exponentially growing pauses. Must be >= interval_seconds. |
Validation rules
All validation runs in RefreshConfig.__post_init__ and raises
InvalidConfigError:
| Rule | Condition |
|---|---|
interval_seconds > 0 | Non-positive or non-numeric value |
0 <= jitter_seconds <= interval_seconds | Negative jitter, or jitter exceeds interval |
backoff_factor >= 1.0 | Less than 1.0 |
backoff_max_seconds >= interval_seconds | Less than interval |
Behavior summary
- The background worker starts when
Core.initialize()is called with aRefreshConfigpresent andsdk_keymode active. - Each refresh re-fetches config through the same transport used at init.
- A successful fetch atomically swaps the immutable snapshot under a mutex — see ADR 0001.
- On every successful swap the SDK emits
LifecycleEvent.CONFIG_UPDATED. - A transient failure leaves the prior snapshot in place; the worker backs off
exponentially up to
backoff_max_seconds. - Persistent failures are logged through the diagnostic logger (never crash the host process).
- The worker is a daemon thread and never blocks interpreter exit.
Core.close()stops the worker cleanly.- Supplying a
RefreshConfigin direct-config (data) mode starts no worker and logs arefresh.skippeddiagnostic.
Recommended configurations
Unit tests / CI
from convert_sdk import SDKConfig
config = SDKConfig(
data={
"account_id": "1001",
"project": {"id": "2002", "name": "Demo"},
"experiences": [],
"features": [],
"goals": [],
}
)
# No network, no background threads, deterministic.Short-lived script or cron job
import os
from convert_sdk import SDKConfig
config = SDKConfig(
sdk_key=os.environ["CONVERT_SDK_KEY"],
environment="production",
# refresh=None — process exits before a single interval would tick.
# flush() explicitly at the end of the script.
)Long-running web service (Django / Flask / FastAPI)
import os
from convert_sdk import SDKConfig, RefreshConfig
config = SDKConfig(
sdk_key=os.environ["CONVERT_SDK_KEY"],
environment="production",
batch_size=25,
refresh=RefreshConfig(
interval_seconds=300.0,
jitter_seconds=30.0,
backoff_factor=2.0,
backoff_max_seconds=600.0,
),
)
# Larger batch_size amortises per-request flush cost.
# Auto-refresh keeps long-lived processes from serving stale config.Opt-in periodic flush for a long-lived background worker
import os
from convert_sdk import SDKConfig
config = SDKConfig(
sdk_key=os.environ["CONVERT_SDK_KEY"],
batch_size=50,
auto_flush_interval_ms=5000, # flush every 5 seconds
)What to read next
- Initialization — narrative on init modes, context-manager lifecycle, refresh
- Code Examples — practical patterns using each option
- Type Hints — full dataclass field types and enums
- Extending — custom
Transport,DataStore, and event-bus - Testing — test patterns using direct-config mode