Skip to content

Commit c286331

Browse files
committed
feat: use web UUID to connect with web-insights & web-testing
1 parent 1187a4a commit c286331

11 files changed

Lines changed: 157 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.19.0] - 2026-02-17
9+
10+
- Added support to use the context `id` as the visitor UUID instead of auto-generating one. You can read the visitor UUID from the flag result via `flag.get_uuid()` (e.g. to pass to the web client).
11+
12+
Example usage:
13+
14+
```python
15+
from vwo import init
16+
17+
# Initialize the SDK
18+
vwo_client = init({
19+
"account_id": "123456",
20+
"sdk_key": "32-alpha-numeric-sdk-key",
21+
})
22+
23+
# Default: SDK generates a UUID from id and account
24+
context_with_generated_uuid = {"id": "user-123"}
25+
flag1 = vwo_client.get_flag("feature-key", context_with_generated_uuid)
26+
27+
# Use your own UUID (e.g. from web client) by passing a valid web UUID as id
28+
context_with_custom_uuid = {
29+
"id": "D7E2EAA667909A2DB8A6371FF0975C2A5", # your existing UUID
30+
}
31+
flag2 = vwo_client.get_flag("feature-key", context_with_custom_uuid)
32+
33+
# Get the UUID from the flag result (e.g. to pass to web client)
34+
uuid = flag1.get_uuid()
35+
print("Visitor UUID:", uuid)
36+
```
37+
838
## [1.18.0] - 2026-02-05
939

1040
### Added

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def run(self):
121121

