-
Notifications
You must be signed in to change notification settings - Fork 0
MigrationFromJavascript
This guide is for teams that are familiar with the Convert JavaScript SDK and want to adopt the Python SDK — either porting backend Node.js code to Python or sharing mental models with a JavaScript front end.
The two SDKs are behaviorally equivalent: the same (visitor_id, experience_id)
pair produces the same variation in both. The API surface uses Pythonic conventions
rather than JavaScript idioms.
| JavaScript SDK | Python SDK | Notes |
|---|---|---|
Core constructor |
Core(SDKConfig(...)).initialize() |
Python uses a dataclass config; initialize() is explicit |
core.onReady() |
core.is_ready |
Python init is synchronous; no async promise |
core.createContext(visitorId, attributes) |
core.create_context(visitor_id, visitor_attributes=attributes) |
snake_case; visitor_attributes= kwarg |
context.runExperience(key, opts) |
context.run_experience(key, attributes=...) |
Returns typed frozen dataclass, not plain dict |
context.runExperiences(opts) |
context.run_experiences() |
Returns list[ExperienceResult]
|
context.runFeature(key, opts) |
context.run_feature(key) |
Returns FeatureResult | None
|
context.runFeatures(opts) |
context.run_features() |
Returns list[FeatureResult]
|
context.trackConversion(key, data) |
context.track_conversion(key, revenue=..., conversion_data=...) |
Returns ConversionResult; unknown goal is NOT an exception |
context.setDefaultSegments(segments) |
context.set_segments(segments) |
Takes a dict of segment keys; kept separate from set_attributes
|
context.runCustomSegments(keys, attrs) |
context.run_custom_segments(keys, rule_data=...) |
Returns CustomSegmentsResult
|
core.on(event, handler) |
core.on(LifecycleEvent.X, handler) |
Typed enum instead of string; handler signature: (payload, error=None)
|
context.releaseQueues() |
core.flush() |
Flush is on Core, not Context; synchronous |
dataRefreshInterval: 300000 (default-on, ms) |
SDKConfig(refresh=RefreshConfig(interval_seconds=300.0)) (opt-in, seconds) |
Unit and default differ — see "Background config refresh" below |
'config.updated' event |
LifecycleEvent.CONFIG_UPDATED |
JS fires after every successful fetch with non-empty data; Python fires only when the snapshot actually differs |
JavaScript:
import { Core } from '@convertcom/js-sdk';
const core = new Core({ sdkKey: process.env.CONVERT_SDK_KEY });
await core.onReady();Python:
import os
from convert_sdk import Core, SDKConfig
core = Core(
SDKConfig(
sdk_key=os.environ["CONVERT_SDK_KEY"],
environment="production",
)
).initialize()
# core.is_ready is True immediately — init is synchronous
assert core.is_readyPython's Core.initialize() is synchronous and blocking. There is no on_ready()
coroutine because initialization does not use an async event loop. If the config
fetch fails, ConfigLoadError is raised at initialize() time, not deferred.
JavaScript:
const context = core.createContext('visitor-abc123', {
browser: 'chrome',
country: 'US',
});Python:
context = core.create_context(
"visitor-abc123",
visitor_attributes={"browser": "chrome", "country": "US"},
)The Python Context object is reusable across multiple evaluations. Update
stored attributes with context.set_attributes(...) and default segments with
context.set_segments(...) (kept strictly separate).
JavaScript:
const result = context.runExperience('checkout-flow', {
locationAttributes: { path: '/checkout' },
});
if (result) {
console.log(result.variationKey);
}Python:
result = context.run_experience("checkout-flow")
if result is not None:
print(result.variation_key, result.variation_id)The Python result is a frozen dataclass (ExperienceResult) rather than a plain
dict. All fields are typed — see Type Hints.
JavaScript:
const feature = context.runFeature('checkout-banner', {
locationAttributes: { path: '/checkout' },
});
if (feature) {
console.log(feature.status, feature.variables);
}Python:
from convert_sdk import FeatureStatus
feature = context.run_feature("checkout-banner")
if feature is not None:
print(feature.status.value) # "enabled" or "disabled"
print(dict(feature.variables)) # type-cast variable dictFeatureStatus is an enum. Compare against the enum members
(feature.status == FeatureStatus.ENABLED) for type safety.
JavaScript:
context.trackConversion('purchase', {
goalData: [
{ key: 'revenue', value: 49.99 },
{ key: 'products_count', value: 2 },
],
});Python:
from convert_sdk import ConversionStatus
result = context.track_conversion(
"purchase_completed",
revenue=49.99,
conversion_data={"products_count": 2},
)
# result is always a typed ConversionResult — never raises for missing goal
if result.status is ConversionStatus.GOAL_NOT_FOUND:
print(result.reason) # "goal_not_found"Key differences:
-
revenueis a named kwarg (not insideconversion_data) -
conversion_datais a flatMapping[str, Any] - An unknown goal returns
ConversionResult(status=GOAL_NOT_FOUND)— it does not raise an exception. Useresult.statusorcontext.diagnose_goal(key)to handle missing goals.
JavaScript:
context.setDefaultSegments({ browser: 'CH', country: 'US' });
const matched = context.runCustomSegments(['segment-key-1'], {});Python:
context.set_segments({"loyalty_tier": "gold"})
result = context.run_custom_segments(
["segment-premium-eu", "segment-mobile"],
rule_data={"device": "mobile"},
)
# result: CustomSegmentsResult
# result.matched_segment_ids: tuple[str, ...] — only newly matched IDsNote: set_segments and set_attributes are distinct methods for distinct
state concerns — segments and attributes are kept strictly separate.
JavaScript:
core.on('conversionCreated', (payload) => {
console.log(payload.goalId);
});Python:
from convert_sdk import LifecycleEvent
from convert_sdk.events import ConversionEventPayload
def on_conversion(payload: ConversionEventPayload, error=None) -> None:
print(payload.goal_key, payload.visitor_id)
core.on(LifecycleEvent.CONVERSION, on_conversion)Python uses a typed LifecycleEvent enum (not bare strings). Each event type
carries a distinct typed payload dataclass — there is no single generic
payload type. Handlers always receive (payload, error=None).
JavaScript:
await context.releaseQueues();Python:
core.flush() # synchronous; no return valueFlush is on Core, not Context. Python's flush() is synchronous. There is
no async variant in the default transport. See Extending for how
to replace the transport.
The two SDKs handle background config refresh differently. A direct port without reading this section will run on stale config indefinitely.
| Concern | JavaScript | Python |
|---|---|---|
| Default behaviour | Always on | Off (SDKConfig.refresh = None) |
| Config field | dataRefreshInterval |
SDKConfig.refresh = RefreshConfig(...) |
| Units | milliseconds | seconds |
| On transient error | Logs and stops rescheduling | Exponential backoff, keeps retrying |
| On bad upstream payload | Logs the parse failure | Stops worker, logs terminal failure |
| Update event |
'config.updated' fires after every successful fetch |
CONFIG_UPDATED fires only when the snapshot actually differs |
| Observability | None on the public surface | core.current_config |
JavaScript:
const core = new Core({
sdkKey: process.env.CONVERT_SDK_KEY,
dataRefreshInterval: 300000, // 5 minutes, in milliseconds
});
core.on('config.updated', () => {
myCache.invalidate();
});Python:
import os
from convert_sdk import Core, SDKConfig, LifecycleEvent, RefreshConfig
core = Core(
SDKConfig(
sdk_key=os.environ["CONVERT_SDK_KEY"],
refresh=RefreshConfig(interval_seconds=300.0), # 5 minutes, in SECONDS
)
).initialize()
core.on(LifecycleEvent.CONFIG_UPDATED, lambda payload, error=None: my_cache.invalidate())The Python SDK also exposes a richer policy surface (jitter_seconds,
backoff_factor, backoff_max_seconds) that the JavaScript SDK does not. See
Initialization § automatic config refresh.
| Area | JavaScript | Python | Why |
|---|---|---|---|
| Naming | camelCase |
snake_case |
PEP 8 convention |
| Results | plain object
|
frozen dataclass
|
Immutability, type safety |
| Async |
Promise / await
|
synchronous (blocking) | Python SDK is sync-first |
| Config object | {sdkKey, environment, ...} |
SDKConfig(sdk_key=..., environment=...) |
Typed frozen dataclass |
| Errors | thrown Error objects |
typed exceptions with .code and .context
|
Structured error handling |
| Goal miss | throws exception | ConversionResult(status=GOAL_NOT_FOUND) |
Normal misses are results, not exceptions |
| Event payloads | single generic payload | distinct frozen dataclass per event | Type safety |
| Diagnostics | console-level debug |
logging integration |
Python stdlib conventions |
| Extension | subclassing / plugins | Protocol implementations | Structural typing |
The bucketing algorithm is identical between the two SDKs. For the same
(visitor_id, experience_id) pair, both SDKs compute the same bucket value and
select the same variation. This is verified by the parity test suite at
tests/parity/ in the python-sdk repository.
The hash input format is f"{experience_id}{visitor_id}" (experience id
concatenated before visitor id) with MurmurHash3 32-bit seed 9999. If you
compute the bucket value manually in JavaScript and compare it to the Python
SDK's result, the values will match.
The MVP is sync-first. An async public API (AsyncCore / AsyncContext)
and framework-specific helpers (convert-sdk-django,
convert-sdk-fastapi, convert-sdk-flask) are planned for Phase 3 and
will share the same evaluation core, parity contracts, and adapter
Protocols as the sync surface. See
Async and Framework Integrations for the design intent.
-
Code Examples — full
run_experience()/run_feature()reference -
Diagnostics —
diagnose_experience()replaces JS SDK debug mode - Extending — replacing transport/storage adapters
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