diff --git a/plotly/tests/test_core/test_utils/__init__.py b/plotly/tests/test_core/test_utils/__init__.py new file mode 100644 index 00000000000..dbd5b79d852 --- /dev/null +++ b/plotly/tests/test_core/test_utils/__init__.py @@ -0,0 +1,3 @@ +def setup_package(): + import warnings + warnings.filterwarnings('ignore') diff --git a/plotly/tests/test_core/test_utils/test_utils.py b/plotly/tests/test_core/test_utils/test_utils.py new file mode 100644 index 00000000000..5f0a62d88ed --- /dev/null +++ b/plotly/tests/test_core/test_utils/test_utils.py @@ -0,0 +1,13 @@ +import json +from unittest import TestCase + +from plotly.utils import PlotlyJSONEncoder + + +class TestJSONEncoder(TestCase): + + def test_nan_to_null(self): + array = [1, float('NaN'), float('Inf'), float('-Inf'), 'platypus'] + result = json.dumps(array, cls=PlotlyJSONEncoder) + expected_result = '[1, null, null, null, "platypus"]' + self.assertEqual(result, expected_result) diff --git a/plotly/tests/test_optional/test_utils/test_utils.py b/plotly/tests/test_optional/test_utils/test_utils.py index dc54e2f1dbd..a1450913860 100644 --- a/plotly/tests/test_optional/test_utils/test_utils.py +++ b/plotly/tests/test_optional/test_utils/test_utils.py @@ -173,7 +173,7 @@ def test_column_json_encoding(): '"2014-01-05 01:01:01", ' '"2014-01-05 01:01:01.000001"], ' '"name": "col 2"}, ' - '{"data": [1, 2, 3, NaN, NaN, Infinity, ' + '{"data": [1, 2, 3, null, null, null, ' '"2014-01-05"], "name": "col 3"}]' == json_columns) @@ -188,7 +188,7 @@ def test_figure_json_encoding(): js2 = json.dumps(s2, cls=utils.PlotlyJSONEncoder, sort_keys=True) assert(js1 == '{"type": "scatter3d", "x": [1, 2, 3], ' - '"y": [1, 2, 3, NaN, NaN, Infinity, "2014-01-05"], ' + '"y": [1, 2, 3, null, null, null, "2014-01-05"], ' '"z": [1, "A", "2014-01-05", ' '"2014-01-05 01:01:01", "2014-01-05 01:01:01.000001"]}') assert(js2 == '{"type": "scatter", "x": [1, 2, 3]}') @@ -219,7 +219,7 @@ def test_datetime_json_encoding(): def test_pandas_json_encoding(): j1 = json.dumps(df['col 1'], cls=utils.PlotlyJSONEncoder) - assert(j1 == '[1, 2, 3, "2014-01-05", null, NaN, Infinity]') + assert(j1 == '[1, 2, 3, "2014-01-05", null, null, null]') # Test that data wasn't mutated assert_series_equal(df['col 1'], @@ -249,7 +249,7 @@ def test_numpy_masked_json_encoding(): l = [1, 2, np.ma.core.masked] j1 = json.dumps(l, cls=utils.PlotlyJSONEncoder) print j1 - assert(j1 == '[1, 2, NaN]') + assert(j1 == '[1, 2, null]') assert(set(l) == set([1, 2, np.ma.core.masked])) @@ -276,7 +276,7 @@ def test_masked_constants_example(): jy = json.dumps(renderer.plotly_fig['data'][1]['y'], cls=utils.PlotlyJSONEncoder) assert(jy == '[-398.11793026999999, -398.11792966000002, ' - '-398.11786308000001, NaN]') + '-398.11786308000001, null]') def test_numpy_dates(): diff --git a/plotly/utils.py b/plotly/utils.py index 216f0077b20..e948a07507f 100644 --- a/plotly/utils.py +++ b/plotly/utils.py @@ -119,8 +119,72 @@ class PlotlyJSONEncoder(json.JSONEncoder): See PlotlyJSONEncoder.default for more implementation information. + Additionally, this encoder overrides nan functionality so that 'Inf', + 'NaN' and '-Inf' encode to 'null'. Which is stricter JSON than the Python + version. + """ + # we want stricter JSON, so convert NaN, Inf, -Inf --> 'null' + nan_str = inf_str = neg_inf_str = 'null' + + # uses code from official python json.encoder module. Same licence applies. + def iterencode(self, o, _one_shot=False): + """ + Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + if self.ensure_ascii: + _encoder = json.encoder.encode_basestring_ascii + else: + _encoder = json.encoder.encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + + def floatstr(o, allow_nan=self.allow_nan, + _repr=json.encoder.FLOAT_REPR, _inf=json.encoder.INFINITY, + _neginf=-json.encoder.INFINITY): + # Check for specials. Note that this type of test is processor + # and/or platform-specific, so do tests which don't depend on the + # internals. + + # *any* two NaNs are not equivalent (even to itself) try: + # float('NaN') == float('NaN') + if o != o: + text = self.nan_str + elif o == _inf: + text = self.inf_str + elif o == _neginf: + text = self.neg_inf_str + else: + return _repr(o) + + if not allow_nan: + raise ValueError( + "Out of range float values are not JSON compliant: " + + repr(o)) + + return text + + _iterencode = json.encoder._make_iterencode( + markers, self.default, _encoder, self.indent, floatstr, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot) + return _iterencode(o, 0) + def default(self, obj): """ Accept an object (of unknown type) and try to encode with priority: diff --git a/plotly/version.py b/plotly/version.py index 970bb6f45e7..9a5e84e0a7a 100644 --- a/plotly/version.py +++ b/plotly/version.py @@ -1 +1 @@ -__version__ = '1.6.10' +__version__ = '1.6.11'