Repository: frictionlessdata/goodtables-py

Issue:
Ability to check table without headers?
(This would obviously not run checks related to headers.)

```
(pilot-dm4t) legend:refit-dirty dan (master)$ head archive/House1.csv 
1381323977,523,74,0,69,0,0,0,0,0,1
1381323990,526,75,0,69,0,0,0,0,0,1
1381324005,540,74,0,68,0,0,0,0,0,1
1381324020,532,74,0,68,0,0,0,0,0,1
1381324035,540,74,0,69,0,0,0,0,0,1
```

Adding a `"dialect": {"header": False}` to resource has no effect.

`datapackage.json`:

```
...
"resources": [
    {
      "name": "house_1.csv",
      "path": "archive/House1.csv",
      "dialect": {
        "header": false
      },
...
```

I would opt to just skip the header-related checks on command line, but can't see how (#175)

---

# Tasks

- [x] support inspection for tables without headers
- [x] support inspection for data package resources without headers (now it's possible with supported CSV dialect `header` option- https://github.com/frictionlessdata/datapackage-py/blob/master/tests/test_resource.py#L517-L528) - MOVED #174

Discussion:
roll: @brew 
Could you please review
roll: Thanks Brook!

Patch:
diff --git a/data/empty.csv b/data/empty.csv
new file mode 100644
index 0000000..e69de29
diff --git a/data/invalid_no_headers.csv b/data/invalid_no_headers.csv
new file mode 100644
index 0000000..090c676
--- /dev/null
+++ b/data/invalid_no_headers.csv
@@ -0,0 +1,3 @@
+1,english
+2,中国人
+3,german,extra
diff --git a/goodtables/inspector.py b/goodtables/inspector.py
index dce6a06..ca64eea 100644
--- a/goodtables/inspector.py
+++ b/goodtables/inspector.py
@@ -4,6 +4,7 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import six
 import inspect
 import datetime
 import operator
@@ -133,7 +134,7 @@ class Inspector(object):
         # Prepare vars
         errors = []
         warnings = []
-        headers = None
+        headers = []
         row_number = 0
         fatal_error = False
         checks = copy(self.__checks)
@@ -147,6 +148,8 @@ class Inspector(object):
             stream.open()
             sample = stream.sample
             headers = stream.headers
+            if headers is None:
+                headers = [None] * len(sample[0]) if sample else []
             if _filter_checks(checks, type='schema'):
                 if schema is None and self.__infer_schema:
                     schema = Schema(infer(headers, sample))
@@ -174,13 +177,14 @@ class Inspector(object):
 
         # Head checks
         if not fatal_error:
-            head_checks = _filter_checks(checks, context='head')
-            for check in head_checks:
-                if not columns:
-                    break
-                check['func'](errors, columns, sample)
-            for error in errors:
-                error['row'] = None
+            if None not in headers:
+                head_checks = _filter_checks(checks, context='head')
+                for check in head_checks:
+                    if not columns:
+                        break
+                    check['func'](errors, columns, sample)
+                for error in errors:
+                    error['row'] = None
 
         # Body checks
         if not fatal_error:
@@ -191,7 +195,7 @@ class Inspector(object):
                 extended_rows = stream.iter(extended=True)
                 while True:
                     try:
-                        row_number, headers, row = next(extended_rows)
+                        row_number, _, row = next(extended_rows)
                     except StopIteration:
                         break
                     except Exception as exception:
@@ -235,6 +239,7 @@ class Inspector(object):
         stop = datetime.datetime.now()
 
         # Compose report
+        headers = headers if None not in headers else None
         errors = errors[:self.__error_limit]
         errors = _sort_errors(errors)
         report = copy(extra)
@@ -319,10 +324,13 @@ def _prepare_checks(setup, custom, order_fields, infer_fields):
 
     # Bind options
     for check in checks:
-        args, _, _, _ = inspect.getargspec(check['func'])
-        if 'order_fields' in args:
+        if six.PY2:
+            parameters, _, _, _ = inspect.getargspec(check['func'])
+        else:
+            parameters = inspect.signature(check['func']).parameters
+        if 'order_fields' in parameters:
             check['func'] = partial(check['func'], order_fields=order_fields)
-        if 'infer_fields' in args:
+        if 'infer_fields' in parameters:
             check['func'] = partial(check['func'], infer_fields=infer_fields)
 
     return checks


Test patch:
diff --git a/tests/test_inspector.py b/tests/test_inspector.py
index 82d4626..aedb14a 100644
--- a/tests/test_inspector.py
+++ b/tests/test_inspector.py
@@ -4,6 +4,7 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import pytest
 from goodtables import Inspector
 
 
@@ -179,3 +180,22 @@ def test_inspector_warnings_table_and_error_limit():
     assert len(report['warnings']) == 2
     assert 'table(s) limit' in report['warnings'][0]
     assert 'error(s) limit' in report['warnings'][1]
+
+
+# Empty source
+
+def test_inspector_empty_source():
+    inspector = Inspector()
+    report = inspector.inspect('data/empty.csv')
+    assert report['tables'][0]['row-count'] == 0
+    assert report['tables'][0]['error-count'] == 0
+
+
+# No headers source
+
+def test_inspector_no_headers():
+    inspector = Inspector()
+    report = inspector.inspect('data/invalid_no_headers.csv', headers=None)
+    assert report['tables'][0]['row-count'] == 3
+    assert report['tables'][0]['error-count'] == 1
+    assert report['tables'][0]['errors'][0]['code'] == 'extra-value'