openpyxl NamedStyle fonts, fills and freeze panes: working versions (tested 2026-10-16)
Build an xlsx report with reusable NamedStyles for a header font and fill plus a currency number format, pin the header row with freeze panes, and verify it all survives a save and load round trip.
passed6 passed · 0 failed2026-10-16 09:57 UTC- Status
- passed
- Last passed
- 2026-10-16 09:57 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38883899161
The code
Exactly the files that passed. Your agent can pull them with get_recipe("openpyxl-styles-named-styles") (install), or see them on GitHub.
openpyxl==3.1.5 pytest==9.1.1
from io import BytesIO
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Alignment, Font, NamedStyle, PatternFill
def header_style() -> NamedStyle:
return NamedStyle(
name="Header",
font=Font(bold=True, color="FFFFFF"),
fill=PatternFill("solid", fgColor="4472C4"),
alignment=Alignment(horizontal="center"),
)
def money_style() -> NamedStyle:
return NamedStyle(name="Money", number_format="#,##0.00")
def build_report(rows):
wb = Workbook()
ws = wb.active
ws.title = "Report"
wb.add_named_style(header_style())
wb.add_named_style(money_style())
ws.append(["item", "amount"])
for cell in ws[1]:
cell.style = "Header"
for item, amount in rows:
ws.append([item, amount])
ws.cell(row=ws.max_row, column=2).style = "Money"
ws.freeze_panes = "A2"
return wb
def dumps(wb) -> bytes:
bio = BytesIO()
wb.save(bio)
return bio.getvalue()
def loads(data) -> Workbook:
return load_workbook(BytesIO(data))
import pytest
from openpyxl import Workbook
from openpyxl.styles import Font
from report import build_report, dumps, header_style, loads
def test_header_named_style_applied():
ws = build_report([("widget", 1234.5)])["Report"]
top = ws["A1"]
assert top.style == "Header"
assert top.font.bold
assert top.font.color.rgb == "00FFFFFF"
assert top.fill.fill_type == "solid"
assert top.fill.fgColor.rgb == "004472C4"
def test_money_style_and_freeze_panes_survive_roundtrip():
ws = loads(dumps(build_report([("widget", 1234.5)])))["Report"]
assert ws.freeze_panes == "A2"
amount = ws["B2"]
assert amount.style == "Money"
assert amount.number_format == "#,##0.00"
assert amount.value == 1234.5
def test_unregistered_style_name_raises():
ws = Workbook().active
with pytest.raises(ValueError):
ws["A1"].style = "Header"
def test_duplicate_style_name_rejected():
wb = Workbook()
wb.add_named_style(header_style())
with pytest.raises(ValueError):
wb.add_named_style(header_style())
def test_freeze_panes_a1_means_no_freeze():
ws = Workbook().active
ws.freeze_panes = "B2"
assert ws.freeze_panes == "B2"
ws.freeze_panes = "A1"
assert ws.freeze_panes is None
def test_mutating_style_does_not_restyle_cells():
wb = Workbook()
style = header_style()
wb.add_named_style(style)
ws = wb.active
ws["A1"] = "x"
ws["A1"].style = "Header"
style.font = Font(bold=True, italic=True)
assert ws["A1"].font.italic is not True
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
openpyxl worksheet rows validated into pydantic models offline
Read xlsx rows from openpyxl into pydantic v2 models, handling None cells, whole-float to int coercion, whitespace, ignored extra columns and row-numbered errors, all tested offline.
pandas DataFrame to formatted Excel report with openpyxl styles and roundtrip read
Write a DataFrame to a styled .xlsx with openpyxl (header styles, number formats, widths, freeze panes, filters), then read it back with pandas and verify, fully offline.