Repository: wireservice/csvkit
Issue: csvlook 1.0 unable to read from stdin, raises error "underlying stream is not seekable"

In csvkit 0.9.1, you could do this:

``` sh
$ csvcut -c 1,2,3 examples/iris.csv | csvlook
|---------------+-------------+---------------|
|  sepal_length | sepal_width | petal_length  |
|---------------+-------------+---------------|
|  5.1          | 3.5         | 1.4           |
|  4.9          | 3.0         | 1.4           |
|  4.7          | 3.2         | 1.3           |
|  4.6          | 3.1         | 1.5           |
```

In 1.0, this error is raised: `underlying stream is not seekable`. This seems like it would be expected behavior for `csvsort`, but doesn't seem necessary for `csvlook`:

https://github.com/wireservice/csvkit/blob/master/csvkit/utilities/csvsort.py#L31

(actually, is it necessary for csvsort? Doesn't csvstat essentially require the entire input to be read before it can do its aggregation? I guess that can be saved for another issue/feature request)


Changed files:
csvkit/utilities/csvsort.py, csvkit/utilities/csvlook.py, csvkit/utilities/in2csv.py, csvkit/cli.py, csvkit/utilities/csvjson.py

diff --git a/csvkit/cli.py b/csvkit/cli.py
index a28a3e2..37f8c04 100644
--- a/csvkit/cli.py
+++ b/csvkit/cli.py
@@ -65,6 +65,7 @@ class CSVKitUtility(object):
     description = ''
     epilog = ''
     override_flags = ''
+    buffers_input = False
 
     def __init__(self, args=None, output_file=None):
         """
@@ -187,7 +188,10 @@ class CSVKitUtility(object):
             kwargs = {'encoding': self.args.encoding}
 
         if not path or path == '-':
-            f = sys.stdin
+            if self.buffers_input:
+                f = six.StringIO(sys.stdin.read())
+            else:
+                f = sys.stdin
         else:
             (_, extension) = os.path.splitext(path)
 
diff --git a/csvkit/utilities/csvjson.py b/csvkit/utilities/csvjson.py
index 012496f..f84e640 100644
--- a/csvkit/utilities/csvjson.py
+++ b/csvkit/utilities/csvjson.py
@@ -19,6 +19,7 @@ from csvkit.cli import CSVKitUtility, match_column_identifier
 class CSVJSON(CSVKitUtility):
     description = 'Convert a CSV file into JSON (or GeoJSON).'
     override_flags = ['H']
+    buffers_input = True
 
     def add_arguments(self):
         self.argparser.add_argument('-i', '--indent', dest='indent', type=int, default=None,
diff --git a/csvkit/utilities/csvlook.py b/csvkit/utilities/csvlook.py
index 317eb13..3e22da4 100644
--- a/csvkit/utilities/csvlook.py
+++ b/csvkit/utilities/csvlook.py
@@ -7,6 +7,7 @@ from csvkit.cli import CSVKitUtility
 
 class CSVLook(CSVKitUtility):
     description = 'Render a CSV file in the console as a fixed-width table.'
+    buffers_input = True
 
     def add_arguments(self):
         self.argparser.add_argument('--max-rows', dest='max_rows', type=int,
diff --git a/csvkit/utilities/csvsort.py b/csvkit/utilities/csvsort.py
index afe439a..b3af78f 100644
--- a/csvkit/utilities/csvsort.py
+++ b/csvkit/utilities/csvsort.py
@@ -1,15 +1,13 @@
 #!/usr/bin/env python
 
-import sys
-
 import agate
-import six
 
 from csvkit.cli import CSVKitUtility, parse_column_identifiers
 
 
 class CSVSort(CSVKitUtility):
     description = 'Sort CSV files. Like the Unix "sort" command, but for tabular data.'
+    buffers_input = True
 
     def add_arguments(self):
         self.argparser.add_argument('-n', '--names', dest='names_only', action='store_true',
@@ -28,11 +26,6 @@ class CSVSort(CSVKitUtility):
             self.print_column_names()
             return
 
-        # Otherwise, fails with "io.UnsupportedOperation: underlying stream is not seekable".
-        if self.input_file == sys.stdin:
-            # We can't sort without reading the entire input.
-            self.input_file = six.StringIO(sys.stdin.read())
-
         table = agate.Table.from_csv(self.input_file, sniff_limit=self.args.sniff_limit, header=not self.args.no_header_row, column_types=self.get_column_types(), **self.reader_kwargs)
         column_ids = parse_column_identifiers(self.args.columns, table.column_names, column_offset=self.get_column_offset())
         table = table.order_by(column_ids, reverse=self.args.reverse)
diff --git a/csvkit/utilities/in2csv.py b/csvkit/utilities/in2csv.py
index 2a43510..4c6125a 100644
--- a/csvkit/utilities/in2csv.py
+++ b/csvkit/utilities/in2csv.py
@@ -53,6 +53,8 @@ class In2CSV(CSVKitUtility):
             if not filetype:
                 self.argparser.error('Unable to automatically determine the format of the input file. Try specifying a format with --format.')
 
+        self.buffers_input = filetype == 'csv' or not self.args.no_inference
+
         # Set the input file.
         if filetype in ('xls', 'xlsx'):
             self.input_file = open(self.args.input_path, 'rb')