Claude Code for python-docx: Create and Edit Word Documents in Python — Claude Skills 360 Blog
Blog / AI / Claude Code for python-docx: Create and Edit Word Documents in Python
AI

Claude Code for python-docx: Create and Edit Word Documents in Python

Published: May 20, 2028
Read time: 5 min read
By: Claude Skills 360

python-docx creates and edits Microsoft Word .docx files. pip install python-docx. New: from docx import Document; doc = Document(). Open: doc = Document("existing.docx"). Heading: doc.add_heading("Title", level=1). Paragraph: p = doc.add_paragraph("Hello world"). Bold run: run = p.add_run(" bold"); run.bold = True. Italic: run.italic = True. Font size: from docx.shared import Pt; run.font.size = Pt(14). Color: from docx.shared import RGBColor; run.font.color.rgb = RGBColor(0xFF,0,0). Table: table = doc.add_table(rows=3, cols=4, style="Table Grid"). Cell: table.cell(0,0).text = "Header". Picture: from docx.shared import Inches; doc.add_picture("logo.png", width=Inches(2)). Page break: doc.add_page_break(). List bullet: doc.add_paragraph("Item", style="List Bullet"). List number: doc.add_paragraph("Step", style="List Number"). Style: p.style = doc.styles["Heading 2"]. Paragraph format: p.paragraph_format.space_after = Pt(6). Alignment: from docx.enum.text import WD_ALIGN_PARAGRAPH; p.alignment = WD_ALIGN_PARAGRAPH.CENTER. Header: section = doc.sections[0]; section.header.paragraphs[0].text = "Report". Footer: section.footer.paragraphs[0].text = "Page". Core props: doc.core_properties.author = "Claude". Save: doc.save("output.docx"). Save to bytes: from io import BytesIO; buf = BytesIO(); doc.save(buf). Claude Code generates python-docx reports, invoices, letters, and branded document templates.

CLAUDE.md for python-docx

## python-docx Stack
- Version: python-docx >= 1.1 | pip install python-docx
- Create: doc = Document() | doc = Document("template.docx")
- Content: doc.add_heading(text, level) | doc.add_paragraph(text, style)
- Runs: p.add_run(text); run.bold=True; run.font.size=Pt(12)
- Table: doc.add_table(rows, cols, style="Table Grid"); table.cell(r,c).text = val
- Save: doc.save("out.docx") | doc.save(BytesIO()) for in-memory bytes

python-docx Document Generation Pipeline

# app/docx_gen.py — python-docx headings, tables, formatting, images, and reports
from __future__ import annotations

import io
from dataclasses import dataclass, field
from datetime import date
from pathlib import Path
from typing import Any

from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
from docx.oxml import OxmlElement


# ─────────────────────────────────────────────────────────────────────────────
# 1. Low-level formatting helpers
# ─────────────────────────────────────────────────────────────────────────────

def set_run_style(
    run,
    bold: bool = False,
    italic: bool = False,
    underline: bool = False,
    size_pt: float | None = None,
    color_rgb: tuple[int, int, int] | None = None,
    font_name: str | None = None,
) -> None:
    """Apply formatting to a paragraph Run."""
    if bold:
        run.bold = bold
    if italic:
        run.italic = italic
    if underline:
        run.underline = underline
    if size_pt is not None:
        run.font.size = Pt(size_pt)
    if color_rgb:
        run.font.color.rgb = RGBColor(*color_rgb)
    if font_name:
        run.font.name = font_name


def add_colored_heading(
    doc: Document,
    text: str,
    level: int = 1,
    color_rgb: tuple[int, int, int] = (0x1F, 0x49, 0x7D),
) -> None:
    """Add a heading with a custom color override."""
    p = doc.add_heading(text, level=level)
    for run in p.runs:
        run.font.color.rgb = RGBColor(*color_rgb)


