Skip to content

Commit 1187a4a

Browse files
committed
feat: web insights connectivity support with get/set sessionId methods
1 parent 217e880 commit 1187a4a

File tree

18 files changed

+251
-53
lines changed

18 files changed

+251
-53
lines changed

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,54 @@
11
# Changelog
2+
23
All notable changes to this project will be documented in this file.
34

45
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
56
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
67

8+
## [1.18.0] - 2026-02-05
9+
10+
### Added
11+
12+
- Added session management capabilities to enable integration with VWO's web client testing campaigns. The SDK now automatically generates and manages session IDs to connect server-side feature flag decisions with client-side user sessions.
13+
14+
Example usage:
15+
16+
```python
17+
from vwo import init
18+
19+
options = {
20+
'sdk_key': '32-alpha-numeric-sdk-key',
21+
'account_id': '123456',
22+
}
23+
24+
vwo_client = init(options)
25+
26+
# Session ID is automatically generated if not provided
27+
context = {'id': 'user-123'}
28+
flag = vwo_client.get_flag('feature-key', context)
29+
30+
# Access the session ID to pass to web client for session recording
31+
session_id = flag.get_session_id()
32+
print(f"Session ID for web client: {session_id}")
33+
```
34+
35+
You can also explicitly set a session ID to match a web client session:
36+
37+
```python
38+
from vwo import init
39+
40+
vwo_client = init(options)
41+
42+
context = {
43+
'id': 'user-123',
44+
'session_id': 1697123456 # Custom session ID matching web client
45+
}
46+
47+
flag = vwo_client.get_flag('feature-key', context)
48+
```
49+
50+
This enhancement enables seamless integration between server-side feature flag decisions and client-side session recording, allowing for comprehensive user behavior analysis across both server and client environments.
51+
752
## [1.17.0] - 2025-01-08
853

954
### Added

README.md

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,51 @@ vwo_client.track_event('event_name', user_context, event_properties)
5050
vwo_client.set_attribute('attribute_key', 'attribute_value', user_context)
5151
```
5252

53+
## Utility Functions
54+
55+
### getUUID
56+
57+
The `getUUID` function generates a deterministic UUID for a given user and account combination. This utility function can be used without initializing the SDK, making it useful for generating consistent UUIDs across different parts of your application.
58+
59+
#### Function Signature
60+
61+
```python
62+
vwo.getUUID(user_id: str, account_id: str) -> Optional[str]
63+
```
64+
65+
#### Parameters
66+
67+
| **Parameter** | **Description** | **Required** | **Type** |
68+
| ------------- | -------------------------------------------------- | ------------ | -------- |
69+
| `user_id` | The user's unique identifier. | Yes | str |
70+
| `account_id` | The VWO account ID associated with the user. | Yes | str |
71+
72+
#### Return Value
73+
74+
- Returns a UUID string without dashes in uppercase format (e.g., `"A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"`)
75+
- Returns `None` if either parameter is not a valid non-empty string
76+
77+
#### Example
78+
79+
```python
80+
import vwo
81+
82+
# Generate UUID for a user
83+
userId = 'user-123'
84+
accountId = '123456'
85+
86+
uuid = vwo.getUUID(userId, accountId)
87+
88+
print('Generated UUID:', uuid)
89+
# Output: Generated UUID: CC25A368ADA0542699EAD62489811105
90+
```
91+
92+
#### Use Cases
93+
94+
- Generate consistent UUIDs for users across different services
95+
- Create deterministic identifiers for analytics and tracking
96+
- Generate UUIDs without requiring SDK initialization
97+
5398
## Advanced Configuration Options
5499

55100
To customize the SDK further, additional parameters can be passed to the `init()` API. Here’s a table describing each option:
@@ -82,6 +127,7 @@ The following table explains all the parameters in the `context` dictionary:
82127
| `custom_variables` | Custom attributes for targeting. | No | Dict |
83128
| `user_agent` | User agent string for identifying the user's browser and operating system. | No | str |
84129
| `ip_address` | IP address of the user. | No | str |
130+
| `session_id` | Session ID for connecting server-side decisions with client-side sessions. | No | int |
85131

86132
#### Example
87133

@@ -93,10 +139,64 @@ context = {
93139
'location': 'US'
94140
},
95141
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
96-
'ip_address': '1.1.1.1'
142+
'ip_address': '1.1.1.1',
143+
'session_id': 1697123456 # Optional: Custom session ID for web client integration
144+
}
145+
```
146+
147+
## Session Management
148+
149+
The SDK provides automatic session management capabilities to enable seamless integration with VWO's web client testing campaigns. Session IDs are automatically generated and managed to connect server-side feature flag decisions with client-side user sessions.
150+
151+
### Automatic Session ID Generation
152+
153+
Session IDs are automatically generated using Unix timestamps when not explicitly provided in the context. This ensures consistent session tracking across all feature flag evaluations and event tracking.
154+
155+
### Session ID Access
156+
157+
You can access and manage session IDs through the following methods:
158+
159+
- **Get Session ID from Flag**: Use `get_flag.get_session_id()` to retrieve the session ID used for a specific feature flag evaluation
160+
- **Set Custom Session ID**: Set `session_id` in your context dictionary to use a custom session ID (useful for matching web client sessions)
161+
162+
### Example Usage
163+
164+
```python
165+
from vwo import init
166+
167+
options = {
168+
'sdk_key': '32-alpha-numeric-sdk-key',
169+
'account_id': '123456'
170+
}
171+
172+
vwo_client = init(options)
173+
174+
# Session ID is automatically generated if not provided
175+
context = {'id': 'user-123'}
176+
flag = vwo_client.get_flag('feature-key', context)
177+
178+
# Access the session ID to pass to web client for session recording
179+
session_id = flag.get_session_id()
180+
print(f"Session ID for web client: {session_id}")
181+
```
182+
183+
You can also explicitly set a session ID to match a web client session:
184+
185+
```python
186+
from vwo import init
187+
188+
vwo_client = init(options)
189+
190+
context_with_session = {
191+
'id': 'user-123',
192+
'session_id': 1697123456 # Custom session ID matching web client
97193
}
194+
195+
flag = vwo_client.get_flag('feature-key', context_with_session)
98196
```
99197

