]> jspc29.x-matter.uni-frankfurt.de Git - mvd_epics.git/commitdiff
DASH: use simplejson for proper float('nan') -> null
authorPhilipp Klaus <klaus@physik.uni-frankfurt.de>
Mon, 28 Aug 2017 15:29:03 +0000 (17:29 +0200)
committerPhilipp Klaus <klaus@physik.uni-frankfurt.de>
Mon, 28 Aug 2017 15:29:03 +0000 (17:29 +0200)
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.

python_suite/dashboard/dashboard.py
python_suite/dashboard/requirements.txt

index 548fca92020d662ac2d67310de7f11026b8e5d1e..61d27e69ad26a30b18139e96293ce3fffbe185f6 100755 (executable)
@@ -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/<name>.json')
+@json_replace_nan()
 def api_history(name):
     try:
         history = copy.deepcopy(HISTORY[name])
index 7e1c8e24578ca1d6e51daf987cce675faf447356..39a718d899a6eea8f93b8abe2089f758db9d8dd0 100644 (file)
@@ -1,3 +1,4 @@
 bottle
 jinja2
 pyepics
+simplejson