Skip to main content

Create styled excel reports with declarative python.

Project description

xpyxl — Excel in Python

Compose polished spreadsheets with pure Python—no manual coordinates. You assemble rows/columns/cells; xpyxl handles layout, rendering, and styling with utility-style classes.

Core ideas

  • Positionless composition: Build sheets declaratively from row, col, cell, table, vstack, and hstack.
  • Composable styling: Tailwind-inspired utilities (typography, colors, alignment, number formats) applied via style=[...].
  • Deterministic rendering: Pure-data trees compiled into .xlsx files with predictable output—ideal for tests and CI diffing.

Installation

uv add xpyxl
pip install xpyxl

Getting started

import xpyxl as x

report = (
    x.workbook()[
        x.sheet("Summary")[
            x.row(style=[x.text_2xl, x.bold, x.text_blue])["Q3 Sales Overview"],
            x.row(style=[x.text_sm, x.text_gray])["Region", "Units", "Price"],
            x.row(style=[x.bg_primary, x.text_white, x.bold])["EMEA", 1200, 19.0],
            x.row()["APAC", 900, 21.0],
            x.row()["AMER", 1500, 18.5],
        ]
    ]
)

report.save("report.xlsx")

Rendering Engines

xpyxl supports multiple rendering engines, allowing you to choose the best one for your needs:

  • openpyxl (default): Full-featured engine with comprehensive Excel support. Best for complex workbooks with advanced formatting.
  • xlsxwriter: Fast, memory-efficient engine. Ideal for large datasets and performance-critical applications.

Using Different Engines

Specify the engine when saving:

import xpyxl as x

workbook = x.workbook()[
    x.sheet("Data")[
        x.row(style=[x.bold])["Name", "Value"],
        x.row()["Item A", 100],
        x.row()["Item B", 200],
    ]
]

# Use openpyxl (default)
workbook.save("output-openpyxl.xlsx", engine="openpyxl")

# Use xlsxwriter
workbook.save("output-xlsxwriter.xlsx", engine="xlsxwriter")

Saving without touching disk

Workbook.save accepts a filesystem path, any binary buffer (like io.BytesIO()), or no target at all to get the raw bytes back:

import io
from pathlib import Path

buffer = io.BytesIO()
workbook.save(buffer, engine="xlsxwriter")  # writes into memory

raw_bytes = workbook.save(engine="openpyxl")  # returns bytes when no target is provided
Path("report.xlsx").write_bytes(raw_bytes)

Both engines produce equivalent Excel files, but may have subtle differences in:

  • File size and memory usage
  • Rendering performance
  • Support for advanced Excel features

Choose openpyxl for maximum compatibility and feature support, or xlsxwriter when performance and memory efficiency are priorities.

Performance Benchmarks

Benchmark results comparing the two rendering engines across different scenarios (averaged over 3 runs):

Big Tables

Size Engine Time (s) Memory (MB)
100 openpyxl 0.0977 0.68
100 xlsxwriter 0.0325 0.50
1,000 openpyxl 0.9354 3.10
1,000 xlsxwriter 0.2565 2.04
10,000 openpyxl 11.2455 31.26
10,000 xlsxwriter 4.0539 17.81
50,000 openpyxl 55.5080 156.33
50,000 xlsxwriter 20.5686 94.43

Summary: xlsxwriter is 2.7-3.7x faster and uses 1.4-1.8x less memory for large tables. The openpyxl engine has been optimized with style object caching, providing ~15% performance improvement compared to previous versions.

Simple Layouts

Engine Time (s) Memory (MB)
openpyxl 0.0084 0.38
xlsxwriter 0.0058 0.34

Summary: xlsxwriter is 1.5x faster with similar memory usage.

Complex Layouts

Engine Time (s) Memory (MB)
openpyxl 0.1462 0.69
xlsxwriter 0.0454 0.52

Summary: xlsxwriter is 3.2x faster and uses 1.3x less memory for multi-sheet workbooks with styling.

Run benchmarks yourself:

uv run scripts/benchmark.py

Primitives

x.row(style=[x.bold, x.bg_warning])[1, 2, 3, 4, 5]
x.col(style=[x.italic])["a", "b", "c"]
x.cell(style=[x.text_green, x.number_precision])[42100]
  • row[...] accepts any sequence (numbers, strings, dataclasses…)
  • col[...] stacks values vertically
  • cell[...] wraps a single scalar
  • All primitives accept style=[...]

Component: table

x.table(...) renders a header + body with optional style overrides. Combine with vstack/hstack for dashboards and reports.

sales_table = x.table(
    header_style=[x.text_sm, x.text_gray, x.align_middle],
    style=[x.table_bordered, x.table_compact],
)[
    {"Region": "EMEA", "Units": 1200, "Price": 19.0},
    {"Region": "APAC", "Units": 900, "Price": 21.0},
    {"Region": "AMER", "Units": 1500, "Price": 18.5},
]

layout = x.vstack(
    x.row(style=[x.text_xl, x.bold])["Q3 Sales Overview"],
    x.space(),
    x.hstack(
        sales_table,
        x.cell(style=[x.text_sm, x.text_gray])["Generated with xpyxl"],
        gap=2,
    ),
)

