In _wrap_text_to_colwidths the empty-string check compared each cell
directly to "". For a pandas NA scalar, cell == "" returns a non-bool
NA whose truthiness then raises "boolean value of NA is ambiguous"
inside the surrounding or, so tabulate crashed whenever maxcolwidths
was set and any cell was pd.NA.
Guard the comparison with isinstance(cell, str) so only real strings are
tested for emptiness; the NA then flows through the normal _type path and
renders as <NA>, matching how plain tabulate (without maxcolwidths)
already displays it.
What
Fixes #429: tabulate(df, maxcolwidths=...) raised TypeError: boolean value of NA is ambiguous whenever a cell was a pandas NA scalar (e.g. a float32[pyarrow] column with a missing value).
Why
In _wrap_text_to_colwidths each cell is classified with:
For a pandas NA, cell == "" does not return a plain bool — it returns NA, and forcing that to a truth value inside the or raises TypeError: boolean value of NA is ambiguous. So any table with maxcolwidths set crashed on a pd.NA cell.
Fix
Guard the empty-string test with isinstance(cell, str) so only real strings are compared to "". A NA then flows through the normal _type path and renders as <NA> — exactly how plain tabulate (without maxcolwidths) already displays it, so the two paths now agree.
Verified against the issue's exact reproducer (a float32[pyarrow] column): the maxcolwidths output now matches the non-maxcolwidths output, and a NA mixed with real floats still renders the real values correctly.
Tests
Added test_wrap_pandas_na_value in test/test_textwrapper.py (skips if pandas is unavailable). It reproduces the crash on master (TypeError: boolean value of NA is ambiguous at the same line) and passes with the fix. The wrapping/textwrapper test modules are all green.
This change was prepared by an AI agent under my direction; I reviewed and verified it.