Repository: altair-viz/altair
Pull request #302 (2017-02-23 21:42:53)

BUG: fix serialization of boolean types


Patch:
diff --git a/altair/expr/core.py b/altair/expr/core.py
index c7946291..92329933 100644
--- a/altair/expr/core.py
+++ b/altair/expr/core.py
@@ -8,6 +8,18 @@ from ..utils._py3k_compat import string_types
 import pandas as pd
 
 
+def js_repr(val):
+    """Return a javascript-safe string representation of val"""
+    if val is True:
+        return 'true'
+    elif val is False:
+        return 'false'
+    elif val is None:
+        return 'null'
+    else:
+        return repr(val)
+
+
 class Expression(object):
     """Expression
 
@@ -121,7 +133,7 @@ class UnaryExpression(Expression):
         self.val = val
 
     def __repr__(self):
-        return "({op}{val})".format(op=self.op, val=repr(self.val))
+        return "({op}{val})".format(op=self.op, val=js_repr(self.val))
 
 
 class BinaryExpression(Expression):
@@ -132,8 +144,8 @@ class BinaryExpression(Expression):
 
     def __repr__(self):
         return "({lhs}{op}{rhs})".format(op=self.op,
-                                         lhs=repr(self.lhs),
-                                         rhs=repr(self.rhs))
+                                         lhs=js_repr(self.lhs),
+                                         rhs=js_repr(self.rhs))
 
 
 class FunctionExpression(Expression):
@@ -142,7 +154,7 @@ class FunctionExpression(Expression):
         self.args = args
 
     def __repr__(self):
-        args = ','.join(repr(arg) for arg in self.args)
+        args = ','.join(js_repr(arg) for arg in self.args)
         return "{name}({args})".format(name=self.name, args=args)
 
 
diff --git a/altair/utils/core.py b/altair/utils/core.py
index 8aec51fc..6eb9fefd 100644
--- a/altair/utils/core.py
+++ b/altair/utils/core.py
@@ -131,6 +131,7 @@ def sanitize_dataframe(df):
     * Make a copy
     * Raise ValueError if it has a hierarchical index.
     * Convert categoricals to strings.
+    * Convert np.bool_ dtypes to Python bool objects
     * Convert np.int dtypes to Python int objects
     * Convert floats to objects and replace NaNs by None.
     * Convert DateTime dtypes into appropriate string representations
@@ -153,6 +154,9 @@ def sanitize_dataframe(df):
             # XXXX: work around bug in to_json for categorical types
             # https://github.com/pydata/pandas/issues/10778
             df[col_name] = df[col_name].astype(str)
+        elif str(dtype) == 'bool':
+            # convert numpy bools to objects; np.bool is not JSON serializable
+            df[col_name] = df[col_name].astype(object)
         elif np.issubdtype(dtype, np.integer):
             # convert integers to objects; np.int is not JSON serializable
             df[col_name] = df[col_name].astype(object)


Test patch:
diff --git a/altair/expr/tests/test_expr.py b/altair/expr/tests/test_expr.py
index bc4059ba..2c3b981b 100644
--- a/altair/expr/tests/test_expr.py
+++ b/altair/expr/tests/test_expr.py
@@ -115,3 +115,11 @@ def test_getitem_list(data):
     df2['qqq'] = df2.xxx / df2.yyy
     assert set(dir(df2)) == {'xxx', 'yyy', 'calculated', 'qqq'}
     assert set(dir(df)) == {'xxx', 'yyy', 'zzz', 'calculated'}
+
+
+def test_json_reprs(data):
+    df = expr.DataFrame(data)
+
+    assert repr(df.xxx == None) == '(datum.xxx==null)'
+    assert repr(df.xxx == False) == '(datum.xxx==false)'
+    assert repr(df.xxx == True) == '(datum.xxx==true)'
diff --git a/altair/tests/test_api.py b/altair/tests/test_api.py
index 8f32cdcd..76376b04 100644
--- a/altair/tests/test_api.py
+++ b/altair/tests/test_api.py
@@ -540,13 +540,3 @@ def test_chart_dir():
     assert 'mark' in chart
     assert 'layers' in layerchart
     assert 'facet' in facetchart
-
-
-def test_empty_traits():
-    # regression test for #264
-    axis = Axis(title='')
-    assert axis.to_dict() == {'title': ''}
-
-    # regression test for changes in #265
-    assert Transform().to_dict() == {}  # filter not present
-    assert Formula('blah').to_dict() == {'field': 'blah'}  # expr not present
diff --git a/altair/utils/tests/test_utils.py b/altair/utils/tests/test_utils.py
index 95942428..3d0f4234 100644
--- a/altair/utils/tests/test_utils.py
+++ b/altair/utils/tests/test_utils.py
@@ -84,6 +84,7 @@ def test_sanitize_dataframe():
     df = pd.DataFrame({'s': list('abcde'),
                        'f': np.arange(5, dtype=float),
                        'i': np.arange(5, dtype=int),
+                       'b': np.array([True, False, True, True, False]),
                        'd': pd.date_range('2012-01-01', periods=5, freq='H'),
                        'c': pd.Series(list('ababc'), dtype='category'),
                        'o': pd.Series([np.array(i) for i in range(5)])})