Tables also accept pandas-friendly shapes:

  • records: table()[[{"region": "EMEA", "units": 1200}, ...]] derives the header from dict keys (missing keys are filled with None).
  • dict of lists: table()[{"region": ["EMEA", "APAC"], "units": [1200, 900]}] zips columns together (lengths must match). Headers are inferred from your keys and default to bold text on a muted background; override with header_style=[...] when needed.

Utility styles (non-exhaustive)

  • Typography: text_xs/_sm/_base/_lg/_xl/_2xl/_3xl, bold, italic, mono
  • Text colors: text_red, text_green, text_blue, text_orange, text_purple, text_black, text_gray
  • Backgrounds: bg_red, bg_primary, bg_muted, bg_success, bg_warning, bg_info
  • Layout & alignment: text_left, text_center, text_right, align_top/middle/bottom, wrap, nowrap, wrap_shrink, allow_overflow, row_height(...), row_width(...)
  • Use allow_overflow when you want to keep a column narrow and let the text spill into adjacent empty cells, row_height(32) to force a specific row height, and row_width(12) to pin a column width.
  • Borders: border_all, border_top, border_bottom, border_left, border_right, border_x, border_y, border_red, border_green, border_blue, border_orange, border_purple, border_black, border_gray, border_white, border_muted, border_primary, border_thin, border_medium, border_thick, border_dashed, border_dotted, border_double, border_none
  • Tables: table_bordered, table_banded, table_compact
  • Number/date formats: number_comma, number_precision, percent, currency_usd, currency_eur, date_short, datetime_short, time_short

Border utilities can sit on individual cells or be applied at the row/column level for fast outlines.

Mix and match utilities freely—what you see is what you get.

Layout helpers

  • vstack(a, b, c, gap=1, style=[x.border_all]) vertically stacks components with optional blank rows and shared styles (great for card-like borders).
  • hstack(a, b, gap=1, style=[x.border_all]) arranges components side by side with configurable column gaps and shared wrapper styles.
  • space(rows=1, height=None) inserts empty rows (optionally with a fixed height) in a vstack or empty columns when dropped into an hstack.
  • sheet(name, background_color="#F8FAFC") sets a sheet-wide background fill; the first ~200 rows and 80 columns are painted so the grid feels cohesive.

Examples & Tests

The tests/ directory contains example modules that demonstrate various features of xpyxl. Each module exports a build_workbook() function (or build_sample_workbook() for multi-sheet examples) that returns a SheetNode or list of SheetNode objects.

  • Multi-sheet sales demo: tests/multi_sheet_sales_demo.py - showcases tables, stacks, spacing, and utility styles across multiple sheets.
  • Border styles demo: tests/border_styles_demo.py - demonstrates border utilities at cell, row, and column levels.
  • Wrap styles demo: tests/wrap_styles_demo.py - shows text wrapping and overflow utilities.
  • Row height demo: tests/row_height_demo.py - examples of manual row height and width controls.
  • Big table demo: tests/big_table_demo.py - performance test with a 1k-row table.

Running Tests

Run all test modules to generate combined Excel files with both rendering engines:

uv run scripts/run_tests.py

This will:

  • Collect sheets from all test modules
  • Combine them into a single workbook
  • Generate two output files in .testing/:
    • combined-output-openpyxl.xlsx (rendered with openpyxl engine)
    • combined-output-xlsxwriter.xlsx (rendered with xlsxwriter engine)

Each test module contributes one or more sheets to the combined workbook, allowing you to compare rendering output between engines.

Types & ergonomics

  • Modern Python with full type hints.
  • Pure Python stack traces; easy to debug, script, and test.
  • Deterministic rendering for stable diffs in CI.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xpyxl-0.5.3.tar.gz (23.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xpyxl-0.5.3-py3-none-any.whl (23.4 kB view details)

Uploaded Python 3

File details

Details for the file xpyxl-0.5.3.tar.gz.

File metadata

  • Download URL: xpyxl-0.5.3.tar.gz
  • Upload date:
  • Size: 23.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for xpyxl-0.5.3.tar.gz
Algorithm Hash digest
SHA256 eed27819685a1ff0c09b35e71505e0e0e14f08fee14f1918ed62cef3de3aaf8f
MD5 2081c1f77ec80f4b19a0ceca221d120b
BLAKE2b-256 41b2a718c922b5b5904b8faa3a1c6c446c0c0b387f044d6a1f96b348c56e6b79

See more details on using hashes here.

Provenance

The following attestation bundles were made for xpyxl-0.5.3.tar.gz:

Publisher: publish.yml on dakixr/xpyxl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xpyxl-0.5.3-py3-none-any.whl.

File metadata

  • Download URL: xpyxl-0.5.3-py3-none-any.whl
  • Upload date:
  • Size: 23.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for xpyxl-0.5.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ef50dafd9d0e945b9528ee41f13397eaab28219106176fcb72b7ffe3da3452fd
MD5 a4daee29afb4a7f258620d0c27f28d53
BLAKE2b-256 0da89b81d77204dd1311842b8f7b15deeb1fa8362096f9f6a0808aa26f8146a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for xpyxl-0.5.3-py3-none-any.whl:

Publisher: publish.yml on dakixr/xpyxl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page