pola-rs/polars · error · TypeError
invalid dtype_format value: {fmt!r} (expected format string,
Error message
invalid dtype_format value: {fmt!r} (expected format string, got {qualified_type_name(fmt)!r}) What it means
TypeError raised by _xl_setup_table_options (write_excel preparation) when a dtype_formats value is not a string. dtype_formats maps data types (or tuples/frozensets of types, which get expanded) to Excel number-format strings such as '#,##0.00'; passing an int, function, or other object instead of a format string is rejected.
Source
Thrown at py-polars/src/polars/io/spreadsheet/_write_utils.py:457
column_formulas = {
col: {"formula": options} if isinstance(options, str) else options
for col, options in (formulas or {}).items()
}
# normalise formats
column_formats = dict(column_formats or {})
dtype_formats = dict(dtype_formats or {})
for tp in list(dtype_formats):
if isinstance(tp, (tuple, frozenset)):
updates: dict[OneOrMoreDataTypes, str] = dict.fromkeys(
tp, dtype_formats.pop(tp)
)
dtype_formats.update(updates)
for fmt in dtype_formats.values():
if not isinstance(fmt, str):
msg = f"invalid dtype_format value: {fmt!r} (expected format string, got {qualified_type_name(fmt)!r})"
raise TypeError(msg)
# inject sparkline/row-total placeholder(s)
if sparklines:
df = _xl_inject_dummy_table_columns(df, sparklines)
if column_formulas:
df = _xl_inject_dummy_table_columns(df, column_formulas)
if row_totals:
df = _xl_inject_dummy_table_columns(df, row_total_funcs, dtype=row_totals_dtype)
# seed format cache with default fallback format
fmt_default = format_cache.get({"valign": "vcenter"})
if table_style is None:
# no table style; apply default black (+ve) & red (-ve) numeric formatting
int_base_fmt = _XL_DEFAULT_INTEGER_FORMAT_
flt_base_fmt = _XL_DEFAULT_FLOAT_FORMAT_
else:
# if we have a table style, defer the colours to that styleView on GitHub (pinned to df599052da)
Solutions
- Use proper Excel format strings: dtype_formats={pl.Float64: '#,##0.0000', pl.Date: 'yyyy-mm-dd'}.
- For per-column control use column_formats={'amount': '#,##0.00'} instead of dtype-level formats.
- If the format is dynamic, build it as an f-string that resolves to a str before passing.
Example fix
# before
pl.write_excel(df, dtype_formats={pl.Float64: 4, pl.Date: to_datestr})
# after
pl.write_excel(df, dtype_formats={pl.Float64: '0.0000', pl.Date: 'yyyy-mm-dd'}) Defensive patterns
Strategy: type-guard
Validate before calling
for tp, fmt in dtype_formats.items():
if not isinstance(fmt, str):
raise TypeError(f'dtype_formats[{tp!r}] must be an Excel format string, got {type(fmt).__name__}')
pl.write_excel(df, dtype_formats=dtype_formats) Type guard
def is_valid_dtype_formats(d) -> bool:
return isinstance(d, dict) and all(isinstance(v, str) for v in d.values()) Try / catch
try:
pl.write_excel(df, dtype_formats=dtype_formats)
except TypeError as e:
if 'invalid dtype_format value' in str(e):
pl.write_excel(df, dtype_formats={k: str(v) for k, v in dtype_formats.items()})
else:
raise Prevention
- Excel formats are strings ('#,##0.00', 'yyyy-mm-dd') — never callables or ints.
- Centralize format strings as named constants (FMT_MONEY = '#,##0.00').
- Use column_formats for per-column control and keep dtype_formats keys as dtypes only.
When it happens
Trigger: pl.write_excel(df, dtype_formats={pl.Float64: 4}) (intended '0.0000'), or dtype_formats={pl.Date: some_formatter_callable}. Values are checked with isinstance(fmt, str) after tuple/frozenset keys are expanded.
Common situations: Confusing Excel format strings with Python format specs or pandas float_format callables; passing {'0000'} style or numeric precision directly; copying config from openpyxl number_format usage where non-string values sometimes slipped through.
Related errors
- worksheet object requires the parent workbook object; found
- cannot create a second {col!r} column
- supplying 'columns' param value is mandatory for sparklines
- sparkline data range/cols must all be adjacent
- invalid table style key: {key!r}
AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16).
Data as JSON: /api/errors/a6f062cbadd41d27.
Report an issue: GitHub.