Repository: jazzband/tablib
Pull request #624 (2025-06-26 17:34:52)

feat: support column_width in xlsx format for databook exports
Extend work from #516 to support exports of Databook objects to XLSX with column widths, as already supported for Dataset. I read the new tests added in that PR and did not see any that would have covered the `export_set` lines that I've copied.

claudep: Thanks for your contribution. I guess it should still be welcome to add a test with the `column_width` parameter to a Databook export. And probably the docs needs a small update too.
npilon: @claudep is there anything else needed from me on this change?

Patch:
diff --git a/AUTHORS b/AUTHORS
index 45aa172..d645dfc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -31,6 +31,7 @@ Here is a list of past and present much-appreciated contributors:
     Matthew Hegarty
     Matthias Dellweg
     Mike Waldner
+    Nicholas Pilon
     Peyman Salehi
     Rabin Nankhwa
     Tak Hogan
diff --git a/docs/formats.rst b/docs/formats.rst
index a9bd8e5..be7f8c5 100644
--- a/docs/formats.rst
+++ b/docs/formats.rst
@@ -259,6 +259,13 @@ For example::
     data = tablib.Dataset()
     data.export('xlsx', column_width='adaptive')
 
+This works with ``Databook`` as well::
+
+    data = tablib.Databook()
+    data.export('xlsx', column_width='adaptive')
+
+The adaptive width will be calculated for each sheet in the databook.
+
 .. versionchanged:: 3.8.0
     The ``column_width`` parameter for ``export_set()`` was added.
 
diff --git a/src/tablib/formats/_xlsx.py b/src/tablib/formats/_xlsx.py
index 1a75c20..8fa6957 100644
--- a/src/tablib/formats/_xlsx.py
+++ b/src/tablib/formats/_xlsx.py
@@ -72,7 +72,8 @@ class XLSXFormat:
         return stream.getvalue()
 
     @classmethod
-    def export_book(cls, databook, freeze_panes=True, invalid_char_subst="-", escape=False):
+    def export_book(cls, databook, freeze_panes=True, invalid_char_subst="-",
+                    escape=False, column_width=None):
         """Returns XLSX representation of DataBook.
         See export_set().
         """
@@ -89,6 +90,8 @@ class XLSXFormat:
 
             cls.dset_sheet(dset, ws, freeze_panes=freeze_panes, escape=escape)
 
+            cls._adapt_column_width(ws, column_width)
+
         stream = BytesIO()
         wb.save(stream)
         return stream.getvalue()


Test patch:
diff --git a/tests/test_tablib.py b/tests/test_tablib.py
index 6a6dff6..7a89eb7 100755
--- a/tests/test_tablib.py
+++ b/tests/test_tablib.py
@@ -1350,23 +1350,39 @@ class XLSTests(BaseTestCase):
 
 
 class XLSXTests(BaseTestCase):
-    def _helper_export_column_width(self, column_width):
-        """check that column width adapts to value length"""
-        def _get_width(data, input_arg):
-            xlsx_content = data.export('xlsx', column_width=input_arg)
-            wb = load_workbook(filename=BytesIO(xlsx_content))
-            ws = wb.active
-            return ws.column_dimensions['A'].width
+    def _get_width(self, data, input_arg):
+        xlsx_content = data.export('xlsx', column_width=input_arg)
+        wb = load_workbook(filename=BytesIO(xlsx_content))
+        ws = wb.active
+        return ws.column_dimensions['A'].width
 
+    def _xlsx_cell_values_data(self, cls):
         xls_source = Path(__file__).parent / 'files' / 'xlsx_cell_values.xlsx'
         with xls_source.open('rb') as fh:
-            data = tablib.Dataset().load(fh)
-        width_before = _get_width(data, column_width)
+            return cls().load(fh, format='xlsx')
+
+    def _helper_export_column_width(self, column_width):
+        """check that column width adapts to value length"""
+
+        data = self._xlsx_cell_values_data(cls=tablib.Dataset)
+        width_before = self._get_width(data, column_width)
         data.append([
             'verylongvalue-verylongvalue-verylongvalue-verylongvalue-'
             'verylongvalue-verylongvalue-verylongvalue-verylongvalue',
         ])
-        width_after = _get_width(data, width_before)
+        width_after = self._get_width(data, width_before)
+        return width_before, width_after
+
+    def _book_helper_export_column_width(self, column_width):
+        """check that column width adapts to value length"""
+
+        data = self._xlsx_cell_values_data(cls=tablib.Databook)
+        width_before = self._get_width(data, column_width)
+        data.sheets()[0].append([
+            'verylongvalue-verylongvalue-verylongvalue-verylongvalue-'
+            'verylongvalue-verylongvalue-verylongvalue-verylongvalue',
+        ])
+        width_after = self._get_width(data, width_before)
         return width_before, width_after
 
     def test_xlsx_format_detect(self):
@@ -1536,6 +1552,29 @@ class XLSXTests(BaseTestCase):
         with self.assertRaises(ValueError):
             self._helper_export_column_width("invalid input")
 
+    def test_xlsx_book_column_width_adaptive(self):
+        """Test that column width adapts to value length from a databook"""
+        width_before, width_after = self._book_helper_export_column_width("adaptive")
+        self.assertEqual(width_before, 11)
+        self.assertEqual(width_after, 11)
+
+    def test_xlsx_book_column_width_integer(self):
+        """Test that column width changes to integer length from a databook"""
+        width_before, width_after = self._book_helper_export_column_width(10)
+        self.assertEqual(width_before, 10)
+        self.assertEqual(width_after, 10)
+
+    def test_xlsx_book_column_width_none(self):
+        """Test that column width does not change from a databook"""
+        width_before, width_after = self._book_helper_export_column_width(None)
+        self.assertEqual(width_before, 13)
+        self.assertEqual(width_after, 13)
+
+    def test_xlsx_book_column_width_value_error(self):
+        """Raise ValueError if column_width is not a valid input for a databook"""
+        with self.assertRaises(ValueError):
+            self._book_helper_export_column_width("invalid input")
+
 
 class JSONTests(BaseTestCase):
     def test_json_format_detect(self):