Skip to content

Commit 3245ce5

Browse files
Abhi591rohitesh-wingify
authored andcommitted
fix: Check for None in user_agent and ip_address
1 parent f693474 commit 3245ce5

File tree

8 files changed

+80
-45
lines changed

8 files changed

+80
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ dist/
2929
coverage/
3030
htmlcov/
3131
.venv/
32+
venv/
3233
ENV*

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ 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.1.0] - 2024-08-29
8+
9+
### Fixed
10+
11+
- Fix: Check for None values in `user_agent` and `ip_address` when sending impressions to VWO.
12+
713
## [1.0.0] - 2024-06-20
814
### Added
915
- First release of VWO Feature Management and Experimentation capabilities

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ include requirements.txt
33
include requirements-dev.txt
44
include LICENSE
55
recursive-exclude tests *
6+
recursive-include vwo/resources *.json

setup.py

Lines changed: 2 additions & 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.0.0",
124+
version="1.1.0",
125125
description="VWO Feature Management and Experimentation SDK for Python",
126126
long_description=long_description,
127127
long_description_content_type="text/markdown",
@@ -152,5 +152,6 @@ def run(self):
152152
"doc_check": DocCheckCommand,
153153
},
154154
packages=find_packages(exclude=["tests"]),
155+
include_package_data=True,
155156
install_requires=REQUIREMENTS,
156157
)

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
# Mock package_file equivalent
1818
package_file = {
1919
'name': 'vwo-fme-python-sdk', # Replace with actual package name
20-
'version': '1.0.0' # Replace with actual package version
20+
'version': '1.1.0' # Replace with actual package version
2121
}
2222

2323
# Constants

vwo/packages/network_layer/manager/network_manager.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ async def post_async(self, request: RequestModel):
7575
) as response:
7676
return response.status
7777
except Exception as err:
78-
# LogManager.get_instance().error(error_messages.get('NETWORK_CALL_FAILED').format(
79-
# method = 'POST',
80-
# err = err.with_traceback()
81-
# ))
82-
return
78+
LogManager.get_instance().error(
79+
error_messages.get('NETWORK_CALL_FAILED').format(
80+
method='POST',
81+
err=err,
82+
)
83+
)

vwo/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from ....enums.url_enum import UrlEnum
2424
from ....utils.data_type_util import is_boolean
2525
from ....models.user.context_model import ContextModel
26+
from ....services.settings_manager import SettingsManager
2627

2728

