From a4a13a7930898bc5c61a019d3870384633ed3d16 Mon Sep 17 00:00:00 2001 From: Pitchfork-and-Torch Date: Fri, 18 Sep 2026 03:17:21 +0000 Subject: [PATCH] Reject bool/float/nan in maxcolwidths bool is an int subclass, so maxcolwidths=True previously wrapped every character. float('nan') bypassed the width<=0 guard (NaN comparisons are false) and hung forever inside textwrap. Require positive ints or None. --- tabulate/__init__.py | 20 ++++++++++++++++++++ test/test_regression.py | 21 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tabulate/__init__.py b/tabulate/__init__.py index 12a2950..6fa0dca 100644 --- a/tabulate/__init__.py +++ b/tabulate/__init__.py @@ -1613,6 +1613,25 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"): return rows, headers, headers_pad +def _validate_maxcolwidth(width): + """Return a positive int column width, or None to leave the column unbound. + + ``bool`` is rejected (it is an ``int`` subclass): ``True`` previously wrapped + every character. Non-ints — including ``float('nan')`` — are rejected too; + ``nan`` bypassed ``width <= 0`` (comparisons with NaN are false) and hung + inside ``textwrap`` forever. + """ + if width is None: + return None + if isinstance(width, bool) or not isinstance(width, int): + raise TypeError( + f"maxcolwidths values must be positive ints or None, got {width!r}" + ) + if width <= 0: + raise ValueError(f"invalid width {width!r} (must be > 0)") + return width + + def _wrap_text_to_colwidths( list_of_lists, colwidths, @@ -1636,6 +1655,7 @@ def _wrap_text_to_colwidths( new_row.append(cell) continue + width = _validate_maxcolwidth(width) if width is not None: wrapper = _CustomTextWrap( width=width, diff --git a/test/test_regression.py b/test/test_regression.py index 9555676..c0911f4 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -2,7 +2,7 @@ from tabulate import DataRow, Line, TableFormat, tabulate -from common import assert_equal, skip +from common import assert_equal, raises, skip def test_ansi_color_in_table_cells(): @@ -598,3 +598,22 @@ def test_github_escape_pipe_character(): result = tabulate([["foo|bar"]], headers=("spam|eggs",), tablefmt="github") expected = "| spam\\|eggs |\n|:------------|\n| foo\\|bar |" assert_equal(expected, result) + +def test_maxcolwidths_rejects_bool_float_nan(): + "maxcolwidths: reject bool/float/nan (bool wrapped every char; nan hung)" + import math + + for bad in (True, [True], [0.5], [float("nan")], [math.inf]): + try: + tabulate([["hello"]], maxcolwidths=bad) + raise AssertionError(f"expected TypeError for {bad!r}") + except TypeError: + pass + try: + tabulate([["hello"]], maxcolwidths=[-1]) + raise AssertionError("expected ValueError for -1") + except ValueError: + pass + # None still means unbound + result = tabulate([["hello"]], maxcolwidths=[None], tablefmt="plain") + assert_equal("hello", result)