Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 10 Jul 22:08
fe62cdd

0.3.0 (2026-07-10)

Summary

Stability and correctness release. 27 silent data-loss/corruption bugs fixed, plus new features and hardening. All fixes validated by round-trip inspection, official ECMA-376 schema conformance, and property-based fuzzing (0 failures across 55,000+ writes/checks).

Features

  • Direct DataFrame input — Arrow-consuming functions now accept any object implementing the Arrow PyCapsule interface (__arrow_c_stream__), so bare Polars/pandas DataFrames work without .to_arrow(). Handles Polars Utf8View/BinaryView C-stream exports. Fully backward compatible; byte-identical values vs .to_arrow().
  • num_threads on single-sheet writerswrite_sheet_arrow/write_sheet_arrow_to_bytes accept "auto" (default; uses all cores, prints chosen count) or an explicit int (values <1 clamp to serial). Performance-only knob: every value produces byte-identical output. Invalid values raise cleanly.
  • Categorical/dictionary column support — pandas/Polars Categorical/Enum (Arrow Dictionary) columns are now decoded and written; previously rejected outright. Non-categorical workloads pay nothing (fast-path early return).
  • Null-type column support — all-None columns (Arrow Null) now write as blank cells instead of raising.
  • Clear column-reference errors — unmatched column_formats/column_widths/hidden_columns names hard-error and list available columns as a copyable Python literal. Case-sensitive exact match.

Performance enhancement

  • Parallel single-sheet row generation — large single sheets (>50k rows, no table headers, multi-core) now generate row XML across rayon workers. Byte-identical to the serial path by construction. No regression on the serial path (#[inline(always)] per-row writer).

Bug Fixes

Styles & formatting

  • Cell borders never rendered (borderId hardcoded to 0).
  • Out-of-range text_rotation corrupted the workbook; now mapped/clamped to valid encoding.
  • Invalid color strings ("red", "#FF0000", "F00") broke the entire stylesheet; now normalized to ARGB or omitted (fonts, fills, color scales, data bars).
    Values & types
  • uint64 above i64::MAX overflowed to negative; now routed through the exact f64 path.
  • Formulas with a leading = doubled to ==; now stripped.
  • Excel-1900 date serials off by one on/before 1900-02-28 (phantom 1900-02-29); now matches Excel's canonical serials.
  • Pre-1900 dates produced invalid negative serials; now written as ISO-8601 strings (1900+ stay date-typed).
  • NaN/±Inf in the dict path emitted invalid <v>NaN</v>/<v>inf</v>; now empty cells (matching the Arrow path).
    Ranges & geometry
  • Reversed ranges (end before start) silently discarded CF/merge/data-validation rules; now normalized to valid A1.
  • No enforcement of Excel's grid limits; sheets past 1,048,576 rows / 16,384 cols now rejected with a catchable error on all paths.
  • Hyperlink on row 0 corrupted the sheet; row clamped to ≥1.
  • Hyperlink inside a merged range corrupted the workbook; ref relocated to the merge anchor.
    Tables & charts
  • CRITICAL — table with out-of-range end_col panicked and (under panic="abort") crashed the whole Python process; now clamped/guarded on all four paths.
  • Tables with spaced/invalid names produced unopenable files; names sanitized.
  • Duplicate table names corrupted the workbook; unique id appended to name/displayName.
  • legend_position documented but ignored; now parsed.
  • Scatter-chart value-axis element order violated ECMA-376 (majorGridlines now precedes title).
  • Chart title/axis colors written raw; now normalized. title_font_size=0 (invalid) clamped to the valid 100–400000 range.