python-docx report with styled tables and page breaks: working versions (tested 2026-10-16)
Generate a Word report with python-docx - a title, headings, a styled table and a page break - then reopen the .docx and verify structure, order and styling.
passed6 passed · 0 failed2026-10-16 21:57 UTC- Status
- passed
- Last passed
- 2026-10-16 21:57 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
python-docx@1.2.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38972656245
The code
Exactly the files that passed. Your agent can pull them with get_recipe("python-docx-table-report") (install), or see them on GitHub.
requirements.txt
python-docx==1.2.0 pytest==9.1.1
report.py
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docx.table import Table
from docx.text.paragraph import Paragraph
STYLE = "Light Grid Accent 1"
def build_report(path, title, columns, rows):
doc = Document()
doc.add_heading(title, level=0)
doc.add_heading("Summary", level=1)
doc.add_paragraph(f"{len(rows)} data rows")
table = doc.add_table(rows=1, cols=len(columns))
table.style = STYLE
for cell, name in zip(table.rows[0].cells, columns):
cell.text = name
cell.paragraphs[0].runs[0].bold = True
for row in rows:
for cell, value in zip(table.add_row().cells, row):
cell.text = str(value)
doc.add_page_break()
doc.add_heading("Appendix", level=1)
doc.add_paragraph("Generated by ProvenPin.").alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(str(path))
return doc
def iter_block_items(doc):
# python-docx keeps paragraphs and tables in separate lists, so walk the XML
# body to recover the real document order.
for child in doc.element.body.iterchildren():
if child.tag == qn("w:p"):
yield Paragraph(child, doc)
elif child.tag == qn("w:tbl"):
yield Table(child, doc)
def headings(doc):
return [(p.style.name, p.text) for p in doc.paragraphs if p.style.name.startswith("Heading")]
def has_page_break(paragraph):
return any(
br.get(qn("w:type")) == "page"
for run in paragraph.runs
for br in run._r.findall(qn("w:br"))
)
tests/test_report.py
from docx import Document
from report import build_report, has_page_break, headings, iter_block_items
COLUMNS = ["name", "qty"]
def make(tmp_path):
path = tmp_path / "report.docx"
build_report(path, "Q3 Report", COLUMNS, [["apples", 3], ["pears", 5]])
return Document(str(path))
def test_title_and_headings(tmp_path):
doc = make(tmp_path)
assert doc.paragraphs[0].style.name == "Title"
assert doc.paragraphs[0].text == "Q3 Report"
assert headings(doc) == [("Heading 1", "Summary"), ("Heading 1", "Appendix")]
def test_table_style_rows_and_cells(tmp_path):
table = make(tmp_path).tables[0]
assert table.style.name == "Light Grid Accent 1"
assert len(table.rows) == 3
assert [c.text for c in table.rows[0].cells] == COLUMNS
assert table.cell(0, 0).paragraphs[0].runs[0].bold is True
def test_values_are_stringified(tmp_path):
table = make(tmp_path).tables[0]
assert table.cell(2, 1).text == "5"
def test_body_order_interleaves_tables_and_paragraphs(tmp_path):
kinds = [type(b).__name__ for b in iter_block_items(make(tmp_path))]
assert kinds == ["Paragraph", "Paragraph", "Paragraph", "Table", "Paragraph", "Paragraph", "Paragraph"]
tables = [b for b in iter_block_items(make(tmp_path)) if type(b).__name__ == "Table"]
assert len(tables) == 1
def test_page_break_is_an_empty_paragraph(tmp_path):
doc = make(tmp_path)
broken = [p for p in doc.paragraphs if has_page_break(p)]
assert len(broken) == 1
assert broken[0].text == ""
def test_unknown_table_style_raises_keyerror(tmp_path):
doc = Document()
table = doc.add_table(rows=1, cols=1)
try:
table.style = "No Such Style"
except KeyError:
pass
else:
raise AssertionError("expected KeyError")
pytest.ini
[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.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "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.