Repository: frictionlessdata/tabulator-py
Pull request #204 (2017-10-04 13:11:42)

Added support for pathlib.Path as a source
- connects https://github.com/frictionlessdata/goodtables-py/issues/222

roll: @rgieseke
Please take a look. Source argument is passed to `tabulator` from `goodtables` so I'm quick fixing it here. Is it correct use case:

```python
pathlib = import_module('pathlib')
with Stream(pathlib.Path('data/table.csv')) as stream:
   assert stream.read() == [['id', 'name'], ['1', 'english'], ['2', '中国人']]
```
rgieseke: Many thanks for the quick reply!

After re-installing `tabulator-py`s pathlib-support branch I can run it with a `pathlib` path but still get an error:

```
{'error-count': 1,
 'preset': 'table',
 'table-count': 1,
 'tables': [{'encoding': None,
   'error-count': 1,
   'errors': [{'code': 'source-error',
     'column-number': None,
     'message': "'list' object has no attribute 'items'",
     'row': None,
     'row-number': None}],
   'format': None,
   'headers': [],
   'row-count': 0,
   'schema': None,
   'scheme': None,
   'source': 'inline',
   'time': 0.033,
   'valid': False}],
 'time': 0.035,
 'valid': False,
 'warnings': []}
```

With a string path I get a full check.

Here is what Pandas is using btw.: https://github.com/quintusdias/pandas_dev/blob/master/pandas/io/common.py#L271

Not sure if one needs to support LocalPath as well, haven't used it yet. 
roll: @rgieseke 
Oh :smiley: Seem I've got requirements wrong. I'm going to fix tomorrow.
roll: @rgieseke 
Could you please check what kind of object goes go `validate(source)` as a source? Based on this error it's list not `pathlib.Path`. Could you past error traceback?
rgieseke: I get 

```py
In [3]: from pathlib import Path

In [4]: p = Path("./datapackage.json")

In [5]: validate(p)
Out[5]: 
{'error-count': 1,
 'preset': 'table',
 'table-count': 1,
 'tables': [{'encoding': None,
   'error-count': 1,
   'errors': [{'code': 'source-error',
     'column-number': None,
     'message': "'list' object has no attribute 'items'",
     'row': None,
     'row-number': None}],
   'format': None,
   'headers': [],
   'row-count': 0,
   'schema': None,
   'scheme': None,
   'source': 'inline',
   'time': 0.027,
   'valid': False}],
 'time': 0.028,
 'valid': False,
 'warnings': []}
```

vs. a correct output with

```py
In [2]: validate("datapackage.json")
```

So there is no traceback.

I uninstalled `tabulator` and installed with
```
pip install git+https://github.com/frictionlessdata/tabulator-py.git@pathlib-support
```
rgieseke: Could it be that the problem is `tabulator` trying to read a "datapackage.json"?

```
In [7]: from pathlib import Path

In [8]: p = Path("./datapackage.json")

In [9]: s = tabulator.Stream(p)

In [10]: s.open()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-4a461b81b3b3> in <module>()
----> 1 s.open()

~/venv/lib/python3.6/site-packages/tabulator/stream.py in open(self)
    156         # Open and setup
    157         self.__parser.open(self.__source, encoding=self.__encoding)
--> 158         self.__extract_sample()
    159         self.__extract_headers()
    160         if not self.__allow_html:

~/venv/lib/python3.6/site-packages/tabulator/stream.py in __extract_sample(self)
    285             for _ in range(self.__sample_size):
    286                 try:
--> 287                     row_number, headers, row = next(self.__parser.extended_rows)
    288                     self.__sample_extended_rows.append((row_number, headers, row))
    289                 except StopIteration:

~/venv/lib/python3.6/site-packages/tabulator/parsers/datapackage.py in __iter_extended_rows(self)
     74     def __iter_extended_rows(self):
     75         for number, row in enumerate(self.__resource_iter, start=1):
---> 76             keys, values = zip(*sorted(row.items()))
     77             yield number, list(keys), list(values)

AttributeError: 'list' object has no attribute 'items'
```

Same error message.
roll: @rgieseke 
Thanks! It explain everything)

So this PR solves it for:

```python
validate('data.csv')
```

But I need another one to support:

```python
validate('datapackage.json')
```

Patch:
diff --git a/.travis.yml b/.travis.yml
index 7467a69..ed14160 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,7 +15,7 @@ language:
 python:
   - 2.7
   - 3.3
-  - 3.4
+  # - 3.4
   - 3.5
   - 3.6
 
diff --git a/README.md b/README.md
index 04c99e2..14e198c 100644
--- a/README.md
+++ b/README.md
@@ -218,10 +218,11 @@ There is a list of all supported schemes.
 
 #### file
 
-The default scheme. Source should be a file in local filesystem.
+The default scheme. Source should be a file in local filesystem. You could provide a string or a `pathlib.Path` instance:
 
 ```python
 stream = Stream('data.csv')
+stream = Stream(pathlib.Path('data.csv'))
 ```
 
 #### http/https/ftp/ftps
diff --git a/tabulator/stream.py b/tabulator/stream.py
index 25f48e7..c06d094 100644
--- a/tabulator/stream.py
+++ b/tabulator/stream.py
@@ -55,6 +55,10 @@ class Stream(object):
             else:
                 self.__skip_rows_by_comments.append(str(directive))
 
+        # Support for pathlib.Path
+        if hasattr(source, 'joinpath'):
+            source = str(source)
+
         # Set attributes
         self.__source = source
         self.__scheme = scheme


Test patch:
diff --git a/tests/schemes/test_local.py b/tests/schemes/test_local.py
index fadc515..b2f228f 100644
--- a/tests/schemes/test_local.py
+++ b/tests/schemes/test_local.py
@@ -4,7 +4,10 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import sys
+import pytest
 from tabulator import Stream
+from importlib import import_module
 from tabulator.loaders.local import LocalLoader
 
 
@@ -15,6 +18,13 @@ def test_stream_file():
         assert stream.read() == [['id', 'name'], ['1', 'english'], ['2', '中国人']]
 
 
+@pytest.mark.skipif(sys.version_info < (3, 4), reason='not supported')
+def test_stream_file_pathlib_path():
+    pathlib = import_module('pathlib')
+    with Stream(pathlib.Path('data/table.csv')) as stream:
+        assert stream.read() == [['id', 'name'], ['1', 'english'], ['2', '中国人']]
+
+
 # Loader
 
 def test_loader_local_t():