Errors and warnings¶
xlsxturbo validates eagerly and fails loudly. An option it does not recognise is an error, not a value it quietly ignores — so a misspelled key surfaces at the call rather than as a missing style in a spreadsheet somebody else opens next week.
What gets raised¶
Every failure xlsxturbo itself raises is an XlsxTurboError. Catching that one class
catches everything the library validates, and nothing else (one narrow
exception):
import xlsxturbo
try:
xlsxturbo.df_to_xlsx(df, "out.xlsx", header_format={"bold": True})
except xlsxturbo.XlsxTurboError as exc:
log.error("export failed: %s", exc)
The subclasses say what kind of failure it was:
| Exception | Raised for | Also a |
|---|---|---|
OptionError |
Never raised itself — the parent of the two below, so except OptionError catches every problem with what you passed |
— |
ConfigurationError |
An option or argument value is invalid: an unrecognised key, an unparseable colour or cell reference, an out-of-range number, an invalid chart range, an image file that cannot be read | OptionError, ValueError |
ConfigurationTypeError |
An option or argument has the wrong type: a list where a dict is required, a non-string dictionary key, a bytes path |
OptionError, TypeError |
InputDataError |
The object passed as data is not a pandas or polars DataFrame, or its columns cannot be read | ValueError |
FileError |
A filesystem read or write failed: a missing output directory, a permissions problem, a full disk, an unreadable CSV input — and the workbook rules Excel's writer checks only during the save | OSError, ValueError |
WorkbookValidationError |
The configuration is well-formed but Excel forbids it. Raised by the two name pre-checks: two sheets claiming the same table name, and a table name that collides with a defined_names key |
ConfigurationError, so also OptionError and ValueError |
"Anything wrong with what I passed"¶
ConfigurationError and ConfigurationTypeError split a bad value from a
wrong type. That distinction is worth having and is usually not the one you
want when writing a handler — most callers want both and nothing else:
try:
xlsxturbo.df_to_xlsx(df, path, **user_supplied_options)
except xlsxturbo.OptionError as exc:
return {"error": f"bad export options: {exc}"} # the caller's mistake
except xlsxturbo.FileError as exc:
return {"error": f"could not write the file: {exc}"} # not their mistake
OptionError arrived in 0.21.0 by reparenting, not renaming, so every existing
except clause means exactly what it did before.
Two failures cross that split rather than following it — see Save-time validation lands
in FileError before writing a handler that
treats the two branches as "their mistake" and "ours".
Messages carry the option and the specific key or cell reference that caused them, so they identify the offending entry rather than just the option family:
>>> df_to_xlsx(df, "out.xlsx", comments={"B2": {"txt": "note"}})
xlsxturbo.ConfigurationError: comments['B2']: unknown option 'txt'. Valid: text, author
Existing except ValueError and except TypeError still work¶
The hierarchy arrived in 0.19.0 and changed nothing about which builtin any given failure raises. Every class above inherits the builtin that the same failure raised in 0.18 and earlier, so upgrading cannot break a handler you already have:
# Written against 0.18. Still correct, and still catches exactly what it did.
try:
xlsxturbo.df_to_xlsx(df, path, column_widths={"A": 12})
except ValueError as exc:
...
That constraint is why the mapping is not always the one you would pick from scratch.
InputDataError is a ValueError rather than a TypeError, even though "you passed a
list, not a DataFrame" is really a type problem — that failure has always raised
ValueError, and moving it would break working code. Catch InputDataError when you
want the distinction.
I/O failures are an OSError — and still a ValueError¶
FileError inherits both, so either handler works:
try:
xlsxturbo.df_to_xlsx(df, "/nonexistent/dir/out.xlsx")
except xlsxturbo.FileError as exc:
... # what to write today
except OSError:
... # what you would write for any file operation
except ValueError:
... # what 0.18 required
errno is set, so you can tell one filesystem failure from another without
matching on message text:
import errno
try:
xlsxturbo.df_to_xlsx(df, "/mnt/reports/out.xlsx")
except xlsxturbo.FileError as exc:
if exc.errno == errno.ENOSPC:
... # the disk is full: retry elsewhere
elif exc.errno == errno.ENOENT:
... # the directory is gone: create it, or fail the job
raise
Two details worth knowing. The message still begins Failed to save workbook to '<path>':
for a save failure, so message-matching code written against 0.18 keeps working — and
that is exactly why strerror and filename stay None. OSError reformats its
own str() into [Errno n] strerror: 'filename' as soon as filename is set, which
would throw the message away; the path is already in it. errno is the one field that
can be populated without changing what the exception prints.
On platforms whose native error numbers are not POSIX — Windows — errno carries the
POSIX equivalent rather than the native code, so the comparison above means the same
thing everywhere. Where no meaningful number is available it stays None, so compare
against a specific constant rather than testing for truthiness.
Save-time validation lands in FileError¶
Not every rule Excel imposes can be checked when the option is applied. The underlying
writer defers several of them to the moment the workbook is serialised, and whatever
fails there is reported by the save — so a mistake in what you passed can arrive as a
FileError:
xlsxturbo.dfs_to_xlsx([(df, "Data"), (df, "Data")], "out.xlsx")
# FileError: Failed to save workbook to 'out.xlsx': Worksheet name 'data' has
# already been used in this workbook.
A duplicate sheet name and a chart range naming a sheet the workbook does not contain
are the two you are most likely to meet. Both keep the Failed to save workbook to
'<path>': prefix, and the writer's own sentence after it names the rule that was broken.
The mirror case runs the other way: an image xlsxturbo cannot open is a
ConfigurationError, not a FileError, because the path arrived as an option and the
failure is reported by the layer that applies options.
xlsxturbo.df_to_xlsx(df, "out.xlsx", images={"A1": "logo.png"})
# ConfigurationError: images['A1']: Failed to load image 'logo.png': No such file or directory (os error 2)
Both messages name the sheet, the option or the path, and except
xlsxturbo.XlsxTurboError catches either — which is what a top-level export handler
wants. The distinction matters for a handler that branches on whose fault the failure
was: a FileError is not on its own evidence that the disk was involved, and a
ConfigurationError is not on its own evidence that it was not.
What is not in the hierarchy¶
Argument conversion performed by the Python/Rust binding before xlsxturbo sees the value
raises a plain TypeError. Passing a non-string where the signature requires str is the
usual way to hit this. Everything xlsxturbo itself validates is in the hierarchy.
One dict-valued option falls on the binding's side of that line, which is worth knowing
because its neighbours do not: defined_names is typed in the signature, so a wrong
inner type is rejected by the conversion and arrives as a plain TypeError.
column_widths and row_heights look identical from Python and are read by extractors,
so the same mistake in either is a ConfigurationTypeError. Catch
(XlsxTurboError, TypeError) if you need to treat all option-type mistakes alike.
row_heights was on the binding's side of the line until 1.3.x, and moving it fixed
more than the class name. The conversion accepted {True: 40} — bool subclasses int
in Python — and silently sized row 2; it accepted {0: True} as a height of one point;
and it answered a negative or out-of-range key with OverflowError, which is an
ArithmeticError and so is caught by neither except XlsxTurboError nor the
except (XlsxTurboError, TypeError) recommended above. Those are now
ConfigurationTypeError and ConfigurationError, each naming the entry:
>>> df_to_xlsx(df, "out.xlsx", row_heights={-1: 20})
xlsxturbo.ConfigurationError: row_heights['-1']: must be a non-negative row index
A dtype problem discovered deep in the write pipeline surfaces as ConfigurationError
rather than InputDataError: the pipeline reports it as a message without a category, and
inventing one from the text would be guesswork. InputDataError covers the case that
matters — the object is not a supported DataFrame at all — which is checked before any
work starts.
Nothing is half-written¶
Validation of a given option happens as that option is applied, and the workbook is only serialised once every option has been applied. Two consequences:
- A validation error leaves no output file at all. If the destination already held a file, it is exactly as it was.
- A failure during the save leaves the destination untouched too. The workbook is written to a temporary file in the destination's own directory and renamed over the target only once it is complete. Re-exporting over yesterday's report can therefore never leave you with neither.
Because the staging file is created beside the destination, that directory must exist and be writable — a path into a directory that does not exist is a save failure, not a silently created tree. When an existing file is replaced, its permissions are preserved.
See Compatibility and limitations for the full note on output safety.
Warnings¶
constant_memory=True cannot apply options that need a second pass over a finished
sheet. Rather than dropping them silently, xlsxturbo emits a RuntimeWarning naming the
sheet and every option it skipped:
RuntimeWarning: sheet 'Data': constant_memory=True disables these features:
table_style, autofit, conditional_formats
New options default to skipped-and-warned, which is the safe direction: a feature that turns out to be compatible gets promoted deliberately, rather than a new one being silently ignored. The capability matrix lists which options survive constant-memory mode.
Since it is a real warning, it can be promoted to an error in a batch job or a test suite that should not tolerate a silently reduced export:
Reporting a problem¶
A crash, a corrupt output file, or a message that misidentifies the cause is a bug worth
reporting — see CONTRIBUTING.md.
A file Excel refuses to open is the most valuable kind of report; include the calling
code and the xlsxturbo version from xlsxturbo.version().