Repository: blaze/blaze

Issue:
Move datetime logic from into to csv.reader
``` Python
In [1]: from blaze import *

In [2]: csv = CSV('blaze/api/tests/accounts.csv')

In [3]: into(DataFrame, csv).dtypes
Out[3]: 
amount                int64
id                    int64
name                 object
timestamp    datetime64[ns]
dtype: object

In [4]: csv.reader().dtypes
Out[4]: 
0     int64
1     int64
2    object
3    object
dtype: object
```



Discussion:

Resolving patch:
diff --git a/blaze/api/into.py b/blaze/api/into.py
--- a/blaze/api/into.py
+++ b/blaze/api/into.py
@@ -855,42 +855,11 @@ def into(a, b, **kwargs):
 
 @dispatch(pd.DataFrame, CSV)
 def into(a, b, **kwargs):
-    dialect = b.dialect.copy()
-    del dialect['lineterminator']
-
-    schema = b.schema
-    if '?' in str(schema):
-        schema = dshape(str(schema).replace('?', ''))
-
-    dtypes = valmap(to_numpy_dtype, schema[0].dict)
-
-    datenames = [name for name in dtypes if np.issubdtype(dtypes[name],
-                                                          np.datetime64)]
-
-    dtypes = dict((k, v) for k, v in dtypes.items()
-                  if not np.issubdtype(v, np.datetime64))
-
-    if 'strict' in dialect:
-        del dialect['strict']
-
     # Pass only keyword arguments appropriate for read_csv
     kws = keywords(pd.read_csv)
