From: Philipp Klaus Date: Mon, 28 Aug 2017 15:29:03 +0000 (+0200) Subject: DASH: use simplejson for proper float('nan') -> null X-Git-Url: https://jspc29.x-matter.uni-frankfurt.de/git/?a=commitdiff_plain;h=5eebb6b89d0c067aa4a54ad2e1e3cbe179c95050;p=mvd_epics.git DASH: use simplejson for proper float('nan') -> null This fixes a regression introduced in 133fc27 where the pv_overview would not work anymore. Now the logic is: Anything in the local state of the server can contain float('nan'). When sent to the browser, those values will be replaced with the JS/JSON null type. --- diff --git a/python_suite/dashboard/dashboard.py b/python_suite/dashboard/dashboard.py index 548fca9..61d27e6 100755 --- a/python_suite/dashboard/dashboard.py +++ b/python_suite/dashboard/dashboard.py @@ -2,8 +2,9 @@ import json, threading, time, copy, math +import simplejson import epics -from bottle import route, run, static_file, redirect, abort +from bottle import route, run, static_file, redirect, abort, response, HTTPResponse from bottle import jinja2_view as view CONFIG = None @@ -94,8 +95,8 @@ def cb_value_update(**kwargs): pv['num_value'] = kwargs['value'] if kwargs['severity'] == epics.INVALID_ALARM: # avoid NaN (cannot be encoded in JSON) and outdated values if invalid - pv['value'] = None - pv['num_value'] = None + pv['value'] = float('nan') + pv['num_value'] = float('nan') register_pv_value_in_history(kwargs['pvname'], kwargs['timestamp'], pv['num_value']) pv['precision'] = kwargs['precision'] #if type(kwargs['precision']) == int and ('double' in kwargs['type'] or 'float' in kwargs['type']): @@ -107,6 +108,35 @@ def cb_value_update(**kwargs): if pv['unit'] == 'deg C': pv['unit'] = '°C' if pv['unit'] == 'g/m3': pv['unit'] = 'g/m³' +def json_replace_nan(): + """ + Custom JSON decorator for Bottle routes. + It converts a dictionary that you return to JSON before sending + it to the browser (just like Bottle would do, but replaces any + float('nan') value with a JSON-specs-compatible null. + Use like this: + @json_replace_nan() + before the def your_func() of a route(). + """ + def decorator(func): + def wrapper(*args, **kwargs): + try: + rv = func(*args, **kwargs) + except HTTPResponse as resp: + rv = resp + if isinstance(rv, dict): + #Attempt to serialize, raises exception on failure + json_response = simplejson.dumps(rv, ignore_nan=True) + #Set content type only if serialization successful + response.content_type = 'application/json' + return json_response + elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict): + rv.body = dumps(rv.body) + rv.content_type = 'application/json' + return rv + return wrapper + return decorator + @route('/') def index(): redirect('/list/general_overview') @@ -124,10 +154,12 @@ def gview(name): return {'config': CONFIG, 'svg': name} @route('/api/values.json') +@json_replace_nan() def api_values(): return CONFIG @route('/api/history/.json') +@json_replace_nan() def api_history(name): try: history = copy.deepcopy(HISTORY[name]) diff --git a/python_suite/dashboard/requirements.txt b/python_suite/dashboard/requirements.txt index 7e1c8e2..39a718d 100644 --- a/python_suite/dashboard/requirements.txt +++ b/python_suite/dashboard/requirements.txt @@ -1,3 +1,4 @@ bottle jinja2 pyepics +simplejson