122122
setup(
123123
name="vwo-fme-python-sdk",
124-
version="1.18.0",
124+
version="1.19.0",
125125
description="VWO Feature Management and Experimentation SDK for Python",
126126
long_description=long_description,
127127
long_description_content_type="text/markdown",

vwo/api/get_flag_api.py

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ def __init__(self):
5858
self._should_check_for_experiment_rules = False
5959
self._passed_rules_information: Dict[str, Any] = {}
6060
self._evaluated_feature_map: Dict[str, Any] = {}
61-
self._get_flag_response = GetFlag()
6261

6362
def get(
6463
self,
@@ -77,6 +76,8 @@ def get(
7776
"""
7877

7978
feature = get_feature_from_key(settings, feature_key)
79+
is_enabled = False
80+
variables = []
8081
decision = {
8182
"featureName": feature.get_name() if feature else None,
8283
"featureId": feature.get_id() if feature else None,
@@ -115,9 +116,7 @@ def get(
115116
experimentKey=stored_data["experimentKey"],
116117
)
117118
)
118-
self._get_flag_response.set_is_enabled(True)
119-
self._get_flag_response.set_variables(variation.get_variables())
120-
return self._get_flag_response
119+
return GetFlag(is_enabled=True, variables=variation.get_variables(), session_id=context.get_session_id(), uuid=context.get_vwo_uuid())
121120
elif (
122121
stored_data
123122
and stored_data.get("rolloutKey")
@@ -135,8 +134,9 @@ def get(
135134
experimentKey=stored_data["rolloutKey"],
136135
)
137136
)
138-
self._get_flag_response.set_is_enabled(True)
139-
self._get_flag_response.set_variables(variation.get_variables())
137+
is_enabled = True
138+
variables = variation.get_variables()
139+
140140
self._should_check_for_experiment_rules = True
141141
feature_info = {
142142
"rolloutId": stored_data["rolloutId"],
@@ -148,14 +148,12 @@ def get(
148148

149149
if feature is None:
150150
LogManager.get_instance().error_log("FEATURE_NOT_FOUND", data={"featureKey": feature_key}, debug_data = debug_event_props)
151-
self._get_flag_response.set_is_enabled(False)
152-
return self._get_flag_response
151+
is_enabled = False
152+
return GetFlag(is_enabled=is_enabled, variables=variables, session_id=context.get_session_id(), uuid=context.get_vwo_uuid())
153153

154154
if context.get_session_id() is None:
155155
context.set_session_id(get_current_unix_timestamp())
156156

157-
self._get_flag_response.set_session_id(context.get_session_id())
158-
159157
SegmentationManager.get_instance().set_contextual_data(
160158
settings, feature, context
161159
)
@@ -164,7 +162,7 @@ def get(
164162
feature, CampaignTypeEnum.ROLLOUT.value
165163
)
166164

167-
if roll_out_rules and not self._get_flag_response.is_enabled():
165+
if roll_out_rules and not is_enabled:
168166
rollout_rules_to_evaluate: List[CampaignModel] = []
169167

170168
for rule in roll_out_rules:
@@ -215,8 +213,8 @@ def get(
215213
)
216214

217215
if isinstance(variation, VariationModel) and not None:
218-
self._get_flag_response.set_is_enabled(True)
219-
self._get_flag_response.set_variables(variation.get_variables())
216+
is_enabled = True
217+
variables = variation.get_variables()
220218
self._should_check_for_experiment_rules = True
221219
self._update_integrations_decision_object(
222220
rollout_rules_to_evaluate[0], variation, decision
@@ -283,12 +281,9 @@ def get(
283281
else:
284282
if payload is not None and len(payload) > 0:
285283
batchPayload.append(payload)
286-
287-
self._get_flag_response.set_is_enabled(True)
288-
self._get_flag_response.set_variables(
289-
whitelisted_object["variation"].get_variables()
290-
)
291-
284+
285+
is_enabled = True
286+
variables = whitelisted_object["variation"].get_variables()
292287
self._passed_rules_information.update(
293288
{
294289
"experimentId": rule.get_id(),
@@ -307,8 +302,8 @@ def get(
307302
)
308303

309304
if isinstance(variation, VariationModel) and not None:
310-
self._get_flag_response.set_is_enabled(True)
311-
self._get_flag_response.set_variables(variation.get_variables())
305+
is_enabled = True
306+
variables = variation.get_variables()
312307

313308
self._update_integrations_decision_object(
314309
experiment_rules_to_evaluate[0], variation, decision
@@ -331,7 +326,7 @@ def get(
331326
if payload is not None and len(payload) > 0:
332327
batchPayload.append(payload)
333328

334-
if self._get_flag_response.is_enabled():
329+
if is_enabled:
335330
StorageDecorator().set_data_in_storage(
336331
{
337332
"featureKey": feature_key,
@@ -363,9 +358,7 @@ def get(
363358
userId=context.get_id(),
364359
featureKey=feature_key,
365360
status=(
366-
"enabled"
367-
if self._get_flag_response.is_enabled()
368-
else "disabled"
361+
"enabled" if is_enabled else "disabled"
369362
),
370363
)
371364
)
@@ -374,7 +367,7 @@ def get(
374367
settings,
375368
EventEnum.VWO_VARIATION_SHOWN.value,
376369
feature.get_impact_campaign().get_campaign_id(),
377-
(2 if self._get_flag_response.is_enabled() else 1),
370+
(2 if is_enabled else 1),
378371
context
379372
)
380373
if (
@@ -392,7 +385,7 @@ def get(
392385
batchPayload, settings.get_account_id(), settings.get_sdk_key()
393386
)
394387

395-
return self._get_flag_response
388+
return GetFlag(is_enabled=is_enabled, variables=variables, session_id=context.get_session_id(), uuid=context.get_vwo_uuid())
396389

397390
def _update_integrations_decision_object(
398391
self,

vwo/constants/Constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Constants:
1717
# TODO: read from setup.py
1818
sdk_meta = {
1919
"name": "vwo-fme-python-sdk",
20-
"version": "1.18.0"
20+
"version": "1.19.0"
2121
}
2222

2323
# Constants

vwo/models/settings/settings_model.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def __init__(self, data: Dict):
3333
self._version = data["version"]
3434
self._collection_prefix = data.get("collectionPrefix", None)
3535
self._poll_interval = data.get("pollInterval", Constants.POLLING_INTERVAL)
36+
self._is_web_connectivity_enabled = data.get("isWebConnectivityEnabled", True)
3637

3738
# Getter methods for accessing private attributes
3839
def get_features(self) -> List[FeatureModel]:
@@ -88,4 +89,7 @@ def set_poll_interval(self, value: int):
8889
self._poll_interval = value
8990

9091
def get_poll_interval(self) -> int:
91-
return self._poll_interval
92+
return self._poll_interval
93+
94+
def get_is_web_connectivity_enabled(self) -> bool:
95+
return self._is_web_connectivity_enabled

vwo/models/user/context_model.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ def __init__(self, context: Dict):
3333
self.session_id = context.get("session_id", None)
3434
if self.session_id is None:
3535
self.session_id = get_current_unix_timestamp()
36-
self._vwo_uuid = get_uuid(self.id, str(SettingsManager.get_instance().get_account_id()))
36+
37+
# if uuid is provided in the context, use it, otherwise generate a new one
38+
if context.get("uuid") is not None:
39+
self._vwo_uuid = context.get("uuid")
40+
else:
41+
self._vwo_uuid = get_uuid(self.id, str(SettingsManager.get_instance().get_account_id()))
3742

3843
def get_id(self) -> str:
3944
return str(self.id) if self.id is not None else None

vwo/models/user/get_flag.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,25 @@
1818

1919

2020
class GetFlag:
21-
def __init__(self):
22-
self._is_enabled = False
23-
self._variables = []
24-
self._session_id = None
21+
def __init__(self, is_enabled: bool, variables: List[VariableModel], session_id: int, uuid: str):
22+
self._uuid = uuid
23+
self._is_enabled = is_enabled
24+
self._variables = variables
25+
self._session_id = session_id
26+
27+
def get_uuid(self) -> str:
28+
return self._uuid
2529

2630
def is_enabled(self) -> bool:
2731
return self._is_enabled
2832

29-
def set_is_enabled(self, is_enabled: bool) -> None:
30-
self._is_enabled = is_enabled
31-
3233
def get_variables(self) -> List[Dict[str, Any]]:
3334
# convert variables to Dict
3435
variables = []
3536
for variable in self._variables:
3637
variables.append(variable.to_dict())
3738
return variables
3839

39-
def set_variables(self, variables: List[VariableModel]) -> None:
40-
self._variables = variables
41-
4240
def get_variable(self, variable_key: str, default_value: Any = None) -> str:
4341
for variable in self._variables:
4442
if variable.get_key() == variable_key:
@@ -47,6 +45,3 @@ def get_variable(self, variable_key: str, default_value: Any = None) -> str:
4745

4846
def get_session_id(self) -> int:
4947
return self._session_id
50-
51-
def set_session_id(self, session_id: int) -> None:
52-
self._session_id = session_id

vwo/resources/debug-messages.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"IMPRESSION_FOR_TRACK_USER": "Impression built for vwo_variationShown(VWO standard event for tracking user) event haivng Account ID:{accountId}, User ID:{userId}, and experiment ID:{campaignId}",
1212
"IMPRESSION_FOR_TRACK_GOAL": "Impression built for event:{eventName} event having Account ID:{accountId}, and user ID:{userId}",
1313
"IMPRESSION_FOR_SYNC_VISITOR_PROP": "Impression built for {eventName}(VWO internal event) event for Account ID:{accountId}, and user ID:{userId}",
14-
"BATCHING_INITIALIZED": "BATCHING_INITIALIZED"
14+
"BATCHING_INITIALIZED": "BATCHING_INITIALIZED",
15+
"WEB_UUID_FOUND": "VWO Web Testing identified UUID {uuid} as the Context ID for API {apiName}"
1516
}

vwo/utils/network_util.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ def get_events_base_properties(
106106
def _get_event_base_payload(
107107
settings: SettingsModel,
108108
user_id: str,
109-
session_id: str,
110109
event_name: str,
111110
visitor_user_agent: str = "",
112111
ip_address: str = "",
@@ -135,9 +134,7 @@ def _get_event_base_payload(
135134
"d": {
136135
"msgId": f"{uuid_value}-{get_current_unix_timestamp_in_millis()}",
137136
"visId": uuid_value,
138-
"sessionId": (
139-
session_id if session_id is not None else get_current_unix_timestamp()
140-
),
137+
"sessionId": get_current_unix_timestamp(),
141138
"event": {
142139
"props": props,
143140
"name": event_name,
@@ -180,7 +177,6 @@ def get_track_user_payload_data(
180177
properties = _get_event_base_payload(
181178
settings,
182179
user_id,
183-
context.get_session_id(),
184180
event_name,
185181
visitor_user_agent,
186182
ip_address,
@@ -189,6 +185,11 @@ def get_track_user_payload_data(
189185
if context.get_session_id() is not None and context.get_session_id() != 0:
190186
properties["d"]["sessionId"] = context.get_session_id()
191187

188+
# if uuid is provided in the context, use it, otherwise generate a new one
189+
if context.get_vwo_uuid() is not None and context.get_vwo_uuid() != "":
190+
properties["d"]["msgId"] = f"{context.get_vwo_uuid()}-{get_current_unix_timestamp_in_millis()}"
191+
properties["d"]["visId"] = context.get_vwo_uuid()
192+
192193
properties["d"]["event"]["props"]["id"] = campaign_id
193194
properties["d"]["event"]["props"]["variation"] = str(variation_id)
194195
properties["d"]["event"]["props"]["isFirst"] = 1
@@ -224,12 +225,17 @@ def get_track_goal_payload_data(
224225
event_properties: Dict[str, Any],
225226
) -> Dict[str, Any]:
226227
properties = _get_event_base_payload(
227-
settings, context.get_id(), context.get_session_id(), event_name, context.get_user_agent(), context.get_ip_address()
228+
settings, context.get_id(), event_name, context.get_user_agent(), context.get_ip_address()
228229
)
229230

230231
if context.get_session_id() is not None and context.get_session_id() != 0:
231232
properties["d"]["sessionId"] = context.get_session_id()
232233

234+
# if uuid is provided in the context, use it, otherwise generate a new one
235+
if context.get_vwo_uuid() is not None and context.get_vwo_uuid() != "":
236+
properties["d"]["msgId"] = f"{context.get_vwo_uuid()}-{get_current_unix_timestamp_in_millis()}"
237+
properties["d"]["visId"] = context.get_vwo_uuid()
238+
233239
properties["d"]["event"]["props"]["isCustomEvent"] = True
234240
properties["d"]["event"]["props"]["variation"] = 1 # Temporary value for variation
235241
properties["d"]["event"]["props"]["id"] = 1 # Temporary value for ID
@@ -261,6 +267,11 @@ def get_attribute_payload_data(
261267
if context.get_session_id() is not None and context.get_session_id() != 0:
262268
properties["d"]["sessionId"] = context.get_session_id()
263269

270+
# if uuid is provided in the context, use it, otherwise generate a new one
271+
if context.get_vwo_uuid() is not None and context.get_vwo_uuid() != "":
272+
properties["d"]["msgId"] = f"{context.get_vwo_uuid()}-{get_current_unix_timestamp_in_millis()}"
273+
properties["d"]["visId"] = context.get_vwo_uuid()
274+
264275
properties["d"]["event"]["props"]["isCustomEvent"] = True
265276
properties["d"]["event"]["props"][
266277
Constants.VWO_FS_ENVIRONMENT
@@ -461,7 +472,7 @@ def get_messaging_event_payload(
461472
# Get user ID and properties
462473
settings = SettingsManager.get_instance()
463474
user_id = f"{settings.get_account_id()}_{settings.get_sdk_key()}"
464-
properties = _get_event_base_payload(None, user_id, get_current_unix_timestamp(), event_name, None, None)
475+
properties = _get_event_base_payload(None, user_id, event_name, None, None)
465476

466477
# Set the environment key and product
467478
properties["d"]["event"]["props"]["vwo_envKey"] = settings.get_sdk_key()
@@ -501,7 +512,7 @@ def get_sdk_init_event_payload(
501512
# Get user ID and properties
502513
settings = SettingsManager.get_instance()
503514
user_id = f"{settings.get_account_id()}_{settings.get_sdk_key()}"
504-
properties = _get_event_base_payload(None, user_id, get_current_unix_timestamp(), event_name, None, None)
515+
properties = _get_event_base_payload(None, user_id, event_name, None, None)
505516

506517
# Set the required fields as specified
507518
properties["d"]["event"]["props"][
@@ -536,7 +547,7 @@ def get_sdk_usage_stats_event_payload(
536547
settings = SettingsManager.get_instance()
537548
user_id = f"{settings.get_account_id()}_{settings.get_sdk_key()}"
538549
properties = _get_event_base_payload(
539-
None, user_id, get_current_unix_timestamp(), event_name, None, None, True, usage_stats_account_id
550+
None, user_id, event_name, None, None, True, usage_stats_account_id
540551
)
541552

542553
# Set the required fields as specified

0 commit comments

Comments
 (0)