Repository: jazzband/tablib
Pull request #560 (2023-09-05 13:40:17)

feat: `transpose` support dataset with no headers
`data.transpose()` raises `TypeError: 'NoneType' object is not subscriptable`, when `headers` is not set.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/jazzband/tablib/pull/560?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) Report
> Merging [#560](https://app.codecov.io/gh/jazzband/tablib/pull/560?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) (6cf0aab) into [master](https://app.codecov.io/gh/jazzband/tablib/commit/98b8c533cb679743a0b7f5f49f43fbd6970b544b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) (98b8c53) will **increase** coverage by `0.10%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #560      +/-   ##
==========================================
+ Coverage   91.64%   91.74%   +0.10%     
==========================================
  Files          28       28              
  Lines        2800     2823      +23     
==========================================
+ Hits         2566     2590      +24     
+ Misses        234      233       -1     
```


| [Files Changed](https://app.codecov.io/gh/jazzband/tablib/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband) | Coverage Δ | |
|---|---|---|
| [src/tablib/core.py](https://app.codecov.io/gh/jazzband/tablib/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband#diff-c3JjL3RhYmxpYi9jb3JlLnB5) | `85.71% <100.00%> (+0.56%)` | :arrow_up: |
| [tests/test\_tablib.py](https://app.codecov.io/gh/jazzband/tablib/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband#diff-dGVzdHMvdGVzdF90YWJsaWIucHk=) | `98.79% <100.00%> (+0.01%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jazzband)


Patch:
diff --git a/src/tablib/core.py b/src/tablib/core.py
index 252da78..a7ebff2 100644
--- a/src/tablib/core.py
+++ b/src/tablib/core.py
@@ -688,15 +688,11 @@ class Dataset:
 
         return _dset
 
-    def transpose(self):
+    def _transpose_with_headers(self):
         """Transpose a :class:`Dataset`, turning rows into columns and vice
         versa, returning a new ``Dataset`` instance. The first row of the
         original instance becomes the new header row."""
 
-        # Don't transpose if there is no data
-        if not self:
-            return
-
         _dset = Dataset()
         # The first element of the headers stays in the headers,
         # it is our "hinge" on which we rotate the data
@@ -716,6 +712,35 @@ class Dataset:
             _dset.append(row=row_data)
         return _dset
 
+    def _transpose_without_headers(self):
+        """Transpose a :class:`Dataset`, turning rows into columns and vice
+        versa, returning a new ``Dataset`` instance. This instance should not
+        have headers, or the dimension would be invalid."""
+
+        _dset = Dataset()
+
+        # Add columns as rows in new instance
+        for index in range(0, len(self._data[0])):
+            row_data = self.get_col(index)
+            _dset.append(row=row_data)
+
+        return _dset
+
+    def transpose(self):
+        """Transpose a :class:`Dataset`, turning rows into columns and vice
+        versa, returning a new ``Dataset`` instance. If the instance has
+        headers, the first row of the original instance becomes the new header
+        row."""
+
+        # Don't transpose if there is no data
+        if not self:
+            return
+
+        if self.headers is None:
+            return self._transpose_without_headers()
+        else:
+            return self._transpose_with_headers()
+
     def stack(self, other):
         """Stack two :class:`Dataset` instances together by
         joining at the row level, and return new combined


Test patch:
diff --git a/tests/test_tablib.py b/tests/test_tablib.py
index 03ac58b..9830d78 100755
--- a/tests/test_tablib.py
+++ b/tests/test_tablib.py
@@ -402,6 +402,21 @@ class TablibTestCase(BaseTestCase):
         self.assertEqual(second_row,
                          ("gpa", 90, 67, 50))
 
+    def test_transpose_empty_dataset(self):
+        data = tablib.Dataset()
+        self.assertEqual(data.transpose(), None)
+
+    def test_transpose_with_no_headers(self):
+        data = tablib.Dataset()
+        data.append(('Cat', 'Eats fish', 26))
+        data.append(['Dogs like', '_balls', 48])
+        data.append([73, 'people', 'sleeps'])
+        dataTrans = data.transpose()
+        self.assertEqual(dataTrans[0], ('Cat', 'Dogs like', 73))
+        self.assertEqual(dataTrans[1], ('Eats fish', '_balls', 'people'))
+        self.assertEqual(dataTrans[2], (26, 48, 'sleeps'))
+        self.assertEqual(data.transpose().transpose().dict, data.dict)
+
     def test_transpose_multiple_headers(self):
 
         data = tablib.Dataset()