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, andhstack. - Composable styling: Tailwind-inspired utilities (typography, colors, alignment, number formats) applied via
style=[...]. - Deterministic rendering: Pure-data trees compiled into
.xlsxfiles 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 three rendering engines:
- hybrid (default): Combines xlsxwriter speed for generated sheets with openpyxl for importing existing sheets. Best balance of speed and features.
- openpyxl: Full-featured with comprehensive Excel support. Best for complex workbooks with advanced formatting.
- xlsxwriter: Fast, memory-efficient. Ideal for large datasets and performance-critical applications. Does not support
import_sheet.
Specify the engine when saving:
workbook.save("output.xlsx") # hybrid (default)
workbook.save("output.xlsx", engine="openpyxl") # full-featured
workbook.save("output.xlsx", engine="xlsxwriter") # fast, generation only
Workbook.save accepts a filesystem path, any binary buffer (like io.BytesIO()), or no target to get raw bytes:
import io
from pathlib import Path
buffer = io.BytesIO()
workbook.save(buffer, engine="xlsxwriter")
raw_bytes = workbook.save(engine="openpyxl")
Path("report.xlsx").write_bytes(raw_bytes)
Importing existing sheets
Pull in a static sheet from an existing Excel file:
report = x.workbook()[
x.import_sheet("template.xlsx", "Cover"),
x.sheet("Data", show_gridlines=False)[x.row()["Item", "Value"], x.row()["A", 1]],
]
report.save("with-template.xlsx") # uses hybrid by default (fast + imports)
Imported sheets preserve styles, merges, dimensions, freeze panes, filters, and other properties from the source file. You can override sheet gridlines with show_gridlines= on both sheet(...) and import_sheet(...).
Engine support for import_sheet:
- hybrid (default): Combines xlsxwriter speed for generated sheets with openpyxl for importing. Best balance of speed and features.
- openpyxl: Full support with native fidelity.
- xlsxwriter: Does not support
import_sheet. Usehybridoropenpyxlinstead.
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]
x.cell(style=[x.bold], colspan=3)["Quarterly Summary"]
x.cell(rowspan=2)["Region"]
row[...]accepts any sequence (numbers, strings, dataclasses…)col[...]stacks values verticallycell[...]wraps a single scalarcell(...)also acceptscolspan=androwspan=for generated merged cells- All primitives accept
style=[...]
Generated merged cells work in hybrid, openpyxl, xlsxwriter, and HTML output. Merges are anchored on the cell(...) call:
hero = x.row()[
x.cell(style=[x.text_xl, x.bold, x.text_center], colspan=3)["Q3 Sales Overview"]
]
detail = x.vstack(
x.row()[x.cell(rowspan=2)["Region"], "Q1", "Q2"],
x.row()["EMEA", 1200, 1300],
)
Raw scalar values inside row()[...] remain normal 1x1 cells. Wrap a value with x.cell(...) when it should merge.
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 accept polars/pandas-friendly shapes:
- records:
table()[[{"region": "EMEA", "units": 1200}, ...]]derives header from dict keys - dict of lists:
table()[{"region": ["EMEA", "APAC"], "units": [1200, 900]}]zips columns together
Utility styles
- 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(...) - Borders:
border_all,border_top/bottom/left/right/x/y,border_red/green/blue/...,border_thin/medium/thick,border_dashed/dotted/double/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
Layout helpers
vstack(a, b, c, gap=1, style=[x.border_all])vertically stacks components with optional blank rowshstack(a, b, gap=1, style=[x.border_all])arranges components side by side with configurable gapsspace(rows=1, height=None)inserts empty rows/columnssheet(name, background_color="#F8FAFC")sets a sheet-wide background fill
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xpyxl-0.8.1.tar.gz.
File metadata
- Download URL: xpyxl-0.8.1.tar.gz
- Upload date:
- Size: 39.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b25739f8e74e19665b2794f6c5a6fdf290526e91cfad5fe369351fab9d7de9a2
|
|
| MD5 |
f22dd2e16cf4a7ff73145103d952f6ad
|
|
| BLAKE2b-256 |
d0dafdae5cbb1f7e59272ef9806e46482e62f5576ae5c621adefb981c1d7fda3
|
Provenance
The following attestation bundles were made for xpyxl-0.8.1.tar.gz:
Publisher:
publish.yml on dakixr/xpyxl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xpyxl-0.8.1.tar.gz -
Subject digest:
b25739f8e74e19665b2794f6c5a6fdf290526e91cfad5fe369351fab9d7de9a2 - Sigstore transparency entry: 1147856474
- Sigstore integration time:
-
Permalink:
dakixr/xpyxl@53fb1f0302108e79cc7b5c97680f4c5116d450a1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dakixr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@53fb1f0302108e79cc7b5c97680f4c5116d450a1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file xpyxl-0.8.1-py3-none-any.whl.
File metadata
- Download URL: xpyxl-0.8.1-py3-none-any.whl
- Upload date:
- Size: 36.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33709f4b5efd22d63349307ef9fb520c9752a78072f14f9e921c1dfa1bcad06d
|
|
| MD5 |
e1f11e1004661ff6df8faf1687f941c9
|
|
| BLAKE2b-256 |
9d49c08050ca75a22d0a9a1adc00a88afae07a22010f0ae99c37e6bae65e24bb
|
Provenance
The following attestation bundles were made for xpyxl-0.8.1-py3-none-any.whl:
Publisher:
publish.yml on dakixr/xpyxl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xpyxl-0.8.1-py3-none-any.whl -
Subject digest:
33709f4b5efd22d63349307ef9fb520c9752a78072f14f9e921c1dfa1bcad06d - Sigstore transparency entry: 1147856531
- Sigstore integration time:
-
Permalink:
dakixr/xpyxl@53fb1f0302108e79cc7b5c97680f4c5116d450a1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dakixr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@53fb1f0302108e79cc7b5c97680f4c5116d450a1 -
Trigger Event:
push
-
Statement type: