From bedb9bd797cd06a09a45da0b50ad66a783d198ad Mon Sep 17 00:00:00 2001 From: Matthew Boehm Date: Wed, 17 Feb 2016 00:14:56 -0500 Subject: [PATCH 1/2] Ensure that dict keys are strings when json encoding --- docs/source/history.rst | 1 + smiley/jsonutil.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/source/history.rst b/docs/source/history.rst index a513e3f..74d2a59 100644 --- a/docs/source/history.rst +++ b/docs/source/history.rst @@ -7,6 +7,7 @@ dev - Add :ref:`command-report` to produce static HTML output for a run. - Fix a problem with the "ignore" path management under virtualenv. +- Fix some bugs with JSON encoding 0.6 === diff --git a/smiley/jsonutil.py b/smiley/jsonutil.py index 2e9f2ab..ec80e9a 100644 --- a/smiley/jsonutil.py +++ b/smiley/jsonutil.py @@ -6,6 +6,7 @@ import xml.dom.minidom from cliff import commandmanager +from six import string_types LOG = logging.getLogger(__name__) @@ -38,6 +39,13 @@ def _json_special_types(obj): return data +def _stringify(data): + """Convert data to a string if it's not already one""" + # It's not good enough to just call str(k): + # In python2 if data is a `unicode` with non-ascii chars, this will fail. + return data if isinstance(data, string_types) else str(data) + + def _scrub_item(v): """Look for a few types that cause circular references. """ @@ -63,7 +71,7 @@ def _scrub_item(v): def _scrub_dict(data): - return {k: _scrub_item(v) for k, v in data.items()} + return {_stringify(k): _scrub_item(v) for k, v in data.items()} def _scrub_list(data): @@ -89,5 +97,6 @@ def dumps(data): return json.dumps([repr(v) for v in data]) elif isinstance(data, dict): # LOG.debug('trying with repr') - return json.dumps({k: repr(v) for k, v in data.items()}) + return json.dumps( + {_stringify(k): repr(v) for k, v in data.items()}) raise From f6e5b8be4684ba88d44e3a8ac322d98d920aff2e Mon Sep 17 00:00:00 2001 From: Matthew Boehm Date: Wed, 17 Feb 2016 22:33:26 -0500 Subject: [PATCH 2/2] Add unit tests for json encoding dicts with tuple keys --- smiley/tests/test_db.py | 20 ++++++++++++++++++++ smiley/tests/test_jsonutil.py | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/smiley/tests/test_db.py b/smiley/tests/test_db.py index 53efe75..072fb5c 100644 --- a/smiley/tests/test_db.py +++ b/smiley/tests/test_db.py @@ -235,6 +235,26 @@ def test_local_vars(self): self.assertEqual(json.loads(row['local_vars']), self.local_values) + def test_local_var_encoding(self): + """The database should properly encode dict keys as strings""" + self.db.trace( + run_id='12345', + thread_id='t1', + call_id='abcd', + event='test', + func_name='test_trace', + line_no=101, + filename='test_db.py', + trace_arg=self.trace_arg, + local_vars={"tuple_dict": {(1, 2): 3}}, + timestamp=1370436104.65, + ) + c = self.db.conn.cursor() + c.execute("select * from trace where trace.line_no = 101") + row = c.fetchone() + self.assertEqual(row["local_vars"], + '{"tuple_dict": {"(1, 2)": 3}}') + def test_trace_arg(self): c = self.db.conn.cursor() c.execute('select * from trace order by id') diff --git a/smiley/tests/test_jsonutil.py b/smiley/tests/test_jsonutil.py index b2ebbe0..6ad24c3 100644 --- a/smiley/tests/test_jsonutil.py +++ b/smiley/tests/test_jsonutil.py @@ -92,3 +92,9 @@ def test_type(self): expected = "" actual = jsonutil._json_special_types(int) self.assertEqual(expected, actual) + + def test_dict_tuple_keys(self): + """Keys in dicts should be converted to strings""" + actual = jsonutil.dumps({(1, 2): 3}) + expected = '{"(1, 2)": 3}' + self.assertEqual(expected, actual)