feat(text): _Paragraph.fields field-discovery accessor (Phase 4)#51
Merged
Conversation
Public Python API for the headers/footers/slide-numbers/dates epic (#20). Phase 4 ships the small read-side companion to Phase 3's authoring API: `_Paragraph.fields` returns the paragraph's `<a:fld>` children wrapped as `_Field` instances in document order. Combined with Phase 3, a user can now `add_field()` to write, save, re-open, and `paragraph.fields[0]` to read or mutate. Why this is small. The heavy lift landed in Phase 3 — `_Field` itself, the OOXML primitives (Phase 1), and the field-aware `content_children` + `_Paragraph.text` (also Phase 3). Phase 4 just exposes the parallel- to-`runs` discovery accessor that the existing `fld_lst` on `CT_TextParagraph` made trivial. Changes: - pptx.text.text._Paragraph.fields — new `@property`, returns `tuple[_Field, ...]` built from `self._element.fld_lst`. Mirrors the shape of `_Paragraph.runs` exactly so the idiom is instantly familiar. Out of scope for Phase 4 (deliberate): - Interleaved ordered iterator combining `_Run` / `_Field` / `_LineBreak` in a single sequence. `content_children` already exposes this at the oxml layer; surfacing as public API can land later if real users ask. - `Slide.has_auto_slide_number` / `has_auto_date` convenience flags — derive from `.fields` if useful; deferred. - HandoutMaster class and watermark helper — Phase 5. Anti-criteria upheld: - `_Paragraph.runs` continues to yield only `_Run` instances. The new test `it_keeps_runs_field_free_on_mixed_paragraphs` regression-pins that on a mixed `(a:r, a:fld, a:r)` paragraph. - `_Paragraph.text` semantics unchanged (still field-inclusive, as Phase 3 made it). - `_Field` class itself is read-only here — no modifications. Verification (local, CPython 3.14.4): - python3 -m pytest tests/ -q → 3632 passed in 5.24s (+6 vs Phase 3 baseline) - python3 -m ruff check src tests → All checks passed! - python3 -m ruff format --check src tests → 216 files already formatted - python3 -m behave features/ --no-color → 1048 scenarios, 0 failed - python3 uat/uat_headers_footers_phase4.py → PASS (opened uat/out_headers_footers_phase3.pptx, discovered fields[0] with type='slidenum' and id={2ED44585-...}, mutated text to "X" via the discovered handle, re-saved + re-opened, round-tripped clean) Refs #20.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 4 of #20 — field discovery accessor
The small read-side companion to Phase 3's authoring API (PR #50). Adds
_Paragraph.fields, a parallel to_Paragraph.runsthat returns the paragraph's<a:fld>children wrapped as_Fieldinstances in document order. Combined with Phase 3, the round-trip story closes:Why this PR is small
The heavy lift landed earlier:
CT_TextFieldelement class withid/typeattrs._Fieldclass,CT_TextParagraph.fldZeroOrMore (gave usfld_lstfor free), field-awarecontent_childrenand_Paragraph.text.Phase 4 is one
@propertybody — wrapself._element.fld_lstin_Fieldinstances and return as a tuple. The existingfld_lstdoes all the work.Anti-criteria upheld
_Paragraph.runscontinues to yield only_Run(regression-pinned byit_keeps_runs_field_free_on_mixed_paragraphs)._Paragraph.textsemantics unchanged (already field-inclusive since Phase 3)._Fieldclass itself unchanged — read-only addition.Verification (local, CPython 3.14.4)
UAT confirms the full Phase 3 → Phase 4 round-trip: opens
uat/out_headers_footers_phase3.pptx, walks to the slidenum field via.fields, mutates its text via the discovered_Fieldhandle, re-saves, re-opens, and reads it back via.fieldsagain — clean round-trip.6 new tests in
tests/text/test_text.pyDescribe_Paragraph:it_returns_an_empty_tuple_of_fields_when_paragraph_has_noneit_provides_access_to_a_single_fieldit_yields_multiple_fields_in_document_orderit_chains_each_field_parent_back_to_the_paragraphit_returns_a_tuple_not_a_listit_keeps_runs_field_free_on_mixed_paragraphs(anti-regression)What's next
Phase 5 closes out the epic with the
HandoutMasterPython class +HandoutMasterPartplumbing, and the watermark helper. After that, issue #20 ships.Refs #20.