def add_paragraph(
    doc: Document,
    text: str = "",
    bold: bool = False,
    italic: bool = False,
    size_pt: float | None = None,
    alignment: str = "left",
    space_after_pt: float = 6.0,
    style: str | None = None,
) -> Any:
    """Add a paragraph with optional run-level and paragraph-level formatting."""
    kw = {}
    if style:
        kw["style"] = style
    p = doc.add_paragraph(**kw)

    if text:
        run = p.add_run(text)
        set_run_style(run, bold=bold, italic=italic, size_pt=size_pt)

    align_map = {
        "left":    WD_ALIGN_PARAGRAPH.LEFT,
        "center":  WD_ALIGN_PARAGRAPH.CENTER,
        "right":   WD_ALIGN_PARAGRAPH.RIGHT,
        "justify": WD_ALIGN_PARAGRAPH.JUSTIFY,
    }
    p.alignment = align_map.get(alignment, WD_ALIGN_PARAGRAPH.LEFT)
    p.paragraph_format.space_after = Pt(space_after_pt)
    return p


def set_cell_shading(cell, fill_color: str = "1F497D") -> None:
    """Set cell background color (hex string without #)."""
    tc   = cell._tc
    tcPr = tc.get_or_add_tcPr()
    shd  = OxmlElement("w:shd")
    shd.set(qn("w:val"), "clear")
    shd.set(qn("w:color"), "auto")
    shd.set(qn("w:fill"), fill_color)
    tcPr.append(shd)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Table helpers
# ─────────────────────────────────────────────────────────────────────────────

def add_data_table(
    doc: Document,
    headers: list[str],
    rows: list[list[Any]],
    col_widths_inches: list[float] | None = None,
    header_color: str = "1F497D",
    alternate_row_color: str = "DCE6F1",
    style: str = "Table Grid",
    bold_headers: bool = True,
) -> Any:
    """
    Add a formatted data table.
    header_color / alternate_row_color: hex strings without #.

    Example:
        add_data_table(doc,
            headers=["Name", "Score", "Grade"],
            rows=[["Alice", 95, "A"], ["Bob", 82, "B"]],
            col_widths_inches=[2.5, 1.0, 1.0],
        )
    """
    n_cols = len(headers)
    n_rows = len(rows)
    table  = doc.add_table(rows=1 + n_rows, cols=n_cols, style=style)

    # Header row
    hdr_cells = table.rows[0].cells
    for i, header in enumerate(headers):
        hdr_cells[i].text = str(header)
        run = hdr_cells[i].paragraphs[0].runs[0]
        run.bold = bold_headers
        run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
        set_cell_shading(hdr_cells[i], header_color)

    # Data rows
    for row_idx, row_data in enumerate(rows):
        row_cells = table.rows[1 + row_idx].cells
        for col_idx, val in enumerate(row_data):
            row_cells[col_idx].text = str(val)
            if alternate_row_color and row_idx % 2 == 1:
                set_cell_shading(row_cells[col_idx], alternate_row_color)

    # Column widths
    if col_widths_inches:
        for col_idx, width in enumerate(col_widths_inches):
            for cell in table.columns[col_idx].cells:
                cell.width = Inches(width)

    return table


# ─────────────────────────────────────────────────────────────────────────────
# 3. Header / footer helpers
# ─────────────────────────────────────────────────────────────────────────────

def set_header(
    doc: Document,
    left_text: str = "",
    center_text: str = "",
    right_text: str = "",
    logo_path: str | Path | None = None,
    logo_width_inches: float = 1.0,
) -> None:
    """Set page header with optional left/center/right text and logo."""
    section = doc.sections[0]
    header  = section.header
    p       = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
    p.clear()
    p.alignment = WD_ALIGN_PARAGRAPH.LEFT

    if logo_path and Path(logo_path).exists():
        run = p.add_run()
        run.add_picture(str(logo_path), width=Inches(logo_width_inches))
        p.add_run("  ")

    if left_text:
        p.add_run(left_text)

    if center_text or right_text:
        tab_run = p.add_run()
        if center_text:
            p.add_run(center_text)
        if right_text:
            p.add_run("\t" + right_text)


def set_footer(
    doc: Document,
    text: str = "",
    include_page_number: bool = True,
) -> None:
    """Set page footer with optional page number field."""
    section = doc.sections[0]
    footer  = section.footer
    p       = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
    p.clear()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER

    if text:
        p.add_run(text)
        if include_page_number:
            p.add_run("  —  Page ")
    elif include_page_number:
        p.add_run("Page ")

    if include_page_number:
        # Insert PAGE field
        fld = OxmlElement("w:fldChar")
        fld.set(qn("w:fldCharType"), "begin")
        p.runs[-1]._r.append(fld)
        instrText = OxmlElement("w:instrText")
        instrText.text = " PAGE "
        run = p.add_run()
        run._r.append(instrText)
        fld2 = OxmlElement("w:fldChar")
        fld2.set(qn("w:fldCharType"), "end")
        run._r.append(fld2)


# ─────────────────────────────────────────────────────────────────────────────
# 4. Report generators
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class ReportSection:
    title: str
    body:  str
    table_headers: list[str] = field(default_factory=list)
    table_rows:    list[list[Any]] = field(default_factory=list)


def generate_report(
    title: str,
    subtitle: str = "",
    sections: list[ReportSection] | None = None,
    company: str = "My Company",
    author: str = "Claude Code",
    report_date: date | None = None,
    template_path: str | Path | None = None,
) -> bytes:
    """
    Generate a formatted Word report and return it as bytes.

    Example:
        sections = [
            ReportSection("Summary", "Q1 performance exceeded targets.",
                          table_headers=["Metric", "Target", "Actual"],
                          table_rows=[["Revenue", "$1M", "$1.2M"], ["Churn", "5%", "3.8%"]]),
        ]
        pdf_bytes = generate_report("Q1 Report", company="Acme Corp", sections=sections)
        with open("q1_report.docx", "wb") as f: f.write(pdf_bytes)
    """
    rpt_date = report_date or date.today()

    if template_path and Path(template_path).exists():
        doc = Document(str(template_path))
    else:
        doc = Document()

    # Metadata
    doc.core_properties.author  = author
    doc.core_properties.company = company
    doc.core_properties.title   = title

    # Title page
    add_colored_heading(doc, title, level=1)
    if subtitle:
        add_paragraph(doc, subtitle, size_pt=13, alignment="left", italic=True)

    add_paragraph(doc, f"{company}  ·  {rpt_date.strftime('%B %d, %Y')}", size_pt=10)
    doc.add_page_break()

    # Sections
    for section in sections or []:
        add_colored_heading(doc, section.title, level=2)
        if section.body:
            add_paragraph(doc, section.body, size_pt=11, space_after_pt=8)
        if section.table_headers and section.table_rows:
            add_data_table(doc, section.table_headers, section.table_rows)
            add_paragraph(doc, "")  # spacing after table

    set_footer(doc, company, include_page_number=True)

    buf = io.BytesIO()
    doc.save(buf)
    return buf.getvalue()


@dataclass
class InvoiceItem:
    description: str
    quantity: float
    unit_price: float

    @property
    def total(self) -> float:
        return self.quantity * self.unit_price


def generate_invoice(
    invoice_number: str,
    invoice_date: date,
    due_date: date,
    client_name: str,
    client_address: str,
    items: list[InvoiceItem],
    company: str = "My Company",
    tax_rate: float = 0.10,
    notes: str = "",
) -> bytes:
    """
    Generate a Word invoice.

    Example:
        items = [InvoiceItem("API Integration", 40, 150.0),
                 InvoiceItem("Code Review", 8, 150.0)]
        data = generate_invoice("INV-2024-001", date.today(), date.today(), "Acme", "123 St", items)
    """
    doc = Document()
    doc.core_properties.title  = f"Invoice {invoice_number}"
    doc.core_properties.author = company

    # Header
    title_p = doc.add_paragraph()
    title_p.alignment = WD_ALIGN_PARAGRAPH.LEFT
    run = title_p.add_run(company)
    set_run_style(run, bold=True, size_pt=20, color_rgb=(0x1F, 0x49, 0x7D))

    add_paragraph(doc, f"Invoice #{invoice_number}", bold=True, size_pt=14)
    add_paragraph(doc, f"Date: {invoice_date.strftime('%B %d, %Y')}", size_pt=11)
    add_paragraph(doc, f"Due:  {due_date.strftime('%B %d, %Y')}", size_pt=11)
    add_paragraph(doc, "")

    add_paragraph(doc, "Bill To:", bold=True, size_pt=11)
    add_paragraph(doc, client_name, size_pt=11)
    for line in client_address.split("\n"):
        add_paragraph(doc, line, size_pt=11, space_after_pt=2)
    add_paragraph(doc, "")

    # Line items table
    headers = ["Description", "Qty", "Unit Price", "Total"]
    rows = [[i.description, i.quantity,
             f"${i.unit_price:,.2f}", f"${i.total:,.2f}"] for i in items]
    add_data_table(doc, headers, rows, col_widths_inches=[3.2, 0.6, 1.2, 1.2])
    add_paragraph(doc, "")

    # Totals
    subtotal = sum(i.total for i in items)
    tax      = subtotal * tax_rate
    total    = subtotal + tax
    for label, amount in [
        ("Subtotal", subtotal),
        (f"Tax ({tax_rate*100:.0f}%)", tax),
        ("Total Due", total),
    ]:
        p = add_paragraph(doc, f"{label}: ${amount:,.2f}",
                          bold=(label == "Total Due"),
                          size_pt=11,
                          alignment="right",
                          space_after_pt=3)

    if notes:
        add_paragraph(doc, "")
        add_paragraph(doc, "Notes:", bold=True, size_pt=11)
        add_paragraph(doc, notes, size_pt=11)

    buf = io.BytesIO()
    doc.save(buf)
    return buf.getvalue()


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    from datetime import date

    print("=== Report generation ===")
    sections = [
        ReportSection(
            title="Executive Summary",
            body="Q1 revenue exceeded targets by 20%. Customer retention improved significantly.",
            table_headers=["Metric", "Q1 Target", "Q1 Actual", "Delta"],
            table_rows=[
                ["Revenue",     "$1,000,000", "$1,200,000", "+20%"],
                ["Churn Rate",  "5.0%",       "3.8%",       "-1.2pp"],
                ["NPS Score",   "45",         "52",         "+7"],
            ],
        ),
        ReportSection(
            title="Product Updates",
            body="Three major features shipped: API v2, mobile app redesign, batch processing.",
        ),
    ]
    report_bytes = generate_report(
        "Q1 2024 Business Review",
        subtitle="Confidential — Internal Distribution Only",
        sections=sections,
        company="Acme Corp",
        author="Finance Team",
    )
    Path("/tmp/q1_report.docx").write_bytes(report_bytes)
    print(f"  Report: /tmp/q1_report.docx ({len(report_bytes):,} bytes)")

    print("\n=== Invoice generation ===")
    items = [
        InvoiceItem("API Integration Development", 40, 150.00),
        InvoiceItem("Code Review and Testing",     8,  150.00),
        InvoiceItem("Deployment and Documentation", 4, 100.00),
    ]
    invoice_bytes = generate_invoice(
        "INV-2024-042",
        date(2024, 4, 1),
        date(2024, 4, 30),
        "Acme Corp",
        "123 Business Ave\nSan Francisco, CA 94102",
        items,
        company="Claude Code Agency",
        notes="Net 30. Wire transfer or ACH preferred.",
    )
    Path("/tmp/invoice.docx").write_bytes(invoice_bytes)
    print(f"  Invoice: /tmp/invoice.docx ({len(invoice_bytes):,} bytes)")
    subtotal = sum(i.total for i in items)
    print(f"  Total:   ${subtotal * 1.1:,.2f}")

For the reportlab / weasyprint alternative — reportlab and weasyprint generate PDF output; python-docx generates editable Word .docx files that clients can modify, add comments to, and submit through workflows that expect Word format — use python-docx when deliverables need to remain editable in Microsoft Word, WeasyPrint/fpdf2 when you need a fixed, printable PDF. For the openpyxl / xlsxwriter alternative — openpyxl and xlsxwriter target Excel .xlsx files with spreadsheet structures; python-docx targets Word .docx files with flowing text, headings, and formatted paragraphs — use python-docx for reports, letters, contracts, and invoices; Excel writers for data-heavy tabular output. The Claude Skills 360 bundle includes python-docx skill sets covering set_run_style() for bold/italic/size/color, add_colored_heading() with RGB override, add_paragraph() with alignment and spacing, set_cell_shading() OxmlElement helper, add_data_table() with header color and alternating rows, set_header()/set_footer() with page number field, generate_report() with sections and metadata, generate_invoice() with line items and tax, and BytesIO in-memory save. Start with the free tier to try Word document generation code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free