Skip to content

Commit 40b7dfc

Browse files
authored
Merge pull request #16 from homiedopie/hotfix/INV-1894
(INV-1984) - Circular reference issue with WSGIRequest JSON serialization
2 parents 5241aeb + d1b9028 commit 40b7dfc

File tree

6 files changed

+85
-15
lines changed

6 files changed

+85
-15
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
flake8
22
mock==2.0.0
3-
protobuf==3.9.1
3+
protobuf==3.15.0
44
pytest==4.3.0
55
pytest-cov==2.6.1
66
requests==2.21.0

stackify/transport/default/log.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from stackify.transport.default.formats import JSONObject
33
from stackify.transport.default.error import StackifyError
44
from stackify.utils import data_to_json
5+
from stackify.utils import extract_request
56

67

78
class LogMsg(JSONObject):
@@ -31,6 +32,8 @@ def from_record(self, record):
3132
data = {k: v for k, v in record.__dict__.items()
3233
if k not in RECORD_VARS}
3334

35+
data = extract_request(data)
36+
3437
if data:
3538
self.data = data_to_json(data)
3639

stackify/utils.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,41 @@ def arg_or_env(name, args, default=None, env_key=None):
2323

2424
def data_to_json(data):
2525
try:
26-
if object_is_iterable(data) and 'request' in data and hasattr(data['request'], '_messages'):
27-
data['request'] = get_default_object(data['request'])
28-
2926
return json.dumps(data, default=lambda x: get_default_object(x))
30-
except ValueError as e:
31-
internal_logger.exception('Failed to serialize object to json: {} - Exception: {}'.format(data.__str__(), str(e)))
32-
return json.dumps(data.__str__()) # String representation of the object
27+
except Exception as e:
28+
internal_logger.exception('Failed to serialize object to json: {} - Exception: {}'.format(str(data), str(e)))
29+
return str(data) # String representation of the object
30+
31+
32+
def extract_request(data):
33+
if 'request' in data and "WSGIRequest" in str(data['request']):
34+
new_request = {}
35+
obj = data['request']
36+
37+
if hasattr(obj, 'path'):
38+
new_request['path'] = obj.path
39+
40+
if hasattr(obj, 'method'):
41+
new_request['method'] = obj.method
42+
43+
if hasattr(obj, 'POST'):
44+
new_request['form'] = obj.POST
45+
46+
if hasattr(obj, 'GET'):
47+
new_request['query'] = obj.GET
48+
49+
if hasattr(obj, 'content_type'):
50+
new_request['content_type'] = obj.content_type
51+
52+
data['request'] = new_request
53+
54+
return data
3355

3456

3557
def get_default_object(obj):
58+
if object_is_iterable(obj):
59+
return [item for item in obj]
60+
3661
return hasattr(obj, '__dict__') and obj.__dict__ or obj.__str__()
3762

3863

test.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ function runPyTest() {
2222
virtualenv -p python${python_version} ${test_venv}
2323

2424
echo "Activating virtualenv ${test_venv}..."
25-
source ${test_venv}/bin/activate
25+
26+
if [ -f ${test_venv}/bin/activate ]; then
27+
source ${test_venv}/bin/activate
28+
fi
29+
30+
if [ -f ${test_venv}/Scripts/activate ]; then
31+
source ${test_venv}/Scripts/activate
32+
fi
2633

2734
echo 'Installing dependencies...'
2835
pip install -r requirements.txt

tests/test_utils.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ def tearDown(self):
3333
for logger in self.loggers:
3434
del global_loggers[logger.name]
3535

36+
def test_utils_data_to_json_string(self):
37+
dummy = 'test'
38+
result = stackify.utils.data_to_json(dummy)
39+
self.assertEqual('"test"', result)
40+
3641
def test_utils_data_to_json_unserializable(self):
3742
dummy = Dummy()
3843
result = stackify.utils.data_to_json(dummy)
@@ -60,9 +65,9 @@ def test_utils_data_to_json_tuple(self):
6065
self.assertEqual(expected, result)
6166

6267
def test_utils_data_to_json_dummy_iterable(self):
63-
dummy = DummyInterable()
68+
dummy = DummyIterable()
6469
result = stackify.utils.data_to_json(dummy)
65-
expected = '{"a": 21}'
70+
expected = '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]'
6671
self.assertEqual(expected, result)
6772

6873
def test_utils_data_to_json_dummy_object_with_property(self):
@@ -82,17 +87,38 @@ def test_utils_data_to_json_dummy_object_circular(self, func):
8287
data['payload']['dummy'] = data
8388

8489
result = stackify.utils.data_to_json(data)
85-
func.assert_called()
8690

87-
substring = "'dummy': <tests.test_utils.DummyProperty object at"
91+
substring = "'payload': {'dummy': "
92+
data_str = str(data)
93+
94+
assert func.called
95+
assert "Failed to serialize object to json: {}".format(data_str) in func.call_args_list[0][0][0]
96+
8897
self.assertTrue(substring in result)
98+
self.assertEqual(result, data_str)
8999

90100
def test_utils_data_to_json_dummy_request(self):
91101
dummy = DummyRequest()
92102
result = stackify.utils.data_to_json(dummy)
93103
substring = '{"_messages": "<tests.test_utils.Dummy object at'
94104
self.assertTrue(substring in result)
95105

106+
def test_utils_extract_request_dummy_wsgi_request(self):
107+
dummy = WSGIRequestMock()
108+
dummy.path = 'test'
109+
dummy.POST = 'test_form'
110+
dummy.not_exists = 'test'
111+
data = {
112+
'request': dummy
113+
}
114+
115+
result = stackify.utils.extract_request(data)
116+
request = result['request']
117+
118+
self.assertEqual(request['path'], 'test')
119+
self.assertEqual(request['form'], 'test_form')
120+
self.assertTrue('not_exists' not in request)
121+
96122

97123
class RegexValidatorTest(ClearEnvTest):
98124

@@ -120,13 +146,13 @@ class Dummy(object):
120146
pass
121147

122148

123-
class DummyInterable:
149+
class DummyIterable:
124150
def __iter__(self):
125151
self.a = 1
126152
return self
127153

128154
def __next__(self):
129-
if self.a <= 20:
155+
if self.a <= 10:
130156
x = self.a
131157
self.a += 1
132158
return x
@@ -147,5 +173,10 @@ def __init__(self):
147173
self._messages = Dummy()
148174

149175

176+
class WSGIRequestMock:
177+
def __init__(self):
178+
pass
179+
180+
150181
if __name__ == '__main__':
151182
unittest.main()

tests/transport/agent/test_agent_socket.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import retrying
33
from unittest import TestCase
44
from mock import patch
5+
import os
56

67
import stackify
78
from tests.bases import fake_retry_decorator
@@ -40,7 +41,10 @@ def test_send_should_use_defaut_socket(self, mock_post):
4041
self.agent_socket.send(url, message)
4142

4243
assert mock_post.called
43-
assert mock_post.call_args_list[0][0][0] == 'http+unix://%2Fusr%2Flocal%2Fstackify%2Fstackify.sock/test_url'
44+
if os.name == 'nt':
45+
assert mock_post.call_args_list[0][0][0] == 'http+unix://%2Fusr%2Flocal%2Fstackify%2Fstackify.sock\\test_url'
46+
else:
47+
assert mock_post.call_args_list[0][0][0] == 'http+unix://%2Fusr%2Flocal%2Fstackify%2Fstackify.sock/test_url'
4448

4549
@patch('requests_unixsocket.Session.post')
4650
def test_send_should_include_headers(self, mock_post):

0 commit comments

Comments
 (0)