2829
class SegmentOperandEvaluator:
@@ -44,7 +45,7 @@ def evaluate_custom_variable_dsl(
4445
return False
4546

4647
if "inlist" in operand:
47-
list_id_regex = r"inlist\((\w+:\d+)\)"
48+
list_id_regex = r"inlist\([^)]*\)"
4849
match = re.search(list_id_regex, operand)
4950
if not match or len(match.groups()) < 1:
5051
print("Invalid 'inList' operand format")
@@ -56,6 +57,8 @@ def evaluate_custom_variable_dsl(
5657
query_params_obj = {
5758
"attribute": attribute_value,
5859
"listId": list_id,
60+
"accountId": SettingsManager.get_instance().account_id,
61+
"sdkKey": SettingsManager.get_instance().sdk_key,
5962
}
6063

6164
try:

vwo/utils/network_util.py

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -193,18 +193,43 @@ def get_attribute_payload_data(
193193

194194
return properties
195195

196-
# Function to send a POST API request
196+
197+
# Global variables for event loop management
198+
# `event_loop_initialized` tracks whether the event loop has been initialized.
199+
# `main_event_loop` stores the reference to the main event loop that handles async tasks.
200+
# `loop_lock` ensures that only one thread can initialize or use the event loop at a time.
201+
event_loop_initialized = False
202+
main_event_loop = None
203+
loop_lock = threading.Lock()
204+
205+
# Function to send a POST API request without waiting for the response
197206
def send_post_api_request(properties: Dict[str, Any], payload: Dict[str, Any]):
198-
url = f"{Constants.HTTPS_PROTOCOL}://{UrlService.get_base_url()}{UrlEnum.EVENTS.value}"
207+
global event_loop_initialized, main_event_loop
208+
209+
# Importing the SettingsManager here to avoid circular import issues or unnecessary imports
199210
from ..services.settings_manager import SettingsManager
200211

201-
headers = {
202-
HeadersEnum.USER_AGENT.value: payload['d'].get('visitor_ua', ''),
203-
HeadersEnum.IP.value: payload['d'].get('visitor_ip', '')
204-
}
212+
# Initialize the headers dictionary for the request
213+
headers = {}
214+
215+
# Retrieve 'visitor_ua' and 'visitor_ip' from the payload if they exist
216+
# Strip any whitespace and ensure they are valid strings before adding to headers
217+
visitor_ua = payload['d'].get('visitor_ua')
218+
visitor_ip = payload['d'].get('visitor_ip')
219+
220+
# Add 'visitor_ua' to headers if it's a valid, non-empty string after stripping whitespace
221+
if visitor_ua and isinstance(visitor_ua, str) and visitor_ua.strip():
222+
headers[HeadersEnum.USER_AGENT.value] = visitor_ua.strip()
223+
224+
# Add 'visitor_ip' to headers if it's a valid, non-empty string after stripping whitespace
225+
if visitor_ip and isinstance(visitor_ip, str) and visitor_ip.strip():
226+
headers[HeadersEnum.IP.value] = visitor_ip.strip()
205227

206228
try:
229+
# Get the instance of NetworkManager that handles making network requests
207230
network_instance = NetworkManager.get_instance()
231+
232+
# Create a RequestModel object that holds all the necessary data for the POST request
208233
request = RequestModel(
209234
UrlService.get_base_url(),
210235
'POST',
@@ -216,40 +241,37 @@ def send_post_api_request(properties: Dict[str, Any], payload: Dict[str, Any]):
216241
SettingsManager.get_instance().port
217242
)
218243

219-
# Attempt to get the running loop
220-
try:
221-
loop = asyncio.get_event_loop()
222-
except RuntimeError:
223-
loop = None
224-
225-
if loop and loop.is_running():
226-
# If an event loop is running, schedule the task safely
227-
asyncio.run_coroutine_threadsafe(network_instance.post_async(request), loop)
228-
else:
229-
# If no event loop is running, run it in a new detached thread
230-
run_in_thread(network_instance.post_async(request))
231-
232-
return
233-
244+
# Lock the event loop initialization to prevent race conditions in multi-threaded environments
245+
with loop_lock:
246+
# Check if the event loop is already initialized and running
247+
if event_loop_initialized and main_event_loop.is_running():
248+
# If the loop is running, submit the asynchronous POST request to the loop
249+
# This will not block the main thread
250+
asyncio.run_coroutine_threadsafe(network_instance.post_async(request), main_event_loop)
251+
else:
252+
# If the event loop has not been initialized or is not running:
253+
# 1. Mark the event loop as initialized
254+
# 2. Create a new event loop
255+
# 3. Start the event loop in a separate thread so it doesn't block the main thread
256+
event_loop_initialized = True
257+
main_event_loop = asyncio.new_event_loop()
258+
threading.Thread(target=start_event_loop, args=(main_event_loop,), daemon=True).start()
259+
260+
# Submit the asynchronous POST request to the newly started event loop
261+
asyncio.run_coroutine_threadsafe(network_instance.post_async(request), main_event_loop)
262+
234263
except Exception as err:
235264
LogManager.get_instance().error(
236265
error_messages.get('NETWORK_CALL_FAILED').format(
237-
method = 'POST',
238-
err = err,
266+
method='POST',
267+
err=err,
239268
),
240269
)
241270

242-
# Function to run an asyncio event loop in a separate thread
243-
def run_in_thread(coro):
244-
def run():
245-
loop = asyncio.new_event_loop()
246-
asyncio.set_event_loop(loop)
247-
try:
248-
loop.run_until_complete(coro)
249-
finally:
250-
loop.run_until_complete(loop.shutdown_asyncgens())
251-
loop.close()
252-
253-
thread = threading.Thread(target=run)
254-
thread.daemon = True # Ensure the thread does not block program exit
255-
thread.start()
271+
# Function to start the event loop in a new thread
272+
def start_event_loop(loop):
273+
# Set the provided loop as the current event loop for the new thread
274+
asyncio.set_event_loop(loop)
275+
276+
# Run the event loop indefinitely to handle any submitted asynchronous tasks
277+
loop.run_forever()

0 commit comments

Comments
 (0)