-    options = toolz.merge(dialect, kwargs)
-    options = toolz.keyfilter(lambda k: k in kws, options)
-
-    if b.open == gzip.open:
-        options['compression'] = 'gzip'
-
-    names = options.pop('names', b.columns)
-    usecols = options.pop('usecols', [b.columns.index(name) for name in names])
-    return pd.read_csv(b.path,
-                       header=0 if b.header else None,
-                       dtype=dtypes,
-                       parse_dates=datenames,
-                       names=names,
-                       usecols=usecols,
-                       encoding=b.encoding,
-                       **options)
+    options = toolz.merge(b.dialect, kwargs)
+    options = toolz.keyfilter(kws.__contains__, options)
+    return b.reader(**options)
 
 
 @dispatch((np.ndarray, pd.DataFrame, ColumnDataSource, ctable, tb.Table, list,
diff --git a/blaze/data/csv.py b/blaze/data/csv.py
--- a/blaze/data/csv.py
+++ b/blaze/data/csv.py
@@ -271,11 +271,10 @@ def __init__(self, path, mode='rt', schema=None, columns=None, types=None,
         reader_dialect = keyfilter(read_csv_kwargs.__contains__, dialect)
         if not schema and 'w' not in mode:
             if not types:
-                data = self.reader(skiprows=1 if header else 0,
-                                   nrows=nrows_discovery,
-                                   as_recarray=True, index_col=False,
-                                   header=0 if header else None,
-                                   **reader_dialect).tolist()
+                data = self._reader(skiprows=1 if header else 0,
+                                    nrows=nrows_discovery, as_recarray=True,
+                                    index_col=False, header=0 if header else
+                                    None, **reader_dialect).tolist()
                 types = discover(data)
                 rowtype = types.subshape[0]
                 if isinstance(rowtype[0], Tuple):
@@ -292,9 +291,9 @@ def __init__(self, path, mode='rt', schema=None, columns=None, types=None,
                                      "Please specify schema.")
             if not columns:
                 if header:
-                    columns = first(self.reader(skiprows=0, nrows=1,
-                                                header=None, **reader_dialect
-                                                ).itertuples(index=False))
+                    columns = first(self._reader(skiprows=0, nrows=1,
+                                                 header=None, **reader_dialect
+                                                 ).itertuples(index=False))
                 else:
                     columns = ['_%d' % i for i in range(len(types))]
             if typehints:
@@ -305,47 +304,58 @@ def __init__(self, path, mode='rt', schema=None, columns=None, types=None,
         self._schema = schema
         self.header = header
 
-    def _clean_params(self, header=None, keep_default_na=False,
-                      na_values=na_values, chunksize=None, **kwargs):
+    def _get_reader(self, header=None, keep_default_na=False,
+                    na_values=na_values, chunksize=None, **kwargs):
         kwargs.setdefault('skiprows', int(bool(self.header)))
 
         dialect = merge(keyfilter(read_csv_kwargs.__contains__, self.dialect),
                         kwargs)
+
         # handle windows
         if dialect['lineterminator'] == '\r\n':
             dialect['lineterminator'] = None
-        return dialect
-
-    def reader(self, header=None, keep_default_na=False,
-               na_values=na_values, **kwargs):
-        chunksize = kwargs.pop('chunksize', None)
-        assert chunksize is None, ('reader is for in memory only, '
-                                   'use iterreader to read chunks')
-        dialect = self._clean_params(header=header,
-                                     keep_default_na=keep_default_na,
-                                     na_values=na_values, **kwargs)
-        with csvopen(self) as f:
-            return pd.read_csv(f, chunksize=chunksize,
-                               na_values=na_values,
-                               keep_default_na=keep_default_na,
-                               encoding=self.encoding, header=header, **dialect)
+        return partial(pd.read_csv, chunksize=chunksize, na_values=na_values,
+                       keep_default_na=keep_default_na, encoding=self.encoding,
+                       header=header, **dialect)
+
+    def reader(self, *args, **kwargs):
+        names = kwargs.pop('names', self.columns)
+        usecols = kwargs.pop('usecols', [self.columns.index(name) for name in
+                                         names])
+
+        schema = self.schema
+        if '?' in str(schema):
+            schema = dshape(str(schema).replace('?', ''))
+
+        dtypes = dict((k, v.to_numpy_dtype())
+                      for k, v in schema[0].dict.items())
 
-    def iterreader(self, header=None, keep_default_na=False,
-                   na_values=na_values, **kwargs):
-        chunksize = kwargs.pop('chunksize', self.chunksize)
-        dialect = self._clean_params(header=header,
-                                     keep_default_na=keep_default_na,
-                                     na_values=na_values, **kwargs)
-        assert chunksize is not None, ('iterreader is for chunking only, '
-                                       'for in memory reading use reader()')
+        datenames = [name for name in dtypes if np.issubdtype(dtypes[name],
+                                                              np.datetime64)]
+
+        dtypes = dict((k, v) for k, v in dtypes.items()
+                      if not np.issubdtype(v, np.datetime64))
+
+        return self._reader(*args, names=names, usecols=usecols, dtype=dtypes,
+                            parse_dates=datenames,
+                            **kwargs)
+
+    def _reader(self, **kwargs):
+        if kwargs.setdefault('chunksize', None) is not None:
+            raise ValueError('reader is for in memory only, use '
+                             'CSV.iterreader() to read chunks')
+        reader = self._get_reader(**kwargs)
+        with csvopen(self) as f:
+            return reader(f)
 
+    def iterreader(self, **kwargs):
+        if kwargs.setdefault('chunksize', self.chunksize) is None:
+            raise ValueError('iterreader is for chunking only, for in memory '
+                             'reading use CSV.reader()')
+        reader = self._get_reader(**kwargs)
 
         with csvopen(self) as f:
-            for chunk in pd.read_csv(f, chunksize=chunksize,
-                                     na_values=na_values,
-                                     keep_default_na=keep_default_na,
-                                     encoding=self.encoding, header=header,
-                                     **dialect):
+            for chunk in reader(f):
                 yield chunk
 
     def get_py(self, key):


Test patch:
diff --git a/blaze/api/tests/test_into.py b/blaze/api/tests/test_into.py
--- a/blaze/api/tests/test_into.py
+++ b/blaze/api/tests/test_into.py
@@ -392,3 +392,20 @@ def test_data_frame_single_column_projection():
     assert isinstance(df, pd.DataFrame)
     expected = pd.DataFrame(data, columns=t.schema.measure.names)[['name']]
     assert str(df) == str(expected)
+
+
+def test_datetime_csv_reader_same_as_into():
+    csv = CSV(os.path.join(os.path.dirname(__file__),
+                           'accounts.csv'))
+    rhs = csv.reader().dtypes
+    df = into(pd.DataFrame, csv)
+    dtypes = df.dtypes
+    expected = pd.Series([np.dtype(x) for x in
+                          ['i8', 'i8', 'O', 'datetime64[ns]']],
+                         index=csv.columns)
+    assert dtypes.index.tolist() == expected.index.tolist()
+    assert dtypes.tolist() == expected.tolist()
+
+    # make sure reader with no args does the same thing as into()
+    assert dtypes.index.tolist() == rhs.index.tolist()
+    assert dtypes.tolist() == rhs.tolist()