From 6747a888bcc6550bad248191948784ea68f1acb1 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 5 Dec 2016 13:17:25 -0500 Subject: [PATCH] copied PlotlyJSONEncode class to play around --- plotly/utils.py | 191 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 1 deletion(-) diff --git a/plotly/utils.py b/plotly/utils.py index 841d9a9d305..393a197aed7 100644 --- a/plotly/utils.py +++ b/plotly/utils.py @@ -118,7 +118,7 @@ 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 + 'NaN' and '-Inf' encode to 'null', which is stricter JSON than the Python version. """ @@ -140,7 +140,196 @@ def encode(self, o): Note that setting invalid separators will cause a failure at this step. """ + encoded_o = + # this will raise errors in a normal-expected way + encoded_o = super(PlotlyJSONEncoder, self).encode(o) + print encoded_o + + # now: + # 1. `loads` to switch Infinity, -Infinity, NaN to None + # 2. `dumps` again so you get 'null' instead of extended JSON + try: + new_o = json.loads(encoded_o, parse_constant=self.coerce_to_strict) + print new_o + except ValueError: + + # invalid separators will fail here. raise a helpful exception + raise ValueError( + "Encoding into strict JSON failed. Did you set the separators " + "valid JSON separators?" + ) + else: + print 'else' + return json.dumps(new_o, sort_keys=self.sort_keys, + indent=self.indent, + separators=(self.item_separator, + self.key_separator)) + + def default(self, obj): + """ + Accept an object (of unknown type) and try to encode with priority: + 1. builtin: user-defined objects + 2. sage: sage math cloud + 3. pandas: dataframes/series + 4. numpy: ndarrays + 5. datetime: time/datetime objects + + Each method throws a NotEncoded exception if it fails. + + The default method will only get hit if the object is not a type that + is naturally encoded by json: + + Normal objects: + dict object + list, tuple array + str, unicode string + int, long, float number + True true + False false + None null + + Extended objects: + float('nan') 'NaN' + float('infinity') 'Infinity' + float('-infinity') '-Infinity' + + Therefore, we only anticipate either unknown iterables or values here. + + """ + # TODO: The ordering if these methods is *very* important. Is this OK? + encoding_methods = ( + self.encode_as_plotly, + self.encode_as_sage, + self.encode_as_numpy, + self.encode_as_pandas, + self.encode_as_datetime, + self.encode_as_date, + self.encode_as_list # because some values have `tolist` do last. + ) + for encoding_method in encoding_methods: + try: + return encoding_method(obj) + except NotEncodable: + pass + return json.JSONEncoder.default(self, obj) + + @staticmethod + def encode_as_plotly(obj): + """Attempt to use a builtin `to_plotly_json` method.""" + try: + return obj.to_plotly_json() + except AttributeError: + raise NotEncodable + + @staticmethod + def encode_as_list(obj): + """Attempt to use `tolist` method to convert to normal Python list.""" + if hasattr(obj, 'tolist'): + return obj.tolist() + else: + raise NotEncodable + + @staticmethod + def encode_as_sage(obj): + """Attempt to convert sage.all.RR to floats and sage.all.ZZ to ints""" + if not _sage_imported: + raise NotEncodable + + if obj in sage.all.RR: + return float(obj) + elif obj in sage.all.ZZ: + return int(obj) + else: + raise NotEncodable + + @staticmethod + def encode_as_pandas(obj): + """Attempt to convert pandas.NaT""" + if not _pandas_imported: + raise NotEncodable + + if obj is pandas.NaT: + return None + else: + raise NotEncodable + + @staticmethod + def encode_as_numpy(obj): + """Attempt to convert numpy.ma.core.masked""" + if not _numpy_imported: + raise NotEncodable + + if obj is numpy.ma.core.masked: + return float('nan') + else: + raise NotEncodable + + @staticmethod + def encode_as_datetime(obj): + """Attempt to convert to utc-iso time string using datetime methods.""" + + # first we need to get this into utc + try: + obj = obj.astimezone(pytz.utc) + except ValueError: + # we'll get a value error if trying to convert with naive datetime + pass + except TypeError: + # pandas throws a typeerror here instead of a value error, it's OK + pass + except AttributeError: + # we'll get an attribute error if astimezone DNE + raise NotEncodable + + # now we need to get a nicely formatted time string + try: + time_string = obj.isoformat() + except AttributeError: + raise NotEncodable + else: + return iso_to_plotly_time_string(time_string) + + @staticmethod + def encode_as_date(obj): + """Attempt to convert to utc-iso time string using date methods.""" + try: + time_string = obj.isoformat() + except AttributeError: + raise NotEncodable + else: + return iso_to_plotly_time_string(time_string) + + +class OLDPlotlyJSONEncoder(json.JSONEncoder): + """ + Meant to be passed as the `cls` kwarg to json.dumps(obj, cls=..) + + 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. + + """ + def coerce_to_strict(self, const): + """ + This is used to ultimately *encode* into strict JSON, see `encode` + + """ + # before python 2.7, 'true', 'false', 'null', were include here. + if const in ('Infinity', '-Infinity', 'NaN'): + return None + else: + return const + + def encode(self, o): + """ + Load and then dump the result using parse_constant kwarg + + Note that setting invalid separators will cause a failure at this step. + + """ # this will raise errors in a normal-expected way encoded_o = super(PlotlyJSONEncoder, self).encode(o)