Reusable option bundles¶
df_to_xlsx takes 27 parameters. That is fine for a call with two options and
unpleasant for a call with ten — and it gives you nowhere to put a set of options
you want to use more than once.
ExportOptions is a typed, frozen bundle of exactly those options:
import xlsxturbo
from xlsxturbo import ExportOptions
REPORT = ExportOptions(
freeze_panes=True,
autofit=True,
header_format={"bold": True, "bg_color": "#DDDDDD"},
table_style="Medium9",
)
xlsxturbo.df_to_xlsx(df, "out.xlsx", **REPORT.as_kwargs())
Every keyword argument still works exactly as before. This is an addition, and nothing is deprecated — if you pass two options, keep passing two options.
Why ** rather than options=¶
df_to_xlsx(df, path, options=REPORT) would read better, and it would cost more
than it is worth. The compiled function would need a Python wrapper, and that
wrapper would either repeat all 27 parameters — a second copy to keep in step —
or collapse them to **kwargs, which is what your editor would then show you
instead of 27 named, typed parameters.
Since the point of this type is discoverability, trading the signature's discoverability for it would be a bad deal. So the entry points are untouched and the bundle lowers to what they already accept.
Sharing a bundle across sheets¶
The same object produces a per-sheet dict for dfs_to_xlsx:
xlsxturbo.dfs_to_xlsx(
[
(q1, "Q1", REPORT.as_sheet_options()),
(q2, "Q2", REPORT.as_sheet_options()),
],
"quarters.xlsx",
)
as_sheet_options() drops the two options a per-sheet dict does not accept —
constant_memory and defined_names, both of which apply to the workbook as a
whole. Everything else passes through.
Deriving variants¶
The bundle is frozen: its fields cannot be reassigned. Nested dictionaries and
lists remain mutable and are shared by as_kwargs(), as_sheet_options(),
dataclasses.replace() and merged_with(). Treat those values as read-only when
sharing a bundle. Use copy.deepcopy(bundle) before editing nested values that
must be independent. Copying and pickling preserve which options were left unset.
dataclasses.replace overrides named fields:
merged_with layers one bundle over another, taking only the options the second
one actually set:
BASE = ExportOptions(autofit=True, header_format={"bold": True})
SUMMARY = BASE.merged_with(ExportOptions(table_style="Medium2"))
# autofit and header_format survive; table_style is added
That last property is what makes a shared base worth having. A merge that took every field would silently reset the base's options to their defaults wherever the override said nothing.
Unset is not None¶
An option you never touched and an option you set to None are different, and
the bundle keeps them apart:
ExportOptions().as_kwargs() # {}
ExportOptions(table_style=None).as_kwargs() # {"table_style": None}
This matters on the multi-sheet path, where an explicit None means "not on this
sheet", deliberately overriding a workbook-wide default:
xlsxturbo.dfs_to_xlsx(
[
(summary, "Summary", ExportOptions(table_style=None).as_sheet_options()),
(detail, "Detail", {}),
],
"report.xlsx",
table_style="Medium9", # applies to Detail, not to Summary
)
Collapsing the two would make that impossible to express, so unset options are
omitted from both lowerings and None is passed through.
What is not in the bundle¶
df, output_path and sheet_name. They identify this call rather than
configuring it, so they stay positional arguments where they belong.
The per-feature shapes — ChartOptions, ValidationOptions, HeaderFormat and
the rest — are TypedDicts in xlsxturbo.types, not
dataclasses. They already describe themselves to a type checker, and a second
representation of the same thing would only be another surface to keep in step.