Skip to content

Commit 07e95e6

Browse files
Abhi591rohitesh-wingify
authored andcommitted
feat: added default polling option
1 parent 538272a commit 07e95e6

File tree

7 files changed

+77
-26
lines changed

7 files changed

+77
-26
lines changed

CHANGELOG.md

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

7+
## [1.7.0] - 2025-04-11
8+
9+
### Added
10+
11+
- Added support for polling intervals to periodically fetch and update settings:
12+
- If `poll_interval` is set in options (must be >= 1000 milliseconds), that interval will be used
13+
- If `poll_interval` is configured in VWO application settings, that will be used
14+
- If neither is set, defaults to 10 minute polling interval
15+
716
## [1.6.0] - 2025-04-04
817

918
### Added

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,13 @@ vwo_client.set_attribute('attribute_key', 'attribute_value', context)
154154

155155
### Polling Interval Adjustment
156156

157-
The `poll_interval` is an optional parameter that allows the SDK to automatically fetch and update settings from the VWO server at specified intervals. Setting this parameter ensures your application always uses the latest configuration.
157+
The `poll_interval` is an optional parameter that allows the SDK to automatically fetch and update settings from the VWO server at specified intervals. The polling interval can be configured in three ways:
158+
159+
1. Set via SDK options: If `poll_interval` is specified in the initialization options (must be >= 1000 milliseconds), that interval will be used
160+
2. VWO Application Settings: If configured in your VWO application settings, that interval will be used
161+
3. Default Fallback: If neither of the above is set, a 10 minute (600,000 milliseconds) polling interval is used
162+
163+
Setting this parameter ensures your application always uses the latest configuration by periodically checking for and applying any updates.
158164

