Skip to content

Commit

Permalink
Athena: Switch to simple_json to serialize NaN/Infinity values as nulls.
Browse files Browse the repository at this point in the history
Fixes #2544.
  • Loading branch information
kravets-levko authored and arikfr committed Sep 4, 2018
1 parent d529a1d commit 53abc16
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 9 deletions.
5 changes: 3 additions & 2 deletions redash/query_runner/athena.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json
import logging
import os
import simplejson

from redash.query_runner import *
from redash.settings import parse_boolean
from redash.utils import JSONEncoder
from redash.utils import SimpleJSONEncoder

logger = logging.getLogger(__name__)
ANNOTATE_QUERY = parse_boolean(os.environ.get('ATHENA_ANNOTATE_QUERY', 'true'))
Expand Down Expand Up @@ -194,7 +195,7 @@ def run_query(self, query, user):
'athena_query_id': athena_query_id
}
}
json_data = json.dumps(data, cls=JSONEncoder)
json_data = simplejson.dumps(data, ignore_nan=True, cls=SimpleJSONEncoder)
error = None
except KeyboardInterrupt:
if cursor.query_id:
Expand Down
35 changes: 28 additions & 7 deletions redash/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import pytz
import pystache
import os
import simplejson

from funcy import distinct, select_values
from sqlalchemy.orm.query import Query
Expand Down Expand Up @@ -66,23 +67,43 @@ def generate_token(length):
return ''.join(rand.choice(chars) for x in range(length))


class JSONEncoder(json.JSONEncoder):
class JSONEncoderMixin:
"""Custom JSON encoding class, to handle Decimal and datetime.date instances."""

def default(self, o):
def process_default(self, o):
# Some SQLAlchemy collections are lazy.
if isinstance(o, Query):
return list(o)
return True, list(o)
if isinstance(o, decimal.Decimal):
return float(o)
return True, float(o)

if isinstance(o, (datetime.date, datetime.time)):
return o.isoformat()
return True, o.isoformat()

if isinstance(o, datetime.timedelta):
return str(o)
return True, str(o)

return False, None # default processing


super(JSONEncoder, self).default(o)
class JSONEncoder(JSONEncoderMixin, json.JSONEncoder):
"""Adapter for `json.dumps`."""

def default(self, o):
processed, result = self.process_default(o)
if not processed:
result = super(JSONEncoder, self).default(o)
return result


class SimpleJSONEncoder(JSONEncoderMixin, simplejson.JSONEncoder):
"""Adapter for `simplejson.dumps`."""

def default(self, o):
processed, result = self.process_default(o)
if not processed:
super(SimpleJSONEncoder, self).default(o)
return result


def json_dumps(data):
Expand Down

0 comments on commit 53abc16

Please sign in to comment.