Repository: astanin/python-tabulate
Pull request #342 (2024-09-26 16:42:37)

Merge changes from Pull Request #79 (preserve_whitespace as an argument to tabulate())
Related: #79 


Patch:
diff --git a/README.md b/README.md
index b596cf5..089f20e 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@ When data is a list of dictionaries, a dictionary can be passed as `headers`
 to replace the keys with other column labels:
 
 ```pycon
->>> print(tabulate([{1: "Alice", 2: 24}, {1: "Bob", 2: 19}], 
+>>> print(tabulate([{1: "Alice", 2: 24}, {1: "Bob", 2: 19}],
 ...                headers={1: "Name", 2: "Age"}))
 Name      Age
 ------  -----
@@ -739,13 +739,8 @@ column, in which case every column may have different number formatting:
 ### Text formatting
 
 By default, `tabulate` removes leading and trailing whitespace from text
-columns. To disable whitespace removal, set the global module-level flag
-`PRESERVE_WHITESPACE`:
-
-```python
-import tabulate
-tabulate.PRESERVE_WHITESPACE = True
-```
+columns. To disable whitespace removal, pass `preserve_whitespace=True`.
+Older versions of the library used a global module-level flag PRESERVE_WHITESPACE.
 
 ### Wide (fullwidth CJK) symbols
 
diff --git a/tabulate/__init__.py b/tabulate/__init__.py
index 6b6afb7..598cb4e 100644
--- a/tabulate/__init__.py
+++ b/tabulate/__init__.py
@@ -33,9 +33,6 @@ except ImportError:
 # minimum extra space in headers
 MIN_PADDING = 2
 
-# Whether or not to preserve leading/trailing whitespace in data.
-PRESERVE_WHITESPACE = False
-
 _DEFAULT_FLOATFMT = "g"
 _DEFAULT_INTFMT = ""
 _DEFAULT_MISSINGVAL = ""
@@ -163,7 +160,6 @@ def _grid_line_with_colons(colwidths, colaligns):
     return "+" + "+".join(segments) + "+"
 
 
-
 def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
     alignment = {
         "left": "",
@@ -1100,13 +1096,13 @@ def _choose_width_fn(has_invisible, enable_widechars, is_multiline):
     return width_fn
 
 
-def _align_column_choose_padfn(strings, alignment, has_invisible):
+def _align_column_choose_padfn(strings, alignment, has_invisible, preserve_whitespace):
     if alignment == "right":
-        if not PRESERVE_WHITESPACE:
+        if not preserve_whitespace:
             strings = [s.strip() for s in strings]
         padfn = _padleft
     elif alignment == "center":
-        if not PRESERVE_WHITESPACE:
+        if not preserve_whitespace:
             strings = [s.strip() for s in strings]
         padfn = _padboth
     elif alignment == "decimal":
@@ -1120,7 +1116,7 @@ def _align_column_choose_padfn(strings, alignment, has_invisible):
     elif not alignment:
         padfn = _padnone
     else:
-        if not PRESERVE_WHITESPACE:
+        if not preserve_whitespace:
             strings = [s.strip() for s in strings]
         padfn = _padright
     return strings, padfn
@@ -1163,9 +1159,12 @@ def _align_column(
     has_invisible=True,
     enable_widechars=False,
     is_multiline=False,
+    preserve_whitespace=False,
 ):
     """[string] -> [padded_string]"""
-    strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible)
+    strings, padfn = _align_column_choose_padfn(
+        strings, alignment, has_invisible, preserve_whitespace
+    )
     width_fn = _align_column_choose_width_fn(
         has_invisible, enable_widechars, is_multiline
     )
@@ -1271,15 +1270,21 @@ def _format(val, valtype, floatfmt, intfmt, missingval="", has_invisible=True):
         return f"{val}"
     elif valtype is int:
         if isinstance(val, str):
-            val_striped = val.encode('unicode_escape').decode('utf-8')
-            colored = re.search(r'(\\[xX]+[0-9a-fA-F]+\[\d+[mM]+)([0-9.]+)(\\.*)$', val_striped)
+            val_striped = val.encode("unicode_escape").decode("utf-8")
+            colored = re.search(
+                r"(\\[xX]+[0-9a-fA-F]+\[\d+[mM]+)([0-9.]+)(\\.*)$", val_striped
+            )
             if colored:
                 total_groups = len(colored.groups())
                 if total_groups == 3:
                     digits = colored.group(2)
                     if digits.isdigit():
-                        val_new = colored.group(1) + format(int(digits), intfmt) + colored.group(3)
-                        val = val_new.encode('utf-8').decode('unicode_escape')
+                        val_new = (
+                            colored.group(1)
+                            + format(int(digits), intfmt)
+                            + colored.group(3)
+                        )
+                        val = val_new.encode("utf-8").decode("unicode_escape")
             intfmt = ""
         return format(val, intfmt)
     elif valtype is bytes:
@@ -1645,6 +1650,7 @@ def tabulate(
     disable_numparse=False,
     colglobalalign=None,
     colalign=None,
+    preserve_whitespace=False,
     maxcolwidths=None,
     headersglobalalign=None,
     headersalign=None,
@@ -2323,7 +2329,15 @@ def tabulate(
     if tablefmt == "colon_grid":
         aligns_copy = ["left"] * len(cols)
     cols = [
-        _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
+        _align_column(
+            c,
+            a,
+            minw,
+            has_invisible,
+            enable_widechars,
+            is_multiline,
+            preserve_whitespace,
+        )
         for c, a, minw in zip(cols, aligns_copy, minwidths)
     ]
 
@@ -2820,7 +2834,16 @@ def _main():
         opts, args = getopt.getopt(
             sys.argv[1:],
             "h1o:s:F:I:f:",
-            ["help", "header", "output=", "sep=", "float=", "int=", "colalign=", "format="],
+            [
+                "help",
+                "header",
+                "output=",
+                "sep=",
+                "float=",
+                "int=",
+                "colalign=",
+                "format=",
+            ],
         )
     except getopt.GetoptError as e:
         print(e)


Test patch:
diff --git a/test/common.py b/test/common.py
index ca5a069..fe33259 100644
--- a/test/common.py
+++ b/test/common.py
@@ -2,11 +2,13 @@ import pytest  # noqa
 from pytest import skip, raises  # noqa
 import warnings
 
+
 def assert_equal(expected, result):
     print("Expected:\n%s\n" % expected)
     print("Got:\n%s\n" % result)
     assert expected == result
 
+
 def assert_in(result, expected_set):
     nums = range(1, len(expected_set) + 1)
     for i, expected in zip(nums, expected_set):
diff --git a/test/test_api.py b/test/test_api.py
index e658e82..062573c 100644
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -50,6 +50,7 @@ def test_tabulate_signature():
         ("disable_numparse", False),
         ("colglobalalign", None),
         ("colalign", None),
+        ("preserve_whitespace", False),
         ("maxcolwidths", None),
         ("headersglobalalign", None),
         ("headersalign", None),
diff --git a/test/test_output.py b/test/test_output.py
index 63f25a9..059742c 100644
--- a/test/test_output.py
+++ b/test/test_output.py
@@ -1,7 +1,6 @@
 """Test output of the various forms of tabular data."""
 from pytest import mark
 
-import tabulate as tabulate_module
 from common import assert_equal, raises, skip, check_warnings
 from tabulate import tabulate, simple_separated_format, SEPARATING_LINE
 
@@ -1459,7 +1458,12 @@ def test_colon_grid():
             "+------+------+",
         ]
     )
-    result = tabulate([[3, 4]], headers=("H1", "H2"), tablefmt="colon_grid", colalign=["right", "center"])
+    result = tabulate(
+        [[3, 4]],
+        headers=("H1", "H2"),
+        tablefmt="colon_grid",
+        colalign=["right", "center"],
+    )
     assert_equal(expected, result)
 
 
@@ -1482,7 +1486,9 @@ def test_colon_grid_wide_characters():
             "+-----------+---------+",
         ]
     )
-    result = tabulate(_test_table, headers, tablefmt="colon_grid", colalign=["left", "right"])
+    result = tabulate(
+        _test_table, headers, tablefmt="colon_grid", colalign=["left", "right"]
+    )
     assert_equal(expected, result)
 
 
@@ -2773,7 +2779,9 @@ def test_intfmt_with_string_as_integer():
 @mark.skip(reason="It detects all values as floats but there are strings and integers.")
 def test_intfmt_with_string_with_floats():
     "Output: integer format"
-    result = tabulate([[82000.38], ["1500.47"], ["2463"], [92165]], intfmt=",", tablefmt="plain")
+    result = tabulate(
+        [[82000.38], ["1500.47"], ["2463"], [92165]], intfmt=",", tablefmt="plain"
+    )
     expected = "82000.4\n 1500.47\n 2463\n92,165"
     assert_equal(expected, result)
 
@@ -3208,18 +3216,16 @@ def test_disable_numparse_list():
 
 def test_preserve_whitespace():
     "Output: Default table output, but with preserved leading whitespace."
-    tabulate_module.PRESERVE_WHITESPACE = True
     table_headers = ["h1", "h2", "h3"]
     test_table = [["  foo", " bar   ", "foo"]]
     expected = "\n".join(
         ["h1     h2       h3", "-----  -------  ----", "  foo   bar     foo"]
     )
-    result = tabulate(test_table, table_headers)
+    result = tabulate(test_table, table_headers, preserve_whitespace=True)
     assert_equal(expected, result)
 
-    tabulate_module.PRESERVE_WHITESPACE = False
     table_headers = ["h1", "h2", "h3"]
     test_table = [["  foo", " bar   ", "foo"]]
     expected = "\n".join(["h1    h2    h3", "----  ----  ----", "foo   bar   foo"])
-    result = tabulate(test_table, table_headers)
+    result = tabulate(test_table, table_headers, preserve_whitespace=False)
     assert_equal(expected, result)
diff --git a/test/test_regression.py b/test/test_regression.py
index d2fc3a0..5289db0 100644
--- a/test/test_regression.py
+++ b/test/test_regression.py
@@ -513,6 +513,7 @@ def test_numpy_int64_as_integer():
     except ImportError:
         raise skip("")
 
+
 def test_empty_table_with_colalign():
     "Regression: empty table with colalign kwarg"
     table = tabulate([], ["a", "b", "c"], colalign=("center", "left", "left", "center"))