159165
```python
160166
options = {

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

vwo/constants/Constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Constants:
1717
# Mock package_file equivalent
1818
package_file = {
1919
"name": "vwo-fme-python-sdk", # Replace with actual package name
20-
"version": "1.6.0", # Replace with actual package version
20+
"version": "1.7.0", # Replace with actual package version
2121
}
2222

2323
# Constants
@@ -44,6 +44,7 @@ class Constants:
4444
SETTINGS = "settings"
4545
SETTINGS_EXPIRY = 10000000
4646
SETTINGS_TIMEOUT = 50000
47+
POLLING_INTERVAL = 600000
4748

4849
HOST_NAME = "dev.visualwebsiteoptimizer.com" # TODO: change as needed
4950
SETTINGS_ENDPOINT = "/server-side/v2-settings"

vwo/models/settings/settings_model.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,26 @@
1313
# limitations under the License.
1414

1515

16-
17-
1816
from typing import List, Dict, Optional
1917
from ..campaign.campaign_model import CampaignModel
2018
from ..campaign.feature_model import FeatureModel
2119
from ...utils.model_utils import _parse_campaign, _parse_feature
2220
import json
21+
from ...constants.Constants import Constants
22+
2323

2424
class SettingsModel:
2525
def __init__(self, data: Dict):
2626
# Parse and set attributes
27-
self._features = [_parse_feature(f) for f in data['features']]
28-
self._account_id = data['accountId']
29-
self._groups = data.get('groups', {})
30-
self._campaign_groups = data.get('campaignGroups', {})
31-
self._campaigns = [_parse_campaign(c) for c in data['campaigns']]
32-
self._sdk_key = data['sdkKey']
33-
self._version = data['version']
34-
self._collection_prefix = data.get('collectionPrefix', None)
27+
self._features = [_parse_feature(f) for f in data["features"]]
28+
self._account_id = data["accountId"]
29+
self._groups = data.get("groups", {})
30+
self._campaign_groups = data.get("campaignGroups", {})
31+
self._campaigns = [_parse_campaign(c) for c in data["campaigns"]]
32+
self._sdk_key = data["sdkKey"]
33+
self._version = data["version"]
34+
self._collection_prefix = data.get("collectionPrefix", None)
35+
self._poll_interval = data.get("pollInterval", Constants.POLLING_INTERVAL)
3536

3637
# Getter methods for accessing private attributes
3738
def get_features(self) -> List[FeatureModel]:
@@ -81,4 +82,10 @@ def set_version(self, value: int):
8182
self._version = value
8283

8384
def set_collection_prefix(self, value: Optional[str]):
84-
self._collection_prefix = value
85+
self._collection_prefix = value
86+
87+
def set_poll_interval(self, value: int):
88+
self._poll_interval = value
89+
90+
def get_poll_interval(self) -> int:
91+
return self._poll_interval

vwo/resources/debug-messages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"API_CALLED": "API - {apiName} called",
33
"SERVICE_INITIALIZED": "VWO {service} initialized while creating an instance of SDK",
4+
"USING_POLL_INTERVAL_FROM_SETTINGS": "key: poll_interval not found or invalid. Using poll_interval from {source} ({pollInterval})",
45

56
"EXPERIMENTS_EVALUATION_WHEN_ROLLOUT_PASSED": "Rollout rule got passed for user {userId}. Hence, evaluating experiments",
67
"EXPERIMENTS_EVALUATION_WHEN_NO_ROLLOUT_PRESENT": "No Rollout rules present for the feature. Hence, checking experiment rules",

vwo/vwo_builder.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
)
2828
from .utils.settings_util import set_settings_and_add_campaigns_to_rules
2929
from .packages.storage.storage import Storage
30+
from .constants.Constants import Constants
3031

3132

3233
class VWOBuilder:
@@ -38,6 +39,7 @@ def __init__(self, options):
3839
self.log_manager = None
3940
self.original_settings = None
4041
self.is_settings_fetch_in_progress = False
42+
self.is_valid_poll_interval_passed_from_init = False
4143
self.vwo_instance = None
4244

4345
def set_network_manager(self):
@@ -131,13 +133,13 @@ def get_random_user_id(self):
131133
)
132134

133135
def init_polling(self):
134-
if not self.options.get("poll_interval"):
135-
return self
136-
137136
poll_interval = self.options.get("poll_interval")
138137
if poll_interval and isinstance(poll_interval, int) and poll_interval >= 1000:
139-
self.check_and_poll(poll_interval)
140-
else:
138+
# this is to check if the poll_interval passed in options is valid
139+
self.is_valid_poll_interval_passed_from_init = True
140+
self.check_and_poll()
141+
elif poll_interval:
142+
# only log error if poll_interval is present in options
141143
LogManager.get_instance().error(
142144
error_messages.get("INIT_OPTIONS_INVALID").format(
143145
key="poll_interval", correctType="int >= 1000"
@@ -147,23 +149,48 @@ def init_polling(self):
147149

148150
def build(self, settings):
149151
self.vwo_instance = VWOClient(settings, self.options)
152+
153+
# if poll_interval is not present in options, set it to the pollInterval from settings
154+
self.update_poll_interval_and_check_and_poll(settings)
150155
return self.vwo_instance
151156

152-
def check_and_poll(self, poll_interval):
157+
def update_poll_interval_and_check_and_poll(
158+
self, settings, should_check_and_poll=True
159+
):
160+
# only update the poll_interval if it poll_interval is not valid or not present in options
161+
if not self.is_valid_poll_interval_passed_from_init:
162+
poll_interval = settings.get("pollInterval", Constants.POLLING_INTERVAL)
163+
LogManager.get_instance().debug(
164+
debug_messages.get("USING_POLL_INTERVAL_FROM_SETTINGS").format(
165+
source="settings" if settings.get("pollInterval") else "default",
166+
pollInterval=poll_interval,
167+
)
168+
)
169+
self.options["poll_interval"] = poll_interval
170+
171+
# should_check_and_poll will be true only when we are updating the poll_interval first time from self.build method
172+
# if we are updating the poll_interval already running polling, we don't need to check and poll again
173+
if should_check_and_poll and not self.is_valid_poll_interval_passed_from_init:
174+
self.check_and_poll()
175+
176+
def check_and_poll(self):
153177
def poll():
154178
try:
155179
latest_settings = self.get_settings(True)
156-
if json.dumps(latest_settings, sort_keys=True) != json.dumps(
157-
self.original_settings, sort_keys=True
158-
):
180+
if latest_settings and json.dumps(
181+
latest_settings, sort_keys=True
182+
) != json.dumps(self.original_settings, sort_keys=True):
159183
self.original_settings = latest_settings
160184
LogManager.get_instance().info(
161185
info_messages.get("POLLING_SET_SETTINGS")
162186
)
163187
set_settings_and_add_campaigns_to_rules(
164188
latest_settings, self.vwo_instance
165189
)
166-
else:
190+
# reinitialize the poll_interval value if there is a change in settings
191+
# this is to ensure that we use the updated poll_interval value
192+
self.update_poll_interval_and_check_and_poll(latest_settings, False)
193+
elif latest_settings:
167194
LogManager.get_instance().info(
168195
info_messages.get("POLLING_NO_CHANGE_IN_SETTINGS")
169196
)
@@ -174,8 +201,8 @@ def poll():
174201
finally:
175202
# Reschedule the poll function to be called again after the interval
176203
threading.Timer(
177-
poll_interval / 1000, poll
204+
self.options["poll_interval"] / 1000, poll
178205
).start() # Timer expects seconds, so convert milliseconds
179206

180207
# start the polling after given interval
181-
threading.Timer(poll_interval / 1000, poll).start()
208+
threading.Timer(self.options["poll_interval"] / 1000, poll).start()

0 commit comments

Comments
 (0)