198+
This enhancement enables seamless integration between server-side feature flag decisions and client-side session recording, allowing for comprehensive user behavior analysis across both server and client environments.
199+
100200
### Basic Feature Flagging
101201

102202
Feature Flags serve as the foundation for all testing, personalization, and rollout rules within FME.

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.17.0",
124+
version="1.18.0",
125125
description="VWO Feature Management and Experimentation SDK for Python",
126126
long_description=long_description,
127127
long_description_content_type="text/markdown",

vwo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from .vwo import init
15+
from .vwo import init, getUUID
1616
from .packages.storage.connector import StorageConnector
1717
from .packages.logger.enums.log_level_enum import LogLevelEnum

vwo/api/get_flag_api.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
from ..utils.network_util import get_track_user_payload_data
4949
from ..enums.event_enum import EventEnum
5050
from ..services.settings_manager import SettingsManager
51+
from ..utils.function_util import (
52+
get_current_unix_timestamp,
53+
)
5154

5255

5356
class GetFlagApi:
@@ -86,7 +89,7 @@ def get(
8689
"an": ApiEnum.GET_FLAG.value,
8790
"fk": feature_key,
8891
"uuid": context.get_vwo_uuid(),
89-
"sId": context.get_vwo_session_id(),
92+
"sId": context.get_session_id(),
9093
}
9194

9295
storage_service = StorageService()
@@ -147,6 +150,11 @@ def get(
147150
LogManager.get_instance().error_log("FEATURE_NOT_FOUND", data={"featureKey": feature_key}, debug_data = debug_event_props)
148151
self._get_flag_response.set_is_enabled(False)
149152
return self._get_flag_response
153+
154+
if context.get_session_id() is None:
155+
context.set_session_id(get_current_unix_timestamp())
156+
157+
self._get_flag_response.set_session_id(context.get_session_id())
150158

151159
SegmentationManager.get_instance().set_contextual_data(
152160
settings, feature, context
@@ -445,4 +453,4 @@ def update_debug_event_props_with_message(
445453
else experiment_key
446454
)
447455
message += f" and experiment:{experiment_suffix} with variation:{experiment_variation_id}"
448-
debug_event_props["msg"] = message
456+
debug_event_props["msg"] = message

vwo/api/set_attribute_api.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from ..enums.event_enum import EventEnum
2828

2929

30+
3031
class SetAttributeApi:
3132
def set_attribute(
3233
self, settings: SettingsModel, attribute_map: Dict, context: ContextModel
@@ -43,13 +44,11 @@ def create_and_send_impression_for_attribute(
4344
)
4445
# Construct payload data for tracking the goal
4546
payload = get_attribute_payload_data(
46-
settings,
47-
context,
48-
EventEnum.VWO_SYNC_VISITOR_PROP.value,
49-
attribute_map
47+
settings, context, EventEnum.VWO_SYNC_VISITOR_PROP.value, attribute_map
5048
)
5149

5250
from vwo.vwo_client import VWOClient
51+
5352
vwo_instance = VWOClient.get_instance()
5453

5554
# Check if batch events are enabled
@@ -58,4 +57,4 @@ def create_and_send_impression_for_attribute(
5857
vwo_instance.batch_event_queue.enqueue(payload)
5958
else:
6059
# Send the event immediately if batch events are not enabled
61-
send_post_api_request(properties, payload, context.get_id())
60+
send_post_api_request(properties, payload, context.get_id())

vwo/api/track_api.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def track(
5858
hook_manager.execute(hook_manager.get())
5959
return {event_name: True}
6060

61-
LogManager.get_instance().error_log("EVENT_NOT_FOUND", data={"eventName": event_name}, debug_data={"an": ApiEnum.TRACK_EVENT.value, "uuid": context.get_vwo_uuid(), "sId": context.get_vwo_session_id()})
61+
LogManager.get_instance().error_log("EVENT_NOT_FOUND", data={"eventName": event_name}, debug_data={"an": ApiEnum.TRACK_EVENT.value, "uuid": context.get_vwo_uuid(), "sId": context.get_session_id()})
6262
return {event_name: False}
6363

6464
def create_and_send_impression_for_track(
@@ -83,13 +83,11 @@ def create_and_send_impression_for_track(
8383
)
8484
# Construct payload data for tracking the goal
8585
payload = get_track_goal_payload_data(
86-
settings,
87-
context,
88-
event_name,
89-
event_properties,
86+
settings, context, event_name, event_properties
9087
)
9188

9289
from vwo.vwo_client import VWOClient
90+
9391
vwo_instance = VWOClient.get_instance()
9492

9593
# Check if batch events are enabled
@@ -98,4 +96,4 @@ def create_and_send_impression_for_track(
9896
vwo_instance.batch_event_queue.enqueue(payload)
9997
else:
10098
# Send the event immediately if batch events are not enabled
101-
send_post_api_request(properties, payload, context.get_id())
99+
send_post_api_request(properties, payload, context.get_id())

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.17.0"
20+
"version": "1.18.0"
2121
}
2222

2323
# Constants

vwo/decorators/storage_decorator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,23 @@ def set_data_in_storage(
5151
context = data.get("context")
5252

5353
if not feature_key:
54-
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "featureKey"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_vwo_session_id()})
54+
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "featureKey"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_session_id()})
5555
return None # Invalid feature key, return None
5656

5757
if not context or not context.get_id():
58-
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "Context or Context.id"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_vwo_session_id()})
58+
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "Context or Context.id"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_session_id()})
5959
return None # Invalid user ID, return None
6060

6161
if (
6262
data.get("rolloutKey")
6363
and not data.get("experimentKey")
6464
and not data.get("rolloutVariationId")
6565
):
66-
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "rolloutKey or experimentKey or rolloutVariationId"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_vwo_session_id()})
66+
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "rolloutKey or experimentKey or rolloutVariationId"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_session_id()})
6767
return None # Invalid rollout variation, return None
6868

6969
if data.get("experimentKey") and not data.get("experimentVariationId"):
70-
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "Variation:(rolloutKey, experimentKey or rolloutVariationId)"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_vwo_session_id()})
70+
LogManager.get_instance().error_log("ERROR_STORING_DATA_IN_STORAGE",data={"key": "Variation:(rolloutKey, experimentKey or rolloutVariationId)"}, debug_data={"an": ApiEnum.GET_FLAG.value, "uuid": context.get_vwo_uuid(), "sId": context.get_session_id()})
7171
return None # Invalid experiment variation, return None
7272

7373
success = storage_service.set_data_in_storage(

vwo/models/user/context_model.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ def __init__(self, context: Dict):
3030
)
3131
self._vwo = ContextVWOModel(context.get("_vwo")) if "_vwo" in context else None
3232
self.post_segmentation_variables = context.get("post_segmentation_variables", [])
33+
self.session_id = context.get("session_id", None)
34+
if self.session_id is None:
35+
self.session_id = get_current_unix_timestamp()
3336
self._vwo_uuid = get_uuid(self.id, str(SettingsManager.get_instance().get_account_id()))
34-
self._vwo_session_id = get_current_unix_timestamp()
3537

3638
def get_id(self) -> str:
3739
return str(self.id) if self.id is not None else None
@@ -71,15 +73,16 @@ def get_post_segmentation_variables(self) -> List[str]:
7173

7274
def set_post_segmentation_variables(self, post_segmentation_variables: List[str]) -> None:
7375
self.post_segmentation_variables = post_segmentation_variables
76+
7477

7578
def get_vwo_uuid(self) -> str:
7679
return self._vwo_uuid
7780

78-
def get_vwo_session_id(self) -> str:
79-
return self._vwo_session_id
80-
8181
def set_vwo_uuid(self, vwo_uuid: str) -> None:
8282
self._vwo_uuid = vwo_uuid
83+
84+
def get_session_id(self) -> int:
85+
return self.session_id
8386

84-
def set_vwo_session_id(self, vwo_session_id: str) -> None:
85-
self._vwo_session_id = vwo_session_id
87+
def set_session_id(self, session_id: int) -> None:
88+
self.session_id = session_id

0 commit comments

Comments
 (0)