athena-python-docx 0.15.5__tar.gz → 0.16.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/PKG-INFO +1 -1
  2. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/__init__.py +1 -1
  3. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_buffer.py +29 -29
  4. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_http_doc.py +0 -18
  5. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_timeouts.py +0 -7
  6. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/client.py +2 -2
  7. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/commands.py +5 -0
  8. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/document.py +22 -2
  9. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/paragraph.py +49 -7
  10. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/pyproject.toml +1 -1
  11. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/fake_session.py +7 -0
  12. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_buffer.py +1 -40
  13. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_http_transport.py +1 -68
  14. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_iter_inner_content.py +65 -3
  15. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_ptc.py +91 -15
  16. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/uv.lock +1 -1
  17. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/.gitignore +0 -0
  18. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/CLAUDE.md +0 -0
  19. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/DOCX_EXEC_LAB.md +0 -0
  20. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/README.md +0 -0
  21. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_athena_extension.py +0 -0
  22. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_batching.py +0 -0
  23. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_execution.py +0 -0
  24. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_http.py +0 -0
  25. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_image_utils.py +0 -0
  26. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_postproc.py +0 -0
  27. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_ptc.py +0 -0
  28. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_table_styles.py +0 -0
  29. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/api.py +0 -0
  30. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/bookmarks.py +0 -0
  31. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/charts.py +0 -0
  32. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/comments.py +0 -0
  33. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/__init__.py +0 -0
  34. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/section.py +0 -0
  35. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/style.py +0 -0
  36. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/table.py +0 -0
  37. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/text.py +0 -0
  38. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/errors.py +0 -0
  39. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/exceptions.py +0 -0
  40. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/fields.py +0 -0
  41. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/footnotes.py +0 -0
  42. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/math.py +0 -0
  43. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/opc/__init__.py +0 -0
  44. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/opc/coreprops.py +0 -0
  45. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/oxml/__init__.py +0 -0
  46. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/revisions.py +0 -0
  47. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/sdt.py +0 -0
  48. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/section.py +0 -0
  49. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/session.py +0 -0
  50. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/settings.py +0 -0
  51. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/shape.py +0 -0
  52. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/shared.py +0 -0
  53. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/styles/__init__.py +0 -0
  54. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/styles/style.py +0 -0
  55. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/styles/styles.py +0 -0
  56. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/table.py +0 -0
  57. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/__init__.py +0 -0
  58. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/font.py +0 -0
  59. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/hyperlink.py +0 -0
  60. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/pagebreak.py +0 -0
  61. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/parfmt.py +0 -0
  62. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/run.py +0 -0
  63. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/tabstops.py +0 -0
  64. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/toc.py +0 -0
  65. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/typing.py +0 -0
  66. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab.py +0 -0
  67. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
  68. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
  69. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab_server.py +0 -0
  70. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/publish.sh +0 -0
  71. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/release.sh +0 -0
  72. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/round_trip_smoke.py +0 -0
  73. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/smoke_test_block_not_found.py +0 -0
  74. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/validate_find_replace_asset.py +0 -0
  75. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/__init__.py +0 -0
  76. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/conftest.py +0 -0
  77. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/METHODOLOGY.md +0 -0
  78. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/README.md +0 -0
  79. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/__init__.py +0 -0
  80. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/ab_probe_cases.py +0 -0
  81. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/ab_probe_runner.py +0 -0
  82. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/auto_gen_cases.py +0 -0
  83. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/binary_round_trip.py +0 -0
  84. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/cases.py +0 -0
  85. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/complex_cases.py +0 -0
  86. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/coverage_report.py +0 -0
  87. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/extract.py +0 -0
  88. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/extreme_cases.py +0 -0
  89. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/README.md +0 -0
  90. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/__init__.py +0 -0
  91. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/_runner.py +0 -0
  92. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/extractor.py +0 -0
  93. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  94. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  95. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/local_runner.py +0 -0
  96. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/mega_cases.py +0 -0
  97. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshot.py +0 -0
  98. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  99. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  100. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  101. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  102. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  103. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  104. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  105. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  106. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  107. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  108. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  109. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  110. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  111. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  112. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  113. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  114. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  115. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  116. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  117. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  118. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  119. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  120. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  121. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  122. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  123. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  124. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  125. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  126. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  127. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  128. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  129. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  130. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  131. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  132. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  133. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  134. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  135. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  136. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  137. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  138. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  139. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  140. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  141. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  142. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  143. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  144. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  145. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  146. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  147. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  148. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  149. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  150. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  151. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  152. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  153. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  154. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  155. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  156. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  157. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  158. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  159. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  160. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  161. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  162. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  163. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  164. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  165. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  166. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  167. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  168. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  169. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  170. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  171. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  172. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  173. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  174. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  175. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  176. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  177. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  178. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  179. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  180. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  181. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  182. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  183. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  184. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  185. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  186. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  187. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  188. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  189. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  190. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  191. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  192. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  193. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  194. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  195. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  196. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  197. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  198. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  199. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  200. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  201. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  202. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  203. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  204. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  205. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  206. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  207. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  208. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  209. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  210. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  211. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  212. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  213. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  214. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  215. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  216. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  217. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  218. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  219. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  220. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  221. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  222. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  223. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  224. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  225. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  226. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  227. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  228. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  229. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  230. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  231. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  232. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  233. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  234. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  235. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  236. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  237. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  238. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  239. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  240. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  241. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  242. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  243. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  244. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  245. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/ours_spec.json +0 -0
  246. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/parity_crawl.py +0 -0
  247. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/parity_diff.json +0 -0
  248. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/real_world_cases.py +0 -0
  249. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/round_trip_tests.py +0 -0
  250. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/runner.py +0 -0
  251. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/stock_spec.json +0 -0
  252. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/test_e2e_against_staging.py +0 -0
  253. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/README.md +0 -0
  254. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/__init__.py +0 -0
  255. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/baseline_gaps.json +0 -0
  256. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/compare.py +0 -0
  257. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/intentional_deviations.json +0 -0
  258. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/introspect.py +0 -0
  259. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  260. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/reports/gap_report.json +0 -0
  261. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/run_parity.py +0 -0
  262. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/snapshots/athena_latest.json +0 -0
  263. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  264. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/test_parity_gap.py +0 -0
  265. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_add_section_extract_items.py +0 -0
  266. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_athena_extensions_contract.py +0 -0
  267. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_athena_extensions_registry.py +0 -0
  268. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_batching_perf.py +0 -0
  269. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_block_not_found_error.py +0 -0
  270. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  271. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_add_table_not_supported.py +0 -0
  272. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
  273. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
  274. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_format_stash.py +0 -0
  275. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_run_format_stash.py +0 -0
  276. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_run_guard.py +0 -0
  277. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_text_plain_fastpath.py +0 -0
  278. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_text_replace_semantics.py +0 -0
  279. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_collapsed_range_format.py +0 -0
  280. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_command_dataclasses.py +0 -0
  281. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_commands.py +0 -0
  282. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_comments.py +0 -0
  283. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_asset_id_property.py +0 -0
  284. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_clear.py +0 -0
  285. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_create.py +0 -0
  286. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_create_from_template.py +0 -0
  287. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_factory_validation.py +0 -0
  288. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_docx_exec_lab.py +0 -0
  289. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_docx_exec_lab_server.py +0 -0
  290. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_e2e_partial_failure_cascade.py +0 -0
  291. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_execution_scope.py +0 -0
  292. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_find_replace_session_open.py +0 -0
  293. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_hyperlink_coalescing.py +0 -0
  294. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_insert_deferred.py +0 -0
  295. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_list_styles.py +0 -0
  296. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_merged_cell_secondary_slot.py +0 -0
  297. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_merged_cells.py +0 -0
  298. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_oxml_shim.py +0 -0
  299. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_paragraph_text_len_cache.py +0 -0
  300. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_parity_misc.py +0 -0
  301. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_parity_round2.py +0 -0
  302. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_partial_failure_cascade.py +0 -0
  303. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_phase_a_behavior.py +0 -0
  304. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_phase_b_headers_footers.py +0 -0
  305. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_phase_c_tables.py +0 -0
  306. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_postproc_cell_format_rewrite.py +0 -0
  307. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
  308. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_postproc_ref_restore.py +0 -0
  309. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_pr19766_review_fixes.py +0 -0
  310. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_python_docx_api_parity.py +0 -0
  311. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_revisions.py +0 -0
  312. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  313. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_add_picture.py +0 -0
  314. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_add_run.py +0 -0
  315. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  316. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_comments_add_comment.py +0 -0
  317. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_comments_get.py +0 -0
  318. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_document_audit.py +0 -0
  319. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_document_element.py +0 -0
  320. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_enum_section.py +0 -0
  321. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_font_audit.py +0 -0
  322. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_header_footer.py +0 -0
  323. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_hyperlink.py +0 -0
  324. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_inline_shape.py +0 -0
  325. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  326. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_misc.py +0 -0
  327. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_paragraph_strict.py +0 -0
  328. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_paragraph_style.py +0 -0
  329. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  330. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_parfmt.py +0 -0
  331. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_row_col_cell.py +0 -0
  332. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_add_break.py +0 -0
  333. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_bool_setters.py +0 -0
  334. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_style.py +0 -0
  335. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_style_strict.py +0 -0
  336. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_text.py +0 -0
  337. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_underline.py +0 -0
  338. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_section_audit.py +0 -0
  339. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_section_dimensions.py +0 -0
  340. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_section_onoff.py +0 -0
  341. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_settings.py +0 -0
  342. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_shared_audit.py +0 -0
  343. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_style.py +0 -0
  344. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_styles.py +0 -0
  345. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_audit.py +0 -0
  346. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_cell.py +0 -0
  347. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_dimensions.py +0 -0
  348. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_layout.py +0 -0
  349. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_smoke_integration.py +0 -0
  350. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_style_acceptance.py +0 -0
  351. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_style_font.py +0 -0
  352. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_style_setters_contract.py +0 -0
  353. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_table_set_cell_perf.py +0 -0
  354. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_table_style_id_resolution.py +0 -0
  355. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_temporarily_unavailable.py +0 -0
  356. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_validate_find_replace_asset_script.py +0 -0
  357. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_wire_contract.py +0 -0
  358. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_xml_attr_guard.py +0 -0
  359. {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_zod_wire_contract.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.15.5
3
+ Version: 0.16.1
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -6,7 +6,7 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.15.5"
9
+ __version__ = "0.16.1"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -21,9 +21,9 @@ best-effort coalesce — if the loop thread races us we just flush twice
21
21
  (or zero times; the next loop-thread call drains it).
22
22
 
23
23
  `flush_all` is a process-wide hook used by the Daytona sandbox prelude to
24
- make sure pending writes hit Keryx and server-side edit sessions are
25
- checkpointed before the sandbox is suspended. It walks a weak-ref registry
26
- so dead Document instances don't keep their buffers alive.
24
+ make sure pending writes hit Keryx before the sandbox is suspended. It walks
25
+ a weak-ref registry so dead Workbook instances don't keep their buffers
26
+ alive.
27
27
  """
28
28
 
29
29
  from __future__ import annotations
@@ -124,10 +124,25 @@ if TYPE_CHECKING:
124
124
  # HTTP POST to agora (see ``_ptc._send``), so emitting one card per low-level
125
125
  # mutation produces hundreds of sub-cards per script and pays full network
126
126
  # RTT on every one. The allow-list keeps the per-action signal — one card
127
- # per logical paragraph creation — without the spam or the cumulative
128
- # latency. Asset-level creation events (``CreateDocument``) are emitted
129
- # from ``Document.create`` directly, not through this buffer path.
130
- _PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph"})
127
+ # per logical block creation (paragraph, heading, table) — without the spam
128
+ # or the cumulative latency. Asset-level creation events (``CreateDocument``)
129
+ # are emitted from ``Document.create`` directly, not through this buffer path.
130
+ _PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph", "CreateHeading", "CreateTable"})
131
+
132
+
133
+ def _should_emit_ptc(cmd: Command) -> bool:
134
+ """Whether ``cmd`` should produce a PTC sub-tool-card.
135
+
136
+ Only allow-listed block creates emit, and only when they target the
137
+ **body** story. Creates carrying an ``in_`` story locator (header,
138
+ footer, footnote, …) are skipped: the frontend locate affordance
139
+ scans the body editor only, so a header/footer card would highlight
140
+ an unrelated body block or nothing at all. Skip until the frontend
141
+ can route by ``args.in``.
142
+ """
143
+ if type(cmd).__name__ not in _PTC_EMIT_TOOLS:
144
+ return False
145
+ return getattr(cmd, "in_", None) is None
131
146
 
132
147
 
133
148
  def _ptc_emit_end_batch(cmds: list[Command], *, is_error: bool) -> None:
@@ -180,12 +195,11 @@ def _unregister(buffer: "CommandBuffer") -> None:
180
195
 
181
196
 
182
197
  def flush_all(*, strict: bool = False) -> None:
183
- """Flush and checkpoint every live CommandBuffer in this process.
198
+ """Flush every live CommandBuffer in this process.
184
199
 
185
200
  Used by the Daytona sandbox prelude after user code returns, so
186
- buffered mutations make it to Keryx and server-side edit sessions are
187
- durably released before the sandbox is suspended. Safe to call when no
188
- Buffers exist (no-op).
201
+ buffered mutations make it to Keryx before the sandbox is suspended.
202
+ Safe to call when no Buffers exist (no-op).
189
203
 
190
204
  By default, failures are logged and swallowed for backwards
191
205
  compatibility with existing local scripts. The executor calls
@@ -217,9 +231,9 @@ def flush_all(*, strict: bool = False) -> None:
217
231
  sys.stderr.write(f"[docx-sdk] flush_all: {msg}\n")
218
232
  continue
219
233
  try:
220
- buf.commit()
234
+ buf.flush()
221
235
  except Exception as e: # noqa: BLE001
222
- msg = f"buffer {buf.asset_id} flush/commit failed: {e}"
236
+ msg = f"buffer {buf.asset_id} flush failed: {e}"
223
237
  if strict:
224
238
  failures.append(msg)
225
239
  else:
@@ -415,7 +429,7 @@ class CommandBuffer:
415
429
  # commands still flow through the buffer (and through the batched
416
430
  # HTTP POST), they just don't produce a sub-tool-card. ``emit_end``
417
431
  # is automatically skipped because no ``_ptc_call_id`` was set.
418
- if type(cmd).__name__ in _PTC_EMIT_TOOLS:
432
+ if _should_emit_ptc(cmd):
419
433
  try:
420
434
  cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
421
435
  type(cmd).__name__,
@@ -500,20 +514,6 @@ class CommandBuffer:
500
514
  _apply_proxy_id_rewrites(results, proxy_id_refs)
501
515
  return results
502
516
 
503
- def commit(self) -> None:
504
- """Flush pending commands and release the server-side edit session.
505
-
506
- The HTTP command response tells us docx-studio applied the batch to its
507
- pooled SuperDoc handle. Releasing the handle closes the collaboration
508
- session without discard on the server, which is the durability
509
- checkpoint for asset-backed document edits.
510
- """
511
- self._assert_current_execution()
512
- self.flush()
513
- release_asset = getattr(self._client, "release_asset", None)
514
- if callable(release_asset):
515
- release_asset(self._asset_id)
516
-
517
517
  def close(self) -> None:
518
518
  """Flush and disable. Idempotent."""
519
519
  if self._closed:
@@ -528,7 +528,7 @@ class CommandBuffer:
528
528
  self._proxy_id_refs = {}
529
529
  _ptc_emit_end_batch(pending, is_error=True)
530
530
  else:
531
- self.commit()
531
+ self.flush()
532
532
  finally:
533
533
  self._closed = True
534
534
  _unregister(self)
@@ -699,24 +699,6 @@ class HttpClient:
699
699
  except Exception: # noqa: BLE001
700
700
  pass
701
701
 
702
- def release_asset(self, asset_id: str) -> None:
703
- """Ask docx-studio to close its pooled SuperDoc session for ``asset_id``.
704
-
705
- Command POSTs only prove that the server accepted and applied the
706
- mutation batch to its live SuperDoc handle. The durable checkpoint for
707
- collaborative sessions happens when that handle is closed without
708
- discard, so ``Document.save()`` and the Daytona ``flush_all`` cleanup
709
- call this endpoint after draining the command buffer.
710
- """
711
- url: str = f"{self._base_url}/docs/{asset_id}/session/release"
712
- _http_post_json(
713
- session=self._session,
714
- url=url,
715
- api_key=self._api_key,
716
- body={},
717
- timeout=30.0,
718
- )
719
-
720
702
  def execute_batch(
721
703
  self,
722
704
  asset_id: str,
@@ -13,13 +13,6 @@ LONG_RUNNING_COMMAND_TIMEOUT_SECONDS: float = 300.0
13
13
 
14
14
  _LONG_RUNNING_COMMAND_TYPES: frozenset[str] = frozenset(
15
15
  {
16
- # The first write to a large imported document can spend close to a
17
- # minute opening/syncing the server-side SuperDoc session before the
18
- # block creation itself is applied. These are ordinary agent primitives
19
- # for "append a summary/TLDR", so give the first batch enough room.
20
- "CreateHeading",
21
- "CreateParagraph",
22
- "CreateSectionBreak",
23
16
  "FindReplace",
24
17
  # Plain ``cell.text = value`` table fills are buffered as TableSetCell
25
18
  # commands. On dense legal documents, SuperDoc can spend well over the
@@ -231,8 +231,8 @@ class Session:
231
231
  handle = self._doc_handle
232
232
  buffer = getattr(handle, "buffer", None) if handle is not None else None
233
233
  if buffer is not None:
234
- buffer.commit()
235
- _log_info(f"Saved {self._asset_id} (buffer drained and session released)")
234
+ buffer.flush()
235
+ _log_info(f"Saved {self._asset_id} (buffer drained)")
236
236
 
237
237
  async def close(self) -> None:
238
238
  """Close the session. Idempotent."""
@@ -519,6 +519,11 @@ class ImagesGet(Command):
519
519
  class BlocksList(Command):
520
520
  node_types: list[str] | None = None
521
521
  include_text: bool | None = None
522
+ # Ask the server to inline each paragraph/heading block's run breakdown
523
+ # (``paragraph.inlines``) so ``Document.paragraphs`` can hydrate
524
+ # ``Paragraph.text``/``.runs``/``.style`` from one list instead of a
525
+ # ``get_node_by_id`` per paragraph. Serialized to ``includeRuns`` on the wire.
526
+ include_runs: bool | None = None
522
527
 
523
528
 
524
529
  @dataclass
@@ -359,12 +359,23 @@ class Document:
359
359
  # Include "heading" too so iterating over paragraphs covers both —
360
360
  # python-docx does not distinguish headings from paragraphs at the
361
361
  # Document.paragraphs level either (they're all Paragraph instances).
362
+ # ``includeRuns`` asks the server to inline each block's run breakdown
363
+ # (``paragraph.inlines``) so we can hydrate every returned proxy's
364
+ # ``.text`` / ``.runs`` / ``.style`` from this single list — instead of
365
+ # one ``get_node_by_id`` round-trip per paragraph per property. A
366
+ # read-heavy restyle loop over a large doc drops from ~N×3 round-trips
367
+ # to 1. See ``docx-studio/PERFORMANCE_BATCHING_ANALYSIS.md``.
362
368
  raw: object = run_sync(
363
369
  self._session.doc.blocks.list(
364
- {"nodeTypes": ["paragraph", "heading"], "includeText": False},
370
+ {
371
+ "nodeTypes": ["paragraph", "heading"],
372
+ "includeText": True,
373
+ "includeRuns": True,
374
+ },
365
375
  ),
366
376
  )
367
- # Real shape: {"blocks": [{"kind":"block","nodeType":..,"nodeId":..}], ...}
377
+ # Real shape: {"blocks": [{"kind":"block","nodeType":..,"nodeId":..,
378
+ # "styleId":..,"text":..,"paragraph":{"inlines":[..]}}], ...}
368
379
  if isinstance(raw, dict):
369
380
  blocks_obj: object = raw.get("blocks", [])
370
381
  blocks: list = blocks_obj if isinstance(blocks_obj, list) else []
@@ -380,11 +391,20 @@ class Document:
380
391
  if node_id:
381
392
  nt_raw = b.get("nodeType")
382
393
  nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
394
+ # Seed the read cache when the server inlined this block's runs.
395
+ # The snapshot is ``get_node_by_id``-shaped so
396
+ # ``Paragraph._node_info`` and the text/runs/style getters consume
397
+ # it unchanged. Blocks without an ``inlines`` payload (e.g.
398
+ # tables) fall back to live queries.
399
+ snapshot: dict | None = (
400
+ {"node": b} if isinstance(b.get("paragraph"), dict) else None
401
+ )
383
402
  out.append(
384
403
  Paragraph(
385
404
  session=self._session,
386
405
  node_id=node_id,
387
406
  node_type=nt,
407
+ node_info_snapshot=snapshot,
388
408
  ),
389
409
  )
390
410
  return out
@@ -226,6 +226,7 @@ class Paragraph:
226
226
  in_cell: bool = False,
227
227
  cell_ooxml_address: "tuple[str | None, int, int, int, int] | None" = None,
228
228
  cell: "object | None" = None,
229
+ node_info_snapshot: dict | None = None,
229
230
  ) -> None:
230
231
  self._session: "Session" = session
231
232
  self._node_id: str = node_id
@@ -272,6 +273,36 @@ class Paragraph:
272
273
  # — defensive for paragraph proxies built outside the _Cell
273
274
  # construction paths.
274
275
  self._cell: object | None = cell
276
+ # Prefetched ``get_node_by_id``-shaped snapshot
277
+ # (``{"node": {..., "paragraph": {"inlines": [...]}}}``) seeded by
278
+ # ``Document.paragraphs`` via the bulk ``blocks.list(includeRuns=True)``
279
+ # read. When present, ``text`` / ``runs`` / ``style`` read it instead of
280
+ # issuing a per-property ``get_node_by_id`` round-trip. It is a
281
+ # point-in-time snapshot (consistent with ``Document.paragraphs`` already
282
+ # returning a snapshot list) and is dropped by ``_invalidate_node_info``
283
+ # the moment this proxy mutates its own content.
284
+ self._node_info_snapshot: dict | None = node_info_snapshot
285
+
286
+ def _node_info(self) -> object:
287
+ """Return this paragraph's ``get_node_by_id``-shaped node info.
288
+
289
+ Served from the prefetch snapshot when one was seeded (one bulk
290
+ ``blocks.list`` for the whole document instead of one
291
+ ``get_node_by_id`` per ``.text`` / ``.runs`` / ``.style`` access);
292
+ otherwise a live query. ``_invalidate_node_info`` clears the snapshot
293
+ after a content mutation so the next read re-queries.
294
+ """
295
+ snapshot = self._node_info_snapshot
296
+ if snapshot is not None:
297
+ return snapshot
298
+ return run_sync(
299
+ self._session.doc.get_node_by_id({"id": self._node_id}),
300
+ )
301
+
302
+ def _invalidate_node_info(self) -> None:
303
+ """Drop the prefetched node snapshot after a content mutation, so the
304
+ next ``text`` / ``runs`` / ``style`` read reflects the new state."""
305
+ self._node_info_snapshot = None
275
306
 
276
307
  def _check_cell_inner(self, op: str, **format_props: Any) -> bool:
277
308
  """Return ``True`` if this paragraph is inside a table cell and
@@ -356,7 +387,7 @@ class Paragraph:
356
387
  @property
357
388
  def text(self) -> str:
358
389
  """Return the plain-text content of this paragraph."""
359
- return _node_text(self._session, self._node_id)
390
+ return _extract_text(self._node_info())
360
391
 
361
392
  @text.setter
362
393
  def text(self, value: str) -> None:
@@ -399,6 +430,9 @@ class Paragraph:
399
430
  # We just replaced the entire paragraph contents with ``value``;
400
431
  # the new text-address length is simply ``len(value)``.
401
432
  self._text_len = len(value)
433
+ # Content changed — drop the prefetched snapshot so a follow-up
434
+ # ``.text`` / ``.runs`` read reflects the replacement.
435
+ self._invalidate_node_info()
402
436
 
403
437
  @property
404
438
  def runs(self) -> list["Run"]:
@@ -413,9 +447,7 @@ class Paragraph:
413
447
  """
414
448
  from docx.text.run import Run
415
449
 
416
- info: object = run_sync(
417
- self._session.doc.get_node_by_id({"id": self._node_id}),
418
- )
450
+ info: object = self._node_info()
419
451
  inlines: list[dict] = _walk_inlines(info)
420
452
  runs: list["Run"] = []
421
453
  offset: int = 0
@@ -479,9 +511,7 @@ class Paragraph:
479
511
  from docx.styles.style import ParagraphStyle
480
512
  from docx.enum.style import WD_STYLE_TYPE
481
513
 
482
- info: object = run_sync(
483
- self._session.doc.get_node_by_id({"id": self._node_id}),
484
- )
514
+ info: object = self._node_info()
485
515
  if not isinstance(info, dict):
486
516
  return None
487
517
  node: object = info.get("node")
@@ -537,6 +567,10 @@ class Paragraph:
537
567
  },
538
568
  ),
539
569
  )
570
+ # Style changed — drop the prefetched snapshot so a follow-up ``.style``
571
+ # read reflects the new style instead of the stale prefetched value
572
+ # (the restyle-loop read-after-write this PR's prefetch targets).
573
+ self._invalidate_node_info()
540
574
 
541
575
  @property
542
576
  def alignment(self) -> "WD_ALIGN_PARAGRAPH | None":
@@ -1227,6 +1261,9 @@ class Paragraph:
1227
1261
  )
1228
1262
  offset += 1
1229
1263
  i = j + 1
1264
+ # Inserted content — drop the prefetched node snapshot so a follow-up
1265
+ # ``.text`` / ``.runs`` read on this proxy reflects the new runs.
1266
+ self._invalidate_node_info()
1230
1267
 
1231
1268
  @athena_extension(
1232
1269
  issue=74,
@@ -1397,6 +1434,11 @@ class Paragraph:
1397
1434
  )
1398
1435
  run_sync(self._session.send_command(cmd))
1399
1436
  self._text_len = end_offset
1437
+ # Appended a hyperlink run — drop the prefetched snapshot so a follow-up
1438
+ # ``.text`` / ``.runs`` read reflects the new content. This path fires
1439
+ # ``CreateHyperlink`` directly via ``send_command`` (it bypasses
1440
+ # ``_insert_run_text_segments``), so it needs its own invalidation.
1441
+ self._invalidate_node_info()
1400
1442
 
1401
1443
  run_dict = {"text": text, "hyperlink": {"url": url, "target": url}}
1402
1444
  return Hyperlink(
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-docx"
7
- version = "0.15.5"
7
+ version = "0.16.1"
8
8
  description = "Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -296,6 +296,7 @@ class FakeDocState:
296
296
 
297
297
  if op == "blocks.list":
298
298
  types = params.get("nodeTypes") or None
299
+ include_runs = bool(params.get("includeRuns"))
299
300
  out_blocks: list[dict] = []
300
301
  for b in self.blocks:
301
302
  if types and b.node_type not in types:
@@ -307,6 +308,12 @@ class FakeDocState:
307
308
  "styleId": b.style_id,
308
309
  "alignment": b.alignment,
309
310
  }
311
+ # Mirror the real server: when runs are requested, inline each
312
+ # paragraph/heading block's run breakdown so the SDK hydrates
313
+ # Paragraph.text/.runs/.style from this one list (no per-node read).
314
+ if include_runs and b.node_type in ("paragraph", "heading"):
315
+ entry["text"] = b.text
316
+ entry["paragraph"] = {"inlines": list(b.inlines)}
310
317
  out_blocks.append(entry)
311
318
  # Mirror real SuperDoc: when "table" is in the requested
312
319
  # nodeTypes, top-level tables come back too. The fake doesn't
@@ -7,9 +7,8 @@ Verifies that:
7
7
  - Creates WITH a client id (the transparent-batching path) defer,
8
8
  - pending mutations drain in the same batch as an eager call,
9
9
  - ``flush()`` drains explicitly,
10
- - ``commit()`` drains and releases the server-side session,
11
10
  - the idle timer auto-flushes after a configurable window,
12
- - the process-wide ``flush_all`` walks every live buffer and commits it.
11
+ - the process-wide ``flush_all`` walks every live buffer.
13
12
  """
14
13
 
15
14
  from __future__ import annotations
@@ -27,7 +26,6 @@ from docx._timeouts import DEFAULT_OP_TIMEOUT_SECONDS, LONG_RUNNING_COMMAND_TIME
27
26
  from docx.commands import (
28
27
  BlocksList,
29
28
  Command,
30
- CreateHeading,
31
29
  CreateParagraph,
32
30
  FormatApply,
33
31
  Replace,
@@ -46,7 +44,6 @@ class _FakeClient:
46
44
  # Envelope-level fields recorded per batch — used by
47
45
  # track-changes tests to verify changeMode / user propagation.
48
46
  self.envelopes: list[dict[str, Any]] = []
49
- self.releases: list[str] = []
50
47
  self._lock = threading.Lock()
51
48
 
52
49
  def execute_batch(
@@ -65,10 +62,6 @@ class _FakeClient:
65
62
  # Mimic per-command result envelope.
66
63
  return [{"index": i, "ok": True} for i, _ in enumerate(commands)]
67
64
 
68
- def release_asset(self, asset_id: str) -> None:
69
- with self._lock:
70
- self.releases.append(asset_id)
71
-
72
65
 
73
66
  class _FakeDocHandle:
74
67
  def __init__(self, buffer: CommandBuffer) -> None:
@@ -116,23 +109,6 @@ def test_pending_timeout_extends_for_table_set_cell_batches() -> None:
116
109
  buf.close()
117
110
 
118
111
 
119
- def test_pending_timeout_extends_for_block_creation_batches() -> None:
120
- fake = _FakeClient()
121
- buf = CommandBuffer(fake, "asset_1", auto_flush_seconds=0.0)
122
-
123
- buf.call(
124
- CreateHeading(
125
- text="TLDR",
126
- level=1,
127
- at={"kind": "documentEnd"},
128
- client_node_id="h_test",
129
- )
130
- )
131
-
132
- assert buf.pending_timeout_seconds == LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
133
- buf.close()
134
-
135
-
136
112
  def test_session_pending_timeout_reads_command_buffer_budget() -> None:
137
113
  from docx.client import Session
138
114
 
@@ -213,19 +189,6 @@ def test_flush_with_empty_queue_is_noop() -> None:
213
189
  buf.close()
214
190
 
215
191
 
216
- def test_commit_drains_pending_and_releases_server_session() -> None:
217
- fake = _FakeClient()
218
- buf = CommandBuffer(fake, "asset_1", auto_flush_seconds=0.0)
219
-
220
- buf.call(FormatApply(target={"kind": "block"}, inline={"bold": True}))
221
- buf.commit()
222
-
223
- assert len(fake.batches) == 1
224
- assert _types(fake.batches[0]) == ["FormatApply"]
225
- assert fake.releases == ["asset_1"]
226
- buf.close()
227
-
228
-
229
192
  def test_idle_timer_auto_flushes() -> None:
230
193
  """A non-zero idle window should flush after inactivity.
231
194
 
@@ -319,8 +282,6 @@ def test_flush_all_walks_every_live_buffer() -> None:
319
282
  flush_all()
320
283
  assert len(fake_a.batches) == 1
321
284
  assert len(fake_b.batches) == 1
322
- assert fake_a.releases == ["asset_a"]
323
- assert fake_b.releases == ["asset_b"]
324
285
  buf_a.close()
325
286
  buf_b.close()
326
287
 
@@ -17,7 +17,7 @@ import requests
17
17
  from docx import __version__ as SDK_VERSION
18
18
  from docx._http_doc import HttpClient, HttpDocHandle
19
19
  from docx._timeouts import LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
20
- from docx.commands import CreateParagraph, FindReplace, TableSetCell
20
+ from docx.commands import FindReplace, TableSetCell
21
21
  from docx.errors import AuthenticationError, DocxError, SessionError
22
22
 
23
23
 
@@ -145,73 +145,6 @@ def test_table_set_cell_batch_uses_long_running_http_timeout() -> None:
145
145
  assert captured["timeout"] == LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
146
146
 
147
147
 
148
- def test_block_creation_batch_uses_long_running_http_timeout() -> None:
149
- captured: dict[str, Any] = {}
150
-
151
- def _capture(url: str, **kwargs: Any) -> _FakeResponse: # noqa: ARG001
152
- captured["timeout"] = kwargs["timeout"]
153
- return _FakeResponse(
154
- status_code=200,
155
- body=_ok_body(
156
- [
157
- {
158
- "index": 0,
159
- "type": "CreateParagraph",
160
- "result": {
161
- "client_node_id": "p_test",
162
- "real_node_id": "paragraph_real",
163
- },
164
- }
165
- ],
166
- ),
167
- )
168
-
169
- client = HttpClient(base_url="https://example.com", api_key="bg_key")
170
-
171
- with patch.object(client._session, "post", side_effect=_capture):
172
- result = client.execute_batch(
173
- "asset_test",
174
- [
175
- CreateParagraph(
176
- text="DLDR",
177
- at={"kind": "documentEnd"},
178
- client_node_id="p_test",
179
- )
180
- ],
181
- )
182
-
183
- assert result == [
184
- {"client_node_id": "p_test", "real_node_id": "paragraph_real"}
185
- ]
186
- assert captured["timeout"] == LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
187
-
188
-
189
- def test_release_asset_posts_to_session_release_endpoint() -> None:
190
- captured: dict[str, Any] = {}
191
-
192
- def _capture(url: str, **kwargs: Any) -> _FakeResponse:
193
- captured["url"] = url
194
- captured["body"] = json.loads(kwargs["data"].decode("utf-8"))
195
- captured["auth"] = kwargs["headers"].get("Authorization")
196
- captured["timeout"] = kwargs["timeout"]
197
- return _FakeResponse(
198
- status_code=200,
199
- body=b'{"assetId":"asset_test","released":true}',
200
- )
201
-
202
- client = HttpClient(base_url="https://example.com", api_key="bg_key")
203
-
204
- with patch.object(client._session, "post", side_effect=_capture):
205
- client.release_asset("asset_test")
206
-
207
- assert captured == {
208
- "url": "https://example.com/docs/asset_test/session/release",
209
- "body": {},
210
- "auth": "Bearer bg_key",
211
- "timeout": 30.0,
212
- }
213
-
214
-
215
148
  def test_deeply_nested_paths() -> None:
216
149
  """``doc.format.paragraph.set_alignment(p)`` walks three levels and
217
150
  serializes as ``SetParagraphAlignment``. It's a pure mutation, so it
@@ -35,15 +35,22 @@ def test_document_iter_inner_content_yields_paragraphs_then_table() -> None:
35
35
  assert all(isinstance(i, (Paragraph, Table)) for i in items)
36
36
 
37
37
 
38
- def test_document_paragraphs_uses_lightweight_block_listing() -> None:
38
+ def test_document_paragraphs_prefetches_runs_in_one_listing() -> None:
39
+ """``Document.paragraphs`` issues ONE rich ``blocks.list(includeRuns=True)``
40
+ and hydrates every proxy's text/style/runs from it — so iterating and reading
41
+ per-paragraph properties costs no extra ``getNodeById`` round-trips (the old
42
+ hot path was one query per property per paragraph). See
43
+ ``docx-studio/PERFORMANCE_BATCHING_ANALYSIS.md``."""
39
44
  install_fake_session()
40
45
  from docx import Document
41
46
 
42
47
  doc = Document("asset_fake")
43
48
  doc.add_paragraph("Intro")
49
+ doc.add_heading("Section A", level=1)
44
50
 
45
- _ = doc.paragraphs
51
+ paras = doc.paragraphs
46
52
 
53
+ # paragraphs() asked the server to inline the run breakdown.
47
54
  block_list_calls = [
48
55
  params
49
56
  for op, params in doc._session.calls()
@@ -51,7 +58,62 @@ def test_document_paragraphs_uses_lightweight_block_listing() -> None:
51
58
  and params.get("nodeTypes") == ["paragraph", "heading"]
52
59
  ]
53
60
  assert block_list_calls
54
- assert block_list_calls[-1]["includeText"] is False
61
+ assert block_list_calls[-1]["includeRuns"] is True
62
+
63
+ # Reading text/style/runs off the prefetched proxies issues NO per-paragraph
64
+ # getNodeById round-trips.
65
+ calls_before: int = len(doc._session.calls())
66
+ for p in paras:
67
+ _ = p.text
68
+ _ = p.style
69
+ _ = list(p.runs)
70
+ extra_node_reads = [
71
+ op for op, _ in doc._session.calls()[calls_before:] if op == "getNodeById"
72
+ ]
73
+ assert extra_node_reads == []
74
+
75
+
76
+ def test_paragraph_text_mutation_invalidates_prefetch_snapshot() -> None:
77
+ """A content mutation on a prefetched paragraph drops its snapshot, so the
78
+ next read reflects the new state instead of the stale prefetched value."""
79
+ install_fake_session()
80
+ from docx import Document
81
+
82
+ doc = Document("asset_fake")
83
+ doc.add_paragraph("Original")
84
+
85
+ p = doc.paragraphs[0]
86
+ assert p.text == "Original" # served from the prefetch snapshot
87
+
88
+ p.text = "Replaced" # content mutation → snapshot invalidated
89
+
90
+ calls_before: int = len(doc._session.calls())
91
+ assert p.text == "Replaced" # re-read reflects the mutation
92
+ # The fresh read went back to the server (the stale snapshot was dropped).
93
+ assert any(
94
+ op == "getNodeById" for op, _ in doc._session.calls()[calls_before:]
95
+ )
96
+
97
+
98
+ def test_paragraph_style_set_invalidates_prefetch_snapshot() -> None:
99
+ """Setting ``.style`` on a prefetched paragraph drops its snapshot, so the
100
+ next ``.style`` read returns the new style — the restyle-loop read-after-write
101
+ (``for p in doc.paragraphs: p.style = ...``) the prefetch optimizes for."""
102
+ install_fake_session()
103
+ from docx import Document
104
+
105
+ doc = Document("asset_fake")
106
+ doc.add_paragraph("Body")
107
+
108
+ p = doc.paragraphs[0]
109
+ _ = p.style # prime the prefetch snapshot
110
+
111
+ p.style = "Heading1" # style mutation → snapshot must be invalidated
112
+
113
+ style_after = p.style
114
+ assert style_after is not None
115
+ # Re-read reflects the new style instead of the stale prefetched value.
116
+ assert style_after.name == "Heading1"
55
117
 
56
118
 
57
119
  def test_document_tables_uses_lightweight_block_listing() -> None: