Python API¶
Every entry on this page is generated from the docstrings in the
wordlive
package, so it stays in sync with the code. If something looks thin, the fix
is in the source docstring, not here.
The public surface is small on purpose. Three rough layers:
- Connect —
attach/connectreturn aWordhandle. - Address —
DocumentexposesBookmark,ContentControl, andHeadinganchors, plusanchor_by_idfor unified addressing. - Mutate — wrap writes in
Document.edit()→EditScopefor atomic undo and Selection preservation.
See Concepts for the why behind these shapes.
Connecting to Word¶
wordlive.attach ¶
Attach to an already-running Word instance.
Raises WordNotRunningError if no instance is available. Does not launch
Word and does not close it on exit.
Source code in src/wordlive/_app.py
wordlive.connect ¶
Attach to a running Word, or launch a new one if missing.
With launch_if_missing=False this behaves like attach(). Wordlive never
closes Word on exit — even when it launched the instance itself, the user
is expected to own its lifecycle.
Source code in src/wordlive/_app.py
wordlive.Word ¶
Documents¶
wordlive.Document ¶
Wraps a Word Document COM object.
Source code in src/wordlive/_document.py
tables
property
¶
Iterable, indexable view over the document's tables.
Index by 1-based position (doc.tables[1]) or Title
(doc.tables["Budget"]). Cells are anchors: doc.tables[1].cell(2, 3)
— or doc.anchor_by_id("table:1:2:3") — returns a Cell that works
with set_text, apply_style, and format_paragraph.
headings
property
¶
Iterable view over the document's headings.
Symmetric with bookmarks, content_controls, and styles. Index by
visible text (doc.headings["Risks"]) or 1-based paragraph position
(doc.headings[3]). Document.heading(name) remains as sugar for
self.headings[name].
paragraphs
property
¶
Indexable, iterable view over every paragraph (not just headings).
Index by 1-based position (doc.paragraphs[2]) to get a Paragraph
anchor (para:N) that works with set_text, apply_style,
format_paragraph, and the list verbs. doc.paragraphs.list() emits
offsets, so a body paragraph can be turned into a range:START-END
target for a mid-paragraph insertion. para:N shares its index space
with heading:N.
lists
property
¶
Read-only, iterable view over the document's bullet / numbered lists.
Index a list by 1-based position (doc.lists[2]) to get a
RangeAnchor over its range, so every list verb
(apply_list, restart_numbering, …) is available on it. List
formatting itself is applied through any anchor's apply_list(...).
sections
property
¶
Indexable view over the document's sections, headers, and footers.
doc.sections[1].header() / .footer() return HeaderFooter anchors
(addressed header:S:WHICH / footer:S:WHICH) that work with
set_text / apply_style like any other anchor.
comments
property
¶
Iterable, indexable view over the document's review comments.
doc.comments.add(anchor, text, author=...) attaches a comment to any
anchor's range without changing the text — the polite, side-channel way
to flag something. Index existing comments by 1-based position
(doc.comments[2]) to resolve() or delete() them.
start
property
¶
An anchor at the very start of the document — the prepend target.
The mirror of end. doc.start (anchor id
start, also anchor_by_id("start")) names the position before the
first paragraph; its insert verbs all prepend —
doc.start.insert_paragraph_after(text) adds a new first paragraph
(delegating to prepend_paragraph)
and insert_after(text) prepends inline (delegating to
prepend). The CLI reaches it too:
wordlive insert --anchor-id start --text "…".
end
property
¶
An anchor at the very end of the document — the append target.
doc.end (anchor id end, also anchor_by_id("end")) names the one
position no content names: past the last paragraph. Its insert verbs
all append — doc.end.insert_paragraph_after(text) adds a new final
paragraph (delegating to append_paragraph),
insert_after(text) appends inline (delegating to
append), and insert_image(...) drops a
picture at the end. Because it resolves through anchor_by_id, the CLI
reaches it too: wordlive insert --anchor-id end --text "…".
track_changes
property
writable
¶
Whether Word's Track Changes is currently on for this document.
tracked_changes ¶
Turn on Track Changes for the duration of the block, then restore it.
Every mutation made inside the scope is recorded as a tracked revision
the user can accept or reject — "make this edit visibly." The prior
TrackRevisions setting is restored on exit, so the scope stays polite
even when the user had tracking off.
Pairs with edit() for an atomic, visibly-tracked batch:
with doc.tracked_changes(), doc.edit("Suggest rewordings"):
doc.find_replace("utilise", "use", all=True)
Source code in src/wordlive/_document.py
add_table ¶
add_table(rows: int, cols: int, *, style: str | None = None, data: list[list[Any]] | None = None, header: bool = False) -> Table
Append a rows × cols table at the end of the document and return it.
The "build a document from the bottom up" helper for tables — the
counterpart to append_paragraph.
Sugar for self.end.insert_table(...); see
Anchor.insert_table for the full
semantics of style (defaults to the built-in "Table Grid"), data
(row-major fill, validated up front), and header. To place a table
somewhere other than the end, resolve a position anchor and call
insert_table on it directly (e.g.
doc.headings["Pricing"].insert_table(3, 2, ...)). Wrap in
doc.edit(...) for atomic undo.
Source code in src/wordlive/_document.py
range ¶
Return a RangeAnchor over the absolute offsets [start, end).
Offsets are UTF-16 code units — the coordinates Word uses and that
find() emits as range:START-END. Lazy: the offsets aren't validated
against the document until the anchor is used.
Source code in src/wordlive/_document.py
anchor_by_id ¶
Resolve an anchor_id string into an Anchor.
Recognised forms
start— the position before the first paragraph (the prepend target)end— the position past the last paragraph (the append target)heading:N— Nth paragraph in the document (1-based, must be a heading)para:N— Nth paragraph (1-based, any paragraph; same index space asheading:N)bookmark:NAME— bookmark by namecc:NAME— content control by Title (or Tag)table:N:R:C— cell at 1-based (row, column) of the Nth tablerange:START-END— arbitrary character span (the formfind()emits)header:S:WHICH— the WHICH header of section S (WHICH = primary/first/even)footer:S:WHICH— the WHICH footer of section S
The bare table:N form is not an anchor (a whole table is a collection,
not a single range) — use doc.tables[N] instead.
Raises AnchorNotFoundError for unknown schemes or missing anchors.
Source code in src/wordlive/_document.py
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 | |
find ¶
Locate every fuzzy occurrence of text within scope (or the whole doc).
Matching is whitespace- and Unicode-normalized (NFKC, smart quotes,
dashes, NBSP). Returns a list of {anchor_id, start, end, text} where
offsets are absolute document positions and text is the actual
original substring (not the normalized form).
anchor_id for each match is range:START-END, which resolves through
anchor_by_id to a RangeAnchor — so a hit can be fed straight back
into replace --anchor-id or comments.add. The offsets are live,
though, so use them before further edits shift the document.
Matches are located per segment (contiguous body text or a single table
cell) so the returned offsets stay exact even inside tables; see
_scope_segments.
Source code in src/wordlive/_document.py
find_replace ¶
find_replace(find: str, replace: str, *, scope: Anchor | None = None, all: bool = False, occurrence: int | None = None) -> list[dict[str, Any]]
Fuzzy plain-text replace. See find() for matching semantics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
find
|
str
|
the text to look for (fuzzy-matched). |
required |
replace
|
str
|
the replacement text. |
required |
scope
|
Anchor | None
|
optional anchor to restrict the search to. Headings expand to their body section. |
None
|
all
|
bool
|
replace every match. |
False
|
occurrence
|
int | None
|
1-based index — replace only the Nth match. |
None
|
Raises:
| Type | Description |
|---|---|
AnchorNotFoundError
|
zero matches (uses |
AmbiguousMatchError
|
more than one match and neither |
Returns the list of replacements actually applied, each
{anchor_id, start, end, text} in their pre-replacement coordinates.
Matching is segment-aware (see _scope_segments), so a match inside a
table cell resolves to the right cell rather than drifting into its
neighbour. As a backstop, each write is verified against the located text
and raises ReplaceVerificationError rather than overwriting the wrong
span.
Source code in src/wordlive/_document.py
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 | |
prepend ¶
Prepend text to the very start of the document, inline (no new paragraph).
The mirror of append: text lands before
the document's first character, joining the opening paragraph. Embed
\r / \n for your own paragraph breaks; reach for
prepend_paragraph when you want
text to become a new first paragraph. Wrap in doc.edit(...) for
atomic undo. Not idempotent — each call adds more text.
Source code in src/wordlive/_document.py
prepend_paragraph ¶
Prepend text as a new paragraph at the very start of the document.
The mirror of append_paragraph
— for a title, a banner, or a disclaimer above everything else. text
may contain \r / \n to prepend several paragraphs at once. If
style is given it must name a style defined in the document, otherwise
StyleNotFoundError is raised before any text is inserted. Wrap in
doc.edit(...) for atomic undo. Not idempotent.
Equivalent to insert_paragraph_before(text, style=style) on the
document's first paragraph.
Source code in src/wordlive/_document.py
append ¶
Append text to the very end of the document, inline (no new paragraph).
The high-level form of the old doc.com.Content.InsertAfter(...) escape
hatch: text lands immediately after the document's last character,
continuing the final paragraph. Embed \r / \n to introduce your
own paragraph breaks; reach for
append_paragraph when you want
text to become a new paragraph. Wrap in doc.edit(...) for atomic
undo. Not idempotent — each call adds more text.
Source code in src/wordlive/_document.py
append_paragraph ¶
Append text as a new paragraph at the very end of the document.
The polite, high-level "end of doc" helper — there is no named anchor
for the position past the last paragraph, so this is how you add a
closing note, drop in a generated summary, or build a document from the
bottom up. text may contain \r / \n to append several paragraphs
at once. If style is given it must name a style defined in the
document, otherwise StyleNotFoundError is raised before any text is
inserted. Wrap in doc.edit(...) for atomic undo. Not idempotent —
each call adds another paragraph.
Equivalent to calling insert_paragraph_after(text, style=style) on the
document's last paragraph, without having to locate it first.
Source code in src/wordlive/_document.py
outline ¶
Return all heading paragraphs as [{level, text, anchor_id}, ...].
Source code in src/wordlive/_document.py
snapshot ¶
snapshot(out: str | Path | None = None, *, pages: int | tuple[int, int] | None = None, dpi: int = 150) -> list[Snapshot]
Render document page(s) to PNG so a vision model can see the layout.
Word exports a pixel-faithful PDF of the live document and wordlive rasterises the requested pages — a true WYSIWYG image (real fonts, spacing, page geometry), ideal for iterating on style and formatting.
pages selects what to render: None (default) renders every page,
an int a single 1-based page, and a (start, end) tuple an inclusive
span. Returns one Snapshot per page (so a single
page is a one-element list); read .png for the bytes.
If out is given the image is also written there: a single page to out
itself, multiple pages alongside it as <stem>-p<N><suffix>.
dpi controls resolution; ~150 reads well for a vision model without
bloating the image. Read-only — the document and the user's cursor are
untouched. Requires the snapshot extra (PyMuPDF), else
SnapshotError.
Source code in src/wordlive/_document.py
snapshot_anchor ¶
Render the page(s) an anchor sits on. Backs Anchor.snapshot.
A heading: anchor expands to its whole section (the heading plus the
body beneath it, up to the next same-or-higher heading); any other
anchor renders the page(s) its range spans. See
snapshot for out/dpi semantics and
the return shape.
Source code in src/wordlive/_document.py
edit ¶
Open an atomic-undo / Selection-preserving edit scope.
Source code in src/wordlive/_document.py
go_to ¶
Move the user's Selection to the given anchor (rare — most ops preserve it).
Does NOT open an UndoRecord — cursor moves don't belong on the user's
undo stack. If you want the move to ride along with a batch of edits,
call this inside a doc.edit(...) scope and the surrounding
UndoRecord will still group everything together.
Source code in src/wordlive/_document.py
wordlive.DocumentCollection ¶
Indexable view over open documents.
Source code in src/wordlive/_document.py
list ¶
[{name, path, saved, is_active}, ...] — used by wordlive status.
name is the document's window name (e.g. Report.docx, or
Document1 for one never saved) and is always non-empty so a caller
can confirm which document it is about to edit. saved is whether the
document has an on-disk location yet; path is that full path, or empty
for an unsaved document. The active document is matched by full path
(falling back to name), which is robust when several unsaved documents
share a blank path.
Source code in src/wordlive/_document.py
Anchors¶
Every anchor type inherits apply_style(name), format_paragraph(...),
insert_paragraph_before/after(...), insert_image(...), insert_table(...),
insert_break(...), and the list verbs (apply_list, remove_list,
list_info, restart_numbering, indent_list, outdent_list) from
Anchor, so the same calls work uniformly on bookmarks,
content controls, headings, paragraphs, table cells, header/footer ranges, and
arbitrary range anchors. insert_image accepts a file path, raw bytes, or a
base64 string and embeds the picture; wrap is required ("inline", "auto",
or a float wrap like "square"/"top-bottom"), and block=True places the
image on its own new line rather than in the anchor's text run.
insert_table(rows, cols, …)
creates a new table at the anchor and returns its Table
(append at the end with Document.add_table).
insert_break(kind="page"|"column"|"section_next"|"section_continuous") drops
an explicit break; for a reflow-safe page break tied to a paragraph (e.g. every
Heading 1), pass page_break_before=True to format_paragraph instead.
Every anchor also has snapshot(...), which renders the page(s) it sits on to
PNG (a heading expands to its whole section) — see Snapshots.
wordlive.Anchor ¶
Bases: ABC
Abstract base — subclasses know how to materialise their COM Range.
Concrete subclasses must implement _range() and set_text(). Other
operations (text, insert_before, insert_after, delete,
apply_style, format_paragraph) are derived and inherited as-is.
Source code in src/wordlive/_anchors.py
anchor_id
abstractmethod
property
¶
Stable string identifier for this anchor (e.g. bookmark:Address).
Each anchor kind has its own scheme (bookmark:, cc:, heading:),
so subclasses must declare theirs explicitly — no useful default
exists at this level.
set_text
abstractmethod
¶
insert_paragraph_before ¶
Insert a new paragraph immediately before this anchor's range.
If style is given it must name a style defined in the document;
otherwise StyleNotFoundError is raised before any text is inserted.
Source code in src/wordlive/_anchors.py
insert_paragraph_after ¶
Insert a new paragraph immediately after this anchor's range.
If style is given it must name a style defined in the document;
otherwise StyleNotFoundError is raised before any text is inserted.
When the anchor is (or ends at) the document's final paragraph there is
no position after the terminal paragraph mark to write to — Word
rejects Range(end, end) there with a "value out of range" COM error.
In that case the new paragraph is split in just before the final mark
instead, so appending to the end of a document — the common
"build from scratch" case, where the only paragraph is the last one —
just works.
Source code in src/wordlive/_anchors.py
insert_image ¶
insert_image(image: str | Path | bytes, *, wrap: str, where: str = 'after', block: bool = False, width: float | None = None, height: float | None = None, alt_text: str | None = None, lock_aspect: bool = True) -> None
Insert an image at this anchor (atomic-undo when inside doc.edit()).
image is a file path, raw image bytes, or a base64 string — a str
is treated as a path when it names an existing file, otherwise as
base64. Word embeds the picture (SaveWithDocument=True) and
auto-detects its natural size, so width/height (points) are optional
overrides. alt_text sets the image's accessibility text.
wrap is required — there is no default — so layout intent is always
explicit:
"inline"keeps the image in the text flow (anInlineShape)."auto"floats it: Square when its width is at most half the section's usable text width, else top-and-bottom."square" | "tight" | "through" | "top-bottom" | "front" | "behind"floats it with that wrap type.
where is "after" (default) or "before" the anchor's range.
block places the image in its own new paragraph (reset to Normal)
rather than embedding it in the anchor's text run — so
heading.insert_image(..., wrap="inline", where="before", block=True)
drops the image on its own line above the heading instead of joining
the heading text. Without it, an inline image anchored at a heading lands
mid-run and the heading text trails it on the same line.
Raises ImageSourceError for a missing/unreadable/invalid image and
ValueError for an unknown wrap or where.
Source code in src/wordlive/_anchors.py
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | |
insert_table ¶
insert_table(rows: int, cols: int, *, where: str = 'after', style: str | None = None, data: list[list[Any]] | None = None, header: bool = False) -> Any
Create a rows × cols table at this anchor and return it.
The structural counterpart to insert_image — it creates new
document structure rather than editing existing structure. Returns the
new Table wrapper so create → fill → read closes on
one object; the table's 1-based document index is on .index.
where is "after" (default) or "before" this anchor's range —
so doc.headings["Pricing"].insert_table(...) drops a table just under
a heading, and doc.end.insert_table(...) (i.e.
Document.add_table) appends one.
style names a table style defined in the document (e.g. "Table
Grid"); an unknown name raises StyleNotFoundError before anything is
inserted. style=None applies the built-in "Table Grid" when it's
available, so a table has visible borders by default rather than the
invisible cell gridlines of a styleless table.
data populates the cells at creation: a row-major 2-D list
([[r1c1, r1c2], …]), validated against rows × cols up front
(OpError on overflow). A short or partial data leaves the remaining
cells empty. Filling at creation keeps the whole grid in one atomic
undo and beats a set_cell storm.
header=True bolds the first row as a header. Wrap in doc.edit(...)
for atomic undo. Raises ValueError for an unknown where and
OpError for a non-positive rows/cols or a bad data shape.
Source code in src/wordlive/_anchors.py
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 | |
insert_break ¶
Insert a page, column, or section break at this anchor.
The explicit one-off break — the clean alternative to appending a
paragraph whose text is a literal form-feed. kind is one of:
"page"(default) — a manual page break (the 90% case)."column"— a column break (multi-column layouts)."section_next"— a section break that starts the new section on the next page."section_continuous"— a section break with no page break, so the new section flows on the same page.
Section breaks pair with Document.sections:
each new section gets its own headers/footers and page setup. To make a
style (e.g. every Heading 1) open a new page without a stray break
character, prefer
format_paragraph(page_break_before=True)
instead — it survives reflow.
where is "after" (default) or "before" this anchor's range.
Wrap in doc.edit(...) for atomic undo. Raises ValueError for an
unknown kind or where.
Source code in src/wordlive/_anchors.py
snapshot ¶
Render the page(s) this anchor sits on to PNG — let a model see it.
A heading expands to its whole section; any other anchor renders the
page(s) its range spans. Returns a list of
Snapshot (one per page); pass out to also write
the image(s) to disk. Sugar for
Document.snapshot_anchor; see it
for the full semantics. Requires the snapshot extra (PyMuPDF).
Source code in src/wordlive/_anchors.py
apply_style ¶
Apply the named paragraph or character style to this anchor's range.
Word selects paragraph- vs. character-style behaviour from the style's
own Type; we don't model that distinction. Raises StyleNotFoundError
if the style isn't defined in the document.
Source code in src/wordlive/_anchors.py
format_paragraph ¶
format_paragraph(*, alignment: Any = None, left_indent: float | None = None, right_indent: float | None = None, first_line_indent: float | None = None, space_before: float | None = None, space_after: float | None = None, page_break_before: bool | None = None) -> None
Set paragraph-formatting properties on this anchor's range.
All kwargs are optional; only the ones explicitly passed are written.
Indent and spacing values are in points (Word's native unit for
ParagraphFormat.LeftIndent etc.). alignment accepts a
WdParagraphAlignment enum, its int value, or a string
("left"/"center"/"right"/"justify").
page_break_before=True forces the paragraph to begin on a new page —
the clean way to page-break (e.g. apply it to every Heading 1): it's
a paragraph property that survives reflow and leaves no stray break
character, unlike insert_break.
False clears the property.
Source code in src/wordlive/_anchors.py
apply_list ¶
Turn this anchor's paragraphs into a list.
list_type is "bulleted", "numbered", or "outline" (the three
ListGalleries). By default numbering starts fresh at 1; pass
continue_previous=True to continue from a list immediately above.
Raises ValueError for an unknown list_type.
Source code in src/wordlive/_anchors.py
remove_list ¶
Strip list formatting (bullets / numbers) from this anchor's paragraphs.
list_info ¶
Describe the list this anchor sits in: {type, level, number, string}.
type is "none" when there's no list formatting, otherwise one of
"bulleted", "numbered", "outline", "number-only", or "mixed".
number is the first paragraph's value, string its rendered marker.
Source code in src/wordlive/_anchors.py
restart_numbering ¶
Restart this list's numbering at 1.
Re-applies the range's current list template with "continue previous"
off. Raises ValueError if the range isn't part of a list.
Source code in src/wordlive/_anchors.py
indent_list ¶
outdent_list ¶
wordlive.Bookmark ¶
wordlive.ContentControl ¶
wordlive.Heading ¶
Bases: Anchor
Source code in src/wordlive/_anchors.py
section_range ¶
COM Range covering the body under this heading.
Spans from the end of the heading paragraph to the start of the next
heading whose level is <= this one's (or to the end of the document
if no such heading exists). Excludes the heading paragraph itself.
Source code in src/wordlive/_anchors.py
wordlive.HeadingCollection ¶
Iterable, indexable view over a document's headings.
Symmetric with BookmarkCollection and ContentControlCollection:
for h in doc.headings: # iteration → Heading per heading paragraph
...
doc.headings["Risks"] # by visible text
doc.headings[3] # by 1-based paragraph index
"Risks" in doc.headings # membership
doc.headings.list() # same shape as doc.outline()
Source code in src/wordlive/_anchors.py
list ¶
Same shape as Document.outline() — [{level, text, anchor_id}, ...].
Source code in src/wordlive/_anchors.py
wordlive.Paragraph ¶
Bases: Anchor
A paragraph located by 1-based index over doc.Paragraphs.
para:N addresses any paragraph — body text, headings, list items alike.
heading:N is the same index space narrowed to heading paragraphs, so
para:5 and heading:5 resolve to the same paragraph when paragraph 5 is a
heading. A Paragraph inherits every anchor verb (set_text, apply_style,
format_paragraph, apply_list, insert_paragraph_before/after, …).
Source code in src/wordlive/_anchors.py
wordlive.ParagraphCollection ¶
Indexable, iterable view over every paragraph in the document.
Unlike headings, this includes body paragraphs and list items, not just
heading paragraphs. Index by 1-based position (doc.paragraphs[2]); iterate
for a Paragraph per paragraph. list() emits each paragraph's start /
end offsets, so a body paragraph can be turned into a range:START-END
insertion point for mid-paragraph edits.
Source code in src/wordlive/_anchors.py
at ¶
Return the paragraph whose range contains offset, or None.
Used to map a character offset (e.g. the cursor position) back to a
para:N anchor.
Source code in src/wordlive/_anchors.py
list ¶
Every paragraph as [{index, anchor_id, level, is_heading, start, end, text}, ...].
Source code in src/wordlive/_anchors.py
wordlive.RangeAnchor ¶
Bases: Anchor
An anchor over an arbitrary character range — doc.range(start, end).
Unlike bookmarks/headings/cells, a range anchor names nothing in the
document: it's a pair of absolute character offsets (UTF-16 code units, the
same coordinates Word's Document.Range(start, end) uses and that
Document.find() emits as range:START-END). It's the generic target when
no named anchor exists — feed a find() hit straight into a replace, or
drop a comment on an offset span.
The anchor is ephemeral: offsets resolve live against the document on each
access, so an edit elsewhere that shifts the text can leave it pointing at
the wrong span. Resolve, act, discard. set_text keeps the anchor's own
end in sync with the replacement so chained ops on the same instance stay
consistent.
Source code in src/wordlive/_anchors.py
wordlive.StartAnchor ¶
Bases: Anchor
A zero-width anchor at the very start of the document body — doc.start.
The mirror of EndAnchor: the insertion point before
the first paragraph. doc.start returns it and anchor_by_id("start")
resolves it, so "prepend to the document" composes with the usual verbs and
the CLI --anchor-id plumbing.
Only the prepend direction is meaningful at a single start-point, so every
insert verb lands text at the start: insert_paragraph_before /
insert_paragraph_after add a new first paragraph (delegating to
Document.prepend_paragraph), and
insert_before / insert_after / set_text prepend inline (delegating to
Document.prepend). text is always empty and
delete() is a no-op. insert_image and apply_style are inherited: they
resolve to the collapsed start position.
Source code in src/wordlive/_anchors.py
wordlive.EndAnchor ¶
Bases: Anchor
A zero-width anchor at the very end of the document body — doc.end.
The one position no content names: the insertion point past the last
paragraph. doc.end returns it and anchor_by_id("end") resolves it, so
"append to the document" composes with the same verbs and the same CLI
--anchor-id plumbing as every other anchor — no .com drop needed.
Only the append direction is meaningful at a single end-point, so every
insert verb lands text at the end: insert_paragraph_after /
insert_paragraph_before add a new final paragraph (delegating to
Document.append_paragraph), and
insert_after / insert_before / set_text append inline (delegating to
Document.append). text is always empty and
delete() is a no-op — there is no content here to read or remove.
insert_image and apply_style are inherited: they resolve to the
collapsed end position, so an image lands at the end and a style falls on
the final paragraph.
Source code in src/wordlive/_anchors.py
Styles¶
Styles are document-scoped, read-only handles. Document.styles is a
StyleCollection; apply styles to anchors via
Anchor.apply_style.
wordlive.Style ¶
A read-only view onto a single Word style.
Properties access the COM object lazily; nothing is cached so renames or deletions during the session don't return stale data.
Source code in src/wordlive/_styles.py
com
property
¶
Raw COM Style object. Raises StyleNotFoundError if the style is gone.
Tries direct lookup (Styles(name)) first — O(1) on Word's side — and
falls back to iteration only if that raises. Membership checking
still iterates (Word doesn't reserve an HRESULT for "missing style"
and a generic com_error would be indistinguishable from a real
failure), but once the caller has a Style instance the name is
presumed valid and the direct path is safe.
wordlive.StyleCollection ¶
Indexable, iterable view over a document's styles.
Source code in src/wordlive/_styles.py
list ¶
All styles as {name, type, builtin, in_use} dicts.
Source code in src/wordlive/_styles.py
Tables¶
Document.tables is a TableCollection. Index a
table by 1-based position or Title, then read or edit it. A
Cell is an Anchor — its id is
table:N:R:C, so doc.anchor_by_id("table:1:2:3") returns a cell that works
with set_text, apply_style, and format_paragraph like any other anchor.
Create tables with Document.add_table(rows, cols, …)
(append at the end) or Anchor.insert_table(...) (at any
position anchor); both return the new Table, populate cells
from a row-major data grid, default to the Table Grid style, and keep
appended tables from merging into an adjacent one. Table.delete() removes a
whole table — the structural mirror of add_row / delete_row.
wordlive.TableCollection ¶
Indexable, iterable view over a document's tables.
Index by 1-based position (doc.tables[1]) or by the table's Title
(doc.tables["Budget"]). Positions match Word's own Tables(n) ordering —
document order, top to bottom.
Source code in src/wordlive/_tables.py
wordlive.Table ¶
Wraps a Word Table COM object, located by its 1-based document position.
The index is stored at construction (the collection knows it without a COM
round-trip), so anchor_id and cell ids never have to re-scan the document.
Source code in src/wordlive/_tables.py
cell ¶
Return the Cell at 1-based (row, col).
Raises AnchorNotFoundError (kind "table cell") if the coordinates
fall outside the table's grid.
Source code in src/wordlive/_tables.py
grid ¶
All cell text as a row-major list[list[str]].
read ¶
Structured dump: metadata plus every cell with its addressable id.
Each cell carries its anchor_id (table:N:R:C) so a caller can feed
it straight back into replace / style apply / format-paragraph.
Source code in src/wordlive/_tables.py
to_dict ¶
Metadata only — {index, title, rows, columns}. Used by table list.
add_row ¶
Append a row at the end of the table, optionally filling its cells.
values are matched to columns left-to-right; extras past the column
count are ignored, short lists leave trailing cells empty.
Source code in src/wordlive/_tables.py
delete_row ¶
Delete the 1-based row index.
Raises AnchorNotFoundError (kind "table row") if out of range.
Source code in src/wordlive/_tables.py
delete ¶
Delete this entire table — the structural mirror of add_row.
Removes the table and all its cells from the document. Afterwards this
Table (and any Cell anchors derived from it) is stale; the indices
of any tables that followed it shift down by one, so re-resolve through
doc.tables before addressing another.
Source code in src/wordlive/_tables.py
wordlive.Cell ¶
Bases: Anchor
A single table cell, addressed by 1-based (row, column).
Subclasses Anchor, so it inherits insert_before / insert_after /
delete / apply_style / format_paragraph unchanged. Only the bits that
differ for cells — the COM range, text read/write, and the anchor id — are
overridden here.
Source code in src/wordlive/_tables.py
Comments¶
Document.comments is a CommentCollection.
comments.add(anchor, text, author=...) attaches a review comment to any
anchor's range without changing the text — the polite, side-channel way for
an agent to flag something. Existing comments are addressed by 1-based index
(doc.comments[2]) to resolve() or delete().
wordlive.CommentCollection ¶
Indexable, iterable view over a document's review comments.
Source code in src/wordlive/_comments.py
add ¶
Attach a new comment to anchor's range.
anchor is any wordlive anchor (bookmark, heading, cell, range, …); its
COM range becomes the comment's scope and the document text is left
untouched — only an annotation is added. Returns the new Comment.
Source code in src/wordlive/_comments.py
wordlive.Comment ¶
A single review comment, located by its 1-based document index.
Source code in src/wordlive/_comments.py
scope_text
property
¶
The document text the comment is attached to (its anchored range).
resolve ¶
reopen ¶
delete ¶
to_dict ¶
{index, author, text, scope, done} — the JSON shape list() emits.
Source code in src/wordlive/_comments.py
Track Changes¶
Document.tracked_changes() is a context manager that turns Word's Track
Changes on for the scope and restores the prior setting on exit — pair it with
edit() to make a batch of edits visibly, as revisions the user can accept or
reject. Document.track_changes is the underlying read/write property for the
persistent flag. Both are documented on Document.
Lists & numbering¶
List operations apply to a range's paragraphs, so the verbs live on
Anchor — apply_list("numbered"), remove_list(),
list_info(), restart_numbering(), and indent_list() / outdent_list()
work on any anchor. Document.lists is a read-only
ListCollection for discovering the lists already in
the document; index it (doc.lists[2]) to get a
RangeAnchor over a list's range.
wordlive.ListCollection ¶
Read-only, iterable view over the document's lists (doc.lists).
Index a list by 1-based position (doc.lists[2]) to get a
RangeAnchor over its whole range — so every list
verb (apply_list, restart_numbering, …) is immediately available on it.
list() returns a summary per list; positions match Word's own
Document.Lists(n) ordering.
Source code in src/wordlive/_lists.py
list ¶
All lists as {index, type, count, anchor_id} dicts.
Source code in src/wordlive/_lists.py
Sections, headers & footers¶
Document.sections is a SectionCollection. Each
Section reaches its headers and footers as
HeaderFooter anchors — doc.sections[1].header() /
.footer("first") — addressed header:S:WHICH / footer:S:WHICH (WHICH is
primary / first / even). A HeaderFooter is an Anchor, so
set_text, apply_style, and format_paragraph work on it like any other.
wordlive.SectionCollection ¶
Indexable, iterable view over a document's sections (doc.sections).
Index by 1-based position (doc.sections[1]). Every document has at least
one section; doc.sections[1].header() is the common entry point.
Source code in src/wordlive/_sections.py
wordlive.Section ¶
Wraps a Word Section, located by its 1-based document position.
Source code in src/wordlive/_sections.py
header ¶
The section's header for which (primary / first / even).
footer ¶
The section's footer for which (primary / first / even).
page_setup ¶
Read-only {orientation, *_margin, page_width, page_height} in points.
Source code in src/wordlive/_sections.py
to_dict ¶
wordlive.HeaderFooter ¶
Bases: Anchor
A section's header or footer, addressed as header:S:WHICH / footer:S:WHICH.
Subclasses Anchor, so text, set_text, insert_before/after,
apply_style, and format_paragraph all work unchanged — only the COM
range and anchor id are overridden here. WHICH is primary, first, or
even.
Source code in src/wordlive/_sections.py
Editing¶
Selection is the explicit cursor surface: doc.selection.info() reads where
the cursor is, and doc.selection.write(text, replace=...) types at it.
write deliberately moves the cursor, so wrap it in
doc.edit() and call
scope.allow_cursor_move() for atomic undo without
snapping the cursor back. Everywhere else, prefer anchors over the cursor.
wordlive.EditScope ¶
Wraps a Word UndoRecord + a Selection snapshot.
One Ctrl-Z reverts every mutation made inside the with block. The
user's cursor and scroll position are restored on exit unless code inside
the scope calls allow_cursor_move().
Source code in src/wordlive/_edit.py
wordlive.Selection ¶
Wrapper around Application.Selection. Mostly used for reads.
Source code in src/wordlive/_selection.py
info ¶
Structured snapshot of the current selection for wordlive reads.
collapsed is true when there's an insertion point but no selected
text (start == end). The CLI's cursor read enriches this with the
containing para:N anchor.
Source code in src/wordlive/_selection.py
write ¶
Insert text at the user's cursor — the deliberate cursor write.
Unlike every anchor write, this targets the live Selection. With a
spanning selection and replace=True (the default) the selected text is
overwritten; with replace=False, or a collapsed cursor, the text is
inserted at the selection start. Either way the cursor is left after
the inserted text.
This intentionally moves the cursor, so it fights EditScope's
cursor-preservation. To get atomic undo without snapping the cursor
back, wrap it: ::
with doc.edit("type at cursor") as scope:
scope.allow_cursor_move()
doc.selection.write("…")
Source code in src/wordlive/_selection.py
wordlive.SelectionSnapshot
dataclass
¶
A point-in-time capture of where the user's cursor and view are.
vertical_percent
class-attribute
instance-attribute
¶
ActiveWindow.VerticalPercentScrolled at snapshot time, or None if unavailable.
Snapshots¶
Document.snapshot(...) and
Anchor.snapshot(...) render page(s) of the live document
to PNG so a vision model can see the layout — Word exports a pixel-faithful
PDF and wordlive rasterises the requested pages. Document.snapshot selects
pages (all, one, or a span); Anchor.snapshot (and
Document.snapshot_anchor) renders the page(s) an anchor
occupies, expanding a heading to its whole section. Both return a list of
Snapshot (one per page) and optionally write the image(s) to out. This needs
the optional snapshot extra (PyMuPDF); a missing backend raises
SnapshotError.
import wordlive as wl
with wl.attach() as word:
doc = word.documents.active
png = doc.heading("Introduction").snapshot()[0].png # bytes for a model
doc.snapshot("report.png", pages=(1, 3)) # write pages 1-3
wordlive.Snapshot
dataclass
¶
One rendered page of a document.
page is the 1-based document page number; png is the PNG-encoded image
bytes — feed it straight to a vision model, or write it yourself. path is
where the image was written when a snapshot(out=...) call saved it to disk,
otherwise None.
Constants¶
wordlive.constants re-exports the typed IntEnum mirrors of the Word Wd*
magic numbers wordlive uses internally (alignment, break types, wrap types,
…). You rarely need these directly — the high-level API takes plain strings
("center", "page", "square") and maps them — but they're available for
.com escape-hatch code that talks to the raw object model.
Exceptions¶
wordlive.WordliveError ¶
Bases: Exception
Base class for all wordlive errors.
wordlive.WordNotRunningError ¶
Bases: WordliveError
No running Word instance is available.
wordlive.DocumentNotFoundError ¶
Bases: WordliveError
The requested document is not open in Word.
Source code in src/wordlive/exceptions.py
wordlive.AnchorNotFoundError ¶
Bases: WordliveError
The requested anchor (bookmark / content control / heading) does not exist.
Source code in src/wordlive/exceptions.py
wordlive.StyleNotFoundError ¶
Bases: AnchorNotFoundError
The requested paragraph or character style is not defined in the document.
Subclass of AnchorNotFoundError so it shares the same exit code (2) and so
except AnchorNotFoundError catches both bookmark-misses and style-misses.
Retryable after re-reading doc.styles.list().
Source code in src/wordlive/exceptions.py
wordlive.AmbiguousMatchError ¶
Bases: WordliveError
A find/replace pattern matched more than one occurrence without disambiguation.
Carries the list of matches so callers (notably LLM drivers) can pick an
occurrence index and retry.
Source code in src/wordlive/exceptions.py
wordlive.ReplaceVerificationError ¶
Bases: WordliveError
A resolved replacement target didn't match the located text — refused to write.
Word's table position model diverges from rendered Range.Text offsets, so a
find/replace whose match resolves to the wrong span (historically inside a
table) could silently overwrite a neighbouring cell while returning success.
wordlive verifies each target against the located match before writing and
raises this instead of corrupting the document. If you hit it, re-scope the
replace to the cell anchor (scope=doc.anchor_by_id("table:N:R:C")). Maps to
the generic exit code (1). Not retryable as-is — the same call drifts again.
Source code in src/wordlive/exceptions.py
wordlive.ImageSourceError ¶
Bases: WordliveError
An image given to insert_image couldn't be turned into an embeddable file.
Raised for a missing or unreadable path, malformed base64, or bytes whose format isn't a recognised raster image (PNG/JPEG/GIF/BMP/TIFF). It's a bad-input error — not a "named thing is missing" — so it maps to the generic exit code (1) rather than reusing the anchor-not-found code. Not retryable: fix the input.
Source code in src/wordlive/exceptions.py
wordlive.SnapshotError ¶
Bases: WordliveError
A page/section snapshot couldn't be rendered.
Raised when the optional PDF-rendering backend (PyMuPDF) isn't installed, or
when rasterising the exported PDF fails. The PDF export itself goes through
Word's COM, so a busy/modal Word surfaces as WordBusyError, not this. It's
an environment/dependency problem rather than a "named thing is missing", so
it maps to the generic exit code (1). Fix by installing the extra:
pip install "wordlive[snapshot]" (or uv add "wordlive[snapshot]").
Source code in src/wordlive/exceptions.py
wordlive.WordBusyError ¶
Bases: WordliveError
Word rejected the RPC — typically a modal dialog or a transient busy state.
Retryable in principle; caller decides.
Source code in src/wordlive/exceptions.py
wordlive.ComError ¶
Bases: WordliveError
Generic wrapper for an unclassified pywintypes.com_error.