Repository: blaze/blaze

Issue:
The .fields attribute should be empty on non-record datashapes
We currently have a default `fields` attribute even on datasets with a homogenous datashape 

``` python
In [1]: import blaze as bz

In [2]: bz.Data([1, 2, 3]).fields
Out[2]: ['_1']
```

This should probably just be `[]`

cc @fpliger 



Discussion:
+1 on empty list

I agree that this makes sense.  It's probably just a bunch of straightforward work to ensure that we're not relying on this behavior anywhere.


Resolving patch:
diff --git a/blaze/expr/expressions.py b/blaze/expr/expressions.py
--- a/blaze/expr/expressions.py
+++ b/blaze/expr/expressions.py
@@ -121,8 +121,10 @@ def schema(self):
     def fields(self):
         if isinstance(self.dshape.measure, Record):
             return self.dshape.measure.names
-        if hasattr(self, '_name'):
+        name = getattr(self, '_name', None)
+        if name is not None:
             return [self._name]
+        return []
 
     def _len(self):
         try:
@@ -256,7 +258,7 @@ def __init__(self, name, dshape, token=None):
         self._token = token
 
     def __str__(self):
-        return self._name
+        return self._name or ''
 
     def _resources(self):
         return dict()
diff --git a/blaze/interactive.py b/blaze/interactive.py
--- a/blaze/interactive.py
+++ b/blaze/interactive.py
@@ -2,8 +2,7 @@
 
 import datashape
 import datetime
-from datashape import (discover, Tuple, Record, dshape, Fixed, DataShape,
-    to_numpy_dtype, isdimension, var)
+from datashape import discover, Tuple, Record, DataShape, var
 from datashape.predicates import iscollection, isscalar, isrecord
 from pandas import DataFrame, Series
 import itertools
@@ -15,7 +14,7 @@
 from .expr import Expr, Symbol, ndim
 from .dispatch import dispatch
 from into import into, resource
-from .compatibility import _strtypes, unicode
+from .compatibility import _strtypes
 
 __all__ = ['Data', 'Table', 'into', 'to_html']
 
@@ -36,14 +35,14 @@
     pass
 
 
-def Data(data, dshape=None, name=None, fields=None, columns=None,
-         schema=None, **kwargs):
+def Data(data, dshape=None, name=None, fields=None, columns=None, schema=None,
+         **kwargs):
     sub_uri = ''
     if isinstance(data, _strtypes):
         if '::' in data:
             data, sub_uri = data.split('::')
-        data = resource(data, schema=schema, dshape=dshape,
-                              columns=columns, **kwargs)
+        data = resource(data, schema=schema, dshape=dshape, columns=columns,
+                        **kwargs)
     if (isinstance(data, Iterator) and
             not isinstance(data, tuple(not_an_iterator))):
         data = tuple(data)
@@ -54,7 +53,7 @@ def Data(data, dshape=None, name=None, fields=None, columns=None,
         fields = columns
     if schema and dshape:
         raise ValueError("Please specify one of schema= or dshape= keyword"
-                " arguments")
+                         " arguments")
     if schema and not dshape:
         dshape = var * schema
     if dshape and isinstance(dshape, _strtypes):
@@ -76,8 +75,6 @@ def Data(data, dshape=None, name=None, fields=None, columns=None,
             dshape = DataShape(*(dshape.shape + (schema,)))
 
     ds = datashape.dshape(dshape)
-
-    name = name or next(names)
     result = InteractiveSymbol(data, ds, name)
 
     if sub_uri:
@@ -127,7 +124,9 @@ class InteractiveSymbol(Symbol):
     def __init__(self, data, dshape, name=None):
         self.data = data
         self.dshape = dshape
-        self._name = name or next(names)
+        self._name = name or (next(names)
+                              if isrecord(dshape.measure)
+                              else None)
 
     def _resources(self):
         return {self: self.data}
@@ -192,7 +191,7 @@ def concrete_head(expr, n=10):
 
 
 def repr_tables(expr, n=10):
-    result = concrete_head(expr, n)
+    result = concrete_head(expr, n).rename(columns={None: ''})
 
     if isinstance(result, (DataFrame, Series)):
         s = repr(result)
@@ -201,7 +200,7 @@ def repr_tables(expr, n=10):
             s = '\n'.join(s.split('\n')[:-1]) + '\n...'
         return s
     else:
-        return repr(result) # pragma: no cover
+        return repr(result)  # pragma: no cover
 
 
 def numel(shape):
@@ -211,6 +210,7 @@ def numel(shape):
         return 1
     return reduce(lambda x, y: x * y, shape, 1)
 
+
 def short_dshape(ds, nlines=5):
     s = datashape.coretypes.pprint(ds)
     lines = s.split('\n')
@@ -241,9 +241,9 @@ def expr_repr(expr, n=10):
         elif 'bool' in str(expr.dshape):
             return repr(coerce_to(bool, result))
         elif 'datetime' in str(expr.dshape):
-            return repr(coerce_to(datetime, result))
+            return repr(coerce_to(datetime.datetime, result))
         elif 'date' in str(expr.dshape):
-            return repr(coerce_to(date, result))
+            return repr(coerce_to(datetime.date, result))
         else:
             return repr(compute(expr))
 
@@ -252,7 +252,7 @@ def expr_repr(expr, n=10):
         return repr_tables(expr, 10)
 
     # Smallish arrays
-    if ndim(expr) >= 2 and  numel(expr.shape) and numel(expr.shape) < 1000000:
+    if ndim(expr) >= 2 and numel(expr.shape) and numel(expr.shape) < 1000000:
         return repr(compute(expr))
 
     # Other
@@ -309,6 +309,7 @@ def table_length(expr):
 Expr._repr_html_ = lambda x: to_html(x)
 Expr.__len__ = table_length
 
+
 def intonumpy(data, dtype=None, **kwargs):
     # TODO: Don't ignore other kwargs like copy
     result = into(np.ndarray, data)


Test patch:
diff --git a/blaze/tests/test_interactive.py b/blaze/tests/test_interactive.py
--- a/blaze/tests/test_interactive.py
+++ b/blaze/tests/test_interactive.py
@@ -1,5 +1,6 @@
 from blaze.interactive import Data, compute, concrete_head, expr_repr, to_html
 
+import datetime
 from into import into, append
 from into.backends.csv import CSV
 from blaze import discover
@@ -332,3 +333,23 @@ def test_scalar_sql_compute():
             dshape=dshape('var * {name: string, amount: int}'))
     d = Data(t)
     assert repr(d.amount.sum()) == '300'
+
+
+def test_no_name_for_simple_data():
+    d = Data([1, 2, 3])
+    assert repr(d) == '    \n0  1\n1  2\n2  3'
+    assert not d._name
+
+    d = Data(1)
+    assert not d._name
+    assert repr(d) == '1'
+
+
+def test_coerce_date_and_datetime():
+    x = datetime.datetime.now().date()
+    d = Data(x)
+    assert repr(d) == repr(x)
+
+    x = datetime.datetime.now()
+    d = Data(x)
+    assert repr(d) == repr(x)