athena-python-docx 0.30.0__tar.gz → 0.32.0__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 (458) hide show
  1. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/AGENTS.md +29 -4
  2. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/PKG-INFO +1 -1
  3. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_buffer.py +107 -3
  5. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_capabilities.py +8 -0
  6. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_http_doc.py +15 -3
  7. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_postproc.py +217 -1
  8. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_run_order.py +39 -0
  9. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/commands.py +20 -4
  10. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/comments.py +7 -2
  11. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/document.py +60 -6
  12. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/drawing/__init__.py +11 -3
  13. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/errors.py +18 -1
  14. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/revisions.py +4 -1
  15. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/section.py +16 -5
  16. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/settings.py +12 -2
  17. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/shape.py +4 -1
  18. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/styles/style.py +107 -18
  19. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/table.py +206 -48
  20. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/hyperlink.py +10 -1
  21. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/paragraph.py +26 -6
  22. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/run.py +157 -16
  23. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/pyproject.toml +1 -1
  24. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/METHODOLOGY.md +35 -9
  25. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/complex_cases.py +1 -1
  26. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/fake_session.py +2 -0
  27. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +1 -0
  28. athena_python_docx-0.32.0/tests/fidelity/op_snapshots/rw18_header_table_properties.json +34 -0
  29. athena_python_docx-0.32.0/tests/fidelity/op_snapshots/rw19_header_table_rows_columns.json +38 -0
  30. athena_python_docx-0.32.0/tests/fidelity/op_snapshots/rw20_header_table_merges.json +28 -0
  31. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/real_world_cases.py +74 -0
  32. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fixtures/field_reads.json +320 -20
  33. athena_python_docx-0.32.0/tests/live/baseline.json +435 -0
  34. athena_python_docx-0.32.0/tests/live/fixture_scripts.py +188 -0
  35. athena_python_docx-0.32.0/tests/live/reader.py +676 -0
  36. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/live/runner.py +140 -15
  37. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/live/test_live_runner.py +220 -2
  38. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/intentional_deviations.json +46 -2
  39. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_merge.py +17 -7
  40. athena_python_docx-0.32.0/tests/test_cell_nested_tables.py +82 -0
  41. athena_python_docx-0.32.0/tests/test_comments_first_read.py +101 -0
  42. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_drawing_image.py +38 -0
  43. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_enum_parity.py +4 -2
  44. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_export_docx_clean.py +1 -1
  45. athena_python_docx-0.32.0/tests/test_export_rescue_anchors.py +348 -0
  46. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_field_reads_python_docx_parity.py +20 -6
  47. athena_python_docx-0.32.0/tests/test_flushed_write_failures.py +590 -0
  48. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_http_transport.py +1 -1
  49. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_hyperlink_coalescing.py +40 -0
  50. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_iter_inner_content.py +35 -0
  51. athena_python_docx-0.32.0/tests/test_nested_table_refused.py +95 -0
  52. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_paragraph_delete.py +1 -0
  53. athena_python_docx-0.32.0/tests/test_run_break_inner_content.py +121 -0
  54. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_edges.py +25 -0
  55. athena_python_docx-0.32.0/tests/test_run_later_runs.py +580 -0
  56. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_settings.py +33 -0
  57. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_table_layout.py +8 -6
  58. athena_python_docx-0.32.0/tests/test_style_tab_stops_read.py +127 -0
  59. athena_python_docx-0.32.0/tests/test_table_anchors.py +219 -0
  60. athena_python_docx-0.32.0/tests/test_table_direction.py +102 -0
  61. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_table_read_reuse.py +29 -0
  62. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/uv.lock +7 -7
  63. athena_python_docx-0.30.0/tests/live/baseline.json +0 -258
  64. athena_python_docx-0.30.0/tests/live/reader.py +0 -243
  65. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/.gitignore +0 -0
  66. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/CLAUDE.md +0 -0
  67. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/DOCX_EXEC_LAB.md +0 -0
  68. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/README.md +0 -0
  69. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_athena_extension.py +0 -0
  70. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_batching.py +0 -0
  71. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_citations.py +0 -0
  72. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_execution.py +0 -0
  73. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_http.py +0 -0
  74. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_image_utils.py +0 -0
  75. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_ptc.py +0 -0
  76. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_references.py +0 -0
  77. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_table_styles.py +0 -0
  78. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_timeouts.py +0 -0
  79. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/_tracked.py +0 -0
  80. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/api.py +0 -0
  81. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/blkcntnr.py +0 -0
  82. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/bookmarks.py +0 -0
  83. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/charts.py +0 -0
  84. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/client.py +0 -0
  85. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/__init__.py +0 -0
  86. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/base.py +0 -0
  87. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/dml.py +0 -0
  88. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/section.py +0 -0
  89. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/shape.py +0 -0
  90. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/style.py +0 -0
  91. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/table.py +0 -0
  92. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/enum/text.py +0 -0
  93. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/exceptions.py +0 -0
  94. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/fields.py +0 -0
  95. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/footnotes.py +0 -0
  96. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/image/__init__.py +0 -0
  97. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/image/exceptions.py +0 -0
  98. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/image/image.py +0 -0
  99. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/math.py +0 -0
  100. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/opc/__init__.py +0 -0
  101. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/opc/coreprops.py +0 -0
  102. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/oxml/__init__.py +0 -0
  103. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/sdt.py +0 -0
  104. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/session.py +0 -0
  105. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/shared.py +0 -0
  106. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/styles/__init__.py +0 -0
  107. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/styles/_table.py +0 -0
  108. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/styles/latent.py +0 -0
  109. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/styles/styles.py +0 -0
  110. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/__init__.py +0 -0
  111. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/font.py +0 -0
  112. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/pagebreak.py +0 -0
  113. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/parfmt.py +0 -0
  114. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/text/tabstops.py +0 -0
  115. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/toc.py +0 -0
  116. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/docx/typing.py +0 -0
  117. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/docx_exec_lab.py +0 -0
  118. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
  119. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
  120. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/docx_exec_lab_server.py +0 -0
  121. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/dump_wire_fixtures.py +0 -0
  122. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/release.sh +0 -0
  123. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/round_trip_smoke.py +0 -0
  124. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/smoke_test_block_not_found.py +0 -0
  125. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/scripts/validate_find_replace_asset.py +0 -0
  126. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/__init__.py +0 -0
  127. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/conftest.py +0 -0
  128. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/README.md +0 -0
  129. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/__init__.py +0 -0
  130. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/ab_probe_cases.py +0 -0
  131. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/ab_probe_runner.py +0 -0
  132. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/auto_gen_cases.py +0 -0
  133. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/binary_round_trip.py +0 -0
  134. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/cases.py +0 -0
  135. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/coverage_report.py +0 -0
  136. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/extract.py +0 -0
  137. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/extreme_cases.py +0 -0
  138. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/firm_templates/README.md +0 -0
  139. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/firm_templates/__init__.py +0 -0
  140. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/firm_templates/_runner.py +0 -0
  141. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/firm_templates/extractor.py +0 -0
  142. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  143. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  144. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/local_runner.py +0 -0
  145. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/mega_cases.py +0 -0
  146. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshot.py +0 -0
  147. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  148. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  149. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  150. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  151. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  152. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  153. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  154. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  155. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  156. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  157. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  158. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  159. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  160. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  161. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  162. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  163. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  164. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  165. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  166. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  167. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  168. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  169. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  170. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  171. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  172. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  173. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  174. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  175. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  176. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  177. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  178. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  179. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  180. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  181. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  182. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  183. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  184. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  185. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  186. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  187. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  188. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  189. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  190. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  191. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  192. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  193. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  194. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  195. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  196. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  197. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  198. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  199. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  200. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  201. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  202. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  203. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  204. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  205. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  206. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  207. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  208. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  209. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  210. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  211. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  212. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  213. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  214. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  215. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  216. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  217. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  218. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  219. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  220. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  221. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  222. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  223. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  224. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  225. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  226. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  227. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  228. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  229. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  230. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  231. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  232. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  233. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  234. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  235. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  236. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  237. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  238. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  239. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  240. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  241. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  242. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  243. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  244. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  245. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  246. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  247. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  248. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  249. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  250. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  251. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  252. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  253. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  254. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  255. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  256. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  257. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  258. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  259. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  260. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  261. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  262. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  263. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  264. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  265. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  266. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  267. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  268. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  269. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  270. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  271. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  272. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  273. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  274. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  275. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  276. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  277. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  278. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  279. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  280. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  281. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  282. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  283. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  284. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  285. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  286. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  287. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  288. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  289. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  290. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  291. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  292. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  293. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw16_header_footer_tables.json +0 -0
  294. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/op_snapshots/rw17_header_letterhead_filled.json +0 -0
  295. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/ours_spec.json +0 -0
  296. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/parity_crawl.py +0 -0
  297. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/parity_diff.json +0 -0
  298. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/round_trip_tests.py +0 -0
  299. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/stock_spec.json +0 -0
  300. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  301. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fixtures/note_reference_reads.json +0 -0
  302. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fixtures/ruby_reads.json +0 -0
  303. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/fixtures/symbol_reads.json +0 -0
  304. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/live/__init__.py +0 -0
  305. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/live/sdk_driver.py +0 -0
  306. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/live/stock_driver.py +0 -0
  307. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/README.md +0 -0
  308. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/__init__.py +0 -0
  309. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/baseline_gaps.json +0 -0
  310. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/compare.py +0 -0
  311. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/introspect.py +0 -0
  312. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  313. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/reports/gap_report.json +0 -0
  314. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/run_parity.py +0 -0
  315. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/snapshots/athena_latest.json +0 -0
  316. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  317. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/parity/test_parity_gap.py +0 -0
  318. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_add_section.py +0 -0
  319. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_add_section_extract_items.py +0 -0
  320. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_athena_extension_creates_session_open.py +0 -0
  321. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_athena_extensions_contract.py +0 -0
  322. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_athena_extensions_registry.py +0 -0
  323. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_batch_chunking.py +0 -0
  324. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_batching_perf.py +0 -0
  325. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_block_not_found_error.py +0 -0
  326. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_buffer.py +0 -0
  327. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  328. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_add_table_not_supported.py +0 -0
  329. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_borders_spec.py +0 -0
  330. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_fastpath_runs.py +0 -0
  331. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
  332. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
  333. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_inner_format_stash.py +0 -0
  334. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_inner_run_format_stash.py +0 -0
  335. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_inner_run_guard.py +0 -0
  336. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_paragraphs_listed.py +0 -0
  337. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_text_plain_fastpath.py +0 -0
  338. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_cell_text_replace_semantics.py +0 -0
  339. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_citations.py +0 -0
  340. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_collapsed_range_format.py +0 -0
  341. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_color_format_theme.py +0 -0
  342. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_command_dataclasses.py +0 -0
  343. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_commands.py +0 -0
  344. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_comment_paragraphs.py +0 -0
  345. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_comments.py +0 -0
  346. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_content_control_value.py +0 -0
  347. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_core_properties.py +0 -0
  348. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_document_asset_id_property.py +0 -0
  349. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_document_clear.py +0 -0
  350. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_document_create.py +0 -0
  351. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_document_create_from_template.py +0 -0
  352. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_document_factory_validation.py +0 -0
  353. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_docx_exec_lab.py +0 -0
  354. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_docx_exec_lab_server.py +0 -0
  355. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
  356. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_empty_hyperlinks.py +0 -0
  357. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_execution_scope.py +0 -0
  358. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_field_refresh.py +0 -0
  359. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_field_runs.py +0 -0
  360. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_find_replace_session_open.py +0 -0
  361. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_image_url_data_uri.py +0 -0
  362. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_inline_page_break.py +0 -0
  363. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_inline_shape_alt_text.py +0 -0
  364. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_inline_shape_size.py +0 -0
  365. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_inline_shape_type.py +0 -0
  366. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_inline_shapes_floating.py +0 -0
  367. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_insert_deferred.py +0 -0
  368. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_list_styles.py +0 -0
  369. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_merged_cell_secondary_slot.py +0 -0
  370. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_merged_cells.py +0 -0
  371. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_missing_modules.py +0 -0
  372. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_note_reference_reads_python_docx_parity.py +0 -0
  373. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_oxml_shim.py +0 -0
  374. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_page_break.py +0 -0
  375. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_paragraph_runs_hyperlinks.py +0 -0
  376. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_paragraph_text_len_cache.py +0 -0
  377. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_parity_misc.py +0 -0
  378. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_parity_round2.py +0 -0
  379. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_partial_failure_cascade.py +0 -0
  380. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_pdf_export_and_page_renders.py +0 -0
  381. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_phase_a_behavior.py +0 -0
  382. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_phase_b_headers_footers.py +0 -0
  383. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_phase_c_tables.py +0 -0
  384. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_positional_cell_id.py +0 -0
  385. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
  386. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
  387. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_postproc_ref_restore.py +0 -0
  388. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_pr19766_review_fixes.py +0 -0
  389. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_ptc.py +0 -0
  390. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_python_docx_api_parity.py +0 -0
  391. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_read_run_ranges.py +0 -0
  392. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_repeated_node_read.py +0 -0
  393. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_restart_numbering.py +0 -0
  394. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_revisions.py +0 -0
  395. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_round_trip_smoke_script.py +0 -0
  396. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_ruby_reads_python_docx_parity.py +0 -0
  397. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_add_picture_anchor.py +0 -0
  398. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_formatting_wire.py +0 -0
  399. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_identity.py +0 -0
  400. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_mark_inheritance.py +0 -0
  401. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_table_style_objects.py +0 -0
  402. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_text_python_docx.py +0 -0
  403. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_run_text_refreshes_paragraph.py +0 -0
  404. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_sdk_reads_served.py +0 -0
  405. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_section_read_lengths.py +0 -0
  406. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  407. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_add_picture.py +0 -0
  408. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_add_run.py +0 -0
  409. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  410. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  411. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_comments_get.py +0 -0
  412. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_document_audit.py +0 -0
  413. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_document_element.py +0 -0
  414. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_enum_section.py +0 -0
  415. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_font_audit.py +0 -0
  416. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_header_footer.py +0 -0
  417. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_hyperlink.py +0 -0
  418. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_inline_shape.py +0 -0
  419. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  420. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_misc.py +0 -0
  421. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  422. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  423. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  424. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_parfmt.py +0 -0
  425. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  426. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_run_add_break.py +0 -0
  427. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  428. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_run_style.py +0 -0
  429. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  430. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_run_text.py +0 -0
  431. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_run_underline.py +0 -0
  432. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_section_audit.py +0 -0
  433. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_section_dimensions.py +0 -0
  434. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_section_onoff.py +0 -0
  435. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_shared_audit.py +0 -0
  436. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_style.py +0 -0
  437. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_styles.py +0 -0
  438. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_table_audit.py +0 -0
  439. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_table_cell.py +0 -0
  440. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  441. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_smoke_integration.py +0 -0
  442. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_style_acceptance.py +0 -0
  443. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_style_font.py +0 -0
  444. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_style_formatting_writes.py +0 -0
  445. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_style_metadata_writes.py +0 -0
  446. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_style_names.py +0 -0
  447. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_style_setters_contract.py +0 -0
  448. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_symbol_reads_python_docx_parity.py +0 -0
  449. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_table_set_cell_perf.py +0 -0
  450. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_table_style_id_resolution.py +0 -0
  451. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_temporarily_unavailable.py +0 -0
  452. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_text_control_characters.py +0 -0
  453. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_tracked_revisions_offsets.py +0 -0
  454. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_uv_lock_version.py +0 -0
  455. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_validate_find_replace_asset_script.py +0 -0
  456. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_wire_contract.py +0 -0
  457. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_xml_attr_guard.py +0 -0
  458. {athena_python_docx-0.30.0 → athena_python_docx-0.32.0}/tests/test_zod_wire_contract.py +0 -0
@@ -107,6 +107,12 @@ pinned by focused tests and documented in
107
107
  `horz_dpi`, `width`, `scaled_dimensions`, …). A picture in a table cell or
108
108
  a header has no id to read its image by, so `.image` raises `ValueError`;
109
109
  an older docx-studio does not report pictures, so they are not runs there.
110
+ - **A run holding only a page or column break yields `""` from
111
+ `Run.iter_inner_content()`,** as python-docx reads a `w:br` of type `page`
112
+ or `column` as empty text; an empty run (a comment reference's) yields
113
+ nothing. docx-studio's read marks such a run with `break`, and
114
+ `Run.add_break` marks the run it adds one to. Against an older docx-studio,
115
+ which sends no `break`, a re-read run yields nothing.
110
116
  - **`Styles.latent_styles`** (`docx.styles.latent.LatentStyles`) reads
111
117
  the document's `<w:latentStyles>` (defaults and each `w:lsdException`)
112
118
  from docx-studio's `Info` (`includeStyles` → `latentStyles`), as python-docx
@@ -986,13 +992,32 @@ architecture or a DOCX Studio constraint:
986
992
  `Table.autofit` reads `False` (python-docx leaves autofit on); its columns
987
993
  share 9,360 twips, where python-docx shares the section's text width; and
988
994
  in the fixed layout a column width the script sets sizes the column's
989
- cells too, where python-docx sets the `w:gridCol` alone. It also draws
990
- borders and a zebra fill, which no python-docx read sees. Recorded as
991
- `live_deviations` for the script cases' `tables.autofit`, `tables.widths`
992
- and `tables.cell_format` in `tests/parity/intentional_deviations.json`;
995
+ cells too, where python-docx sets the `w:gridCol` alone. Its cell
996
+ paragraphs carry their own spacing (`w:spacing w:after="0" w:line="240"
997
+ w:lineRule="auto"`: no space after, single lines), so a cell paragraph's
998
+ `paragraph_format.space_after` reads `0` and `line_spacing` `1.0`, where
999
+ python-docx's `add_table` writes none. It also draws borders and a zebra
1000
+ fill, which no python-docx read sees. Recorded as `live_deviations` for
1001
+ the script cases' `tables.autofit`, `tables.widths`, `tables.cell_format`
1002
+ and `tables.cell_paragraph_format` in `tests/parity/intentional_deviations.json`;
993
1003
  an imported table's gaps stay gaps, and so do the two cases whose
994
1004
  `cell_format` also differs for another cause (`except`).
995
1005
 
1006
+ - **A held `Table` raises `StaleTableError` once its table is gone.** The SDK
1007
+ holds the anchored ids docx-studio gives it (`t:N/R:C/K…@FIRST.LAST`: the
1008
+ table's place, then the paraIds of its first and last paragraphs), and
1009
+ docx-studio finds the table by those paragraphs, so a table an edit moved (a
1010
+ row added above a table nested in a cell, a table added before it, a merge
1011
+ moving a nested table into the merged cell) is still the one read and
1012
+ written, as python-docx's proxies follow the elements they hold. When no
1013
+ table holds them (the table was deleted, or the cell holding it had its
1014
+ text replaced, which removes the tables it held), docx-studio refuses the
1015
+ batch and the SDK raises `StaleTableError`: "the table this proxy named is
1016
+ gone; re-read `document.tables`". python-docx writes the detached element
1017
+ and changes nothing; raising tells the script instead of losing its write
1018
+ (coordinator, 2026-10-14). A held `_Cell` or `_Row` is still found by its
1019
+ place in its table.
1020
+
996
1021
  - **`_Cell.merge` raises `InvalidSpanError` for every span that would cut
997
1022
  through a merged cell.** It merges what python-docx merges and refuses
998
1023
  what python-docx refuses (an L- or T-shaped pair of corners), but where
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: athena-python-docx
3
- Version: 0.30.0
3
+ Version: 0.32.0
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's DOCX Studio 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.30.0"
9
+ __version__ = "0.32.0"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import discard_all, flush_all
@@ -102,6 +102,54 @@ def _apply_proxy_id_rewrites(
102
102
  pass
103
103
 
104
104
 
105
+ _WRITES_UNAPPLIED = "_docx_writes_unapplied"
106
+
107
+
108
+ def writes_unapplied(exc: BaseException) -> bool:
109
+ """True for the failure of a batch whose buffered writes docx-studio did
110
+ not apply (or may not have): a flush of them, a read they rode with, or
111
+ the idle timer's flush. A read that flushed them raises it rather than
112
+ fall back as it would for a failure of its own, so the script hears of
113
+ the write as python-docx would have failed at the write itself."""
114
+ return getattr(exc, _WRITES_UNAPPLIED, False) is True
115
+
116
+
117
+ def _unapplied_writes(exc: BaseException) -> BaseException:
118
+ """``exc``, marked as the failure of buffered writes (:func:`writes_unapplied`)."""
119
+ if not writes_unapplied(exc):
120
+ setattr(exc, _WRITES_UNAPPLIED, True)
121
+ exc.add_note("the buffered writes this call sent to docx-studio were not applied")
122
+ return exc
123
+
124
+
125
+ _REPORTED_ARGS = "_docx_reported_args"
126
+
127
+
128
+ def keep_reported_message(exc: BaseException) -> None:
129
+ """Keep ``exc``'s message as it was raised, before a flush's account of
130
+ what of it docx-studio applied is put ahead of it (``_flush_outcome``), so
131
+ that a read whose buffered writes were sent again on their own can raise
132
+ its failure as its own (:func:`_as_the_read_s_own_failure`)."""
133
+ if not hasattr(exc, _REPORTED_ARGS):
134
+ setattr(exc, _REPORTED_ARGS, exc.args)
135
+
136
+
137
+ def _as_the_read_s_own_failure(exc: BaseException, read: Command) -> None:
138
+ """``exc``, the failure of the read ``read`` whose buffered writes were
139
+ sent again on their own and applied, saying only that the read failed:
140
+ its message as docx-studio gave it, without the flush's account that the
141
+ writes did not land, ``read`` the one command that did not land
142
+ (``unapplied_commands``, ``failed_command``), and nothing applied before
143
+ it (``partial_applied``). A caller re-applying what the error names then
144
+ repeats no write."""
145
+ reported: object = getattr(exc, _REPORTED_ARGS, None)
146
+ if isinstance(reported, tuple):
147
+ exc.args = reported
148
+ exc.unapplied_commands = [read] # type: ignore[attr-defined]
149
+ exc.failed_command = read # type: ignore[attr-defined]
150
+ exc.partial_applied = [] # type: ignore[attr-defined]
151
+
152
+
105
153
  def _repeatable_read_key(cmd: Command) -> str | None:
106
154
  """The key a repeat of ``cmd`` answers from (:meth:`CommandBuffer.call`):
107
155
  a ``GetNodeById``'s wire form; ``None`` for any other command."""
@@ -677,9 +725,11 @@ class CommandBuffer:
677
725
  # See ``_apply_proxy_id_rewrites`` for the full rationale.
678
726
  _apply_proxy_id_rewrites(_extract_partial_results(exc), proxy_id_refs)
679
727
  _ptc_emit_end_batch(pending, is_error=True)
728
+ _unapplied_writes(exc)
680
729
  raise
681
- except Exception:
730
+ except Exception as exc:
682
731
  _ptc_emit_end_batch(pending, is_error=True)
732
+ _unapplied_writes(exc)
683
733
  raise
684
734
  finally:
685
735
  _forget_reads_of_others(self)
@@ -751,10 +801,17 @@ class CommandBuffer:
751
801
  # the trailing ``cmd`` would otherwise abandon every
752
802
  # buffered Create's rewrite at once.
753
803
  _apply_proxy_id_rewrites(_extract_partial_results(exc), proxy_id_refs)
754
- _ptc_emit_end_batch(all_cmds, is_error=True)
804
+ _ptc_emit_end_batch([cmd], is_error=True)
805
+ if pending:
806
+ self._settle_writes_of_failed_read(
807
+ exc, pending, change_mode=change_mode, user=user, proxy_id_refs=proxy_id_refs
808
+ )
755
809
  raise
756
- except Exception:
810
+ except Exception as exc:
757
811
  _ptc_emit_end_batch(all_cmds, is_error=True)
812
+ if pending:
813
+ # Whether the request landed is not known (a timeout, a dropped connection).
814
+ _unapplied_writes(exc)
758
815
  raise
759
816
  finally:
760
817
  # A batch of one node read changes nothing another buffer read.
@@ -788,6 +845,51 @@ class CommandBuffer:
788
845
  trailing = results[-1]
789
846
  return trailing if trailing is not None else {}
790
847
 
848
+ def _settle_writes_of_failed_read(
849
+ self,
850
+ exc: DocxError,
851
+ pending: list[Command],
852
+ *,
853
+ change_mode: str | None,
854
+ user: dict[str, str] | None,
855
+ proxy_id_refs: "dict[str, list[tuple[object, str]]]",
856
+ ) -> None:
857
+ """The buffered writes ``pending`` that rode with a read whose batch
858
+ failed (``exc``). docx-studio applies a batch all or nothing, so when
859
+ it reported the read itself failing, it applied none of the writes:
860
+ they are sent again on their own, and the read's failure stays its own
861
+ to report, as a read's fallback expects: once they landed, it names the
862
+ read alone as what did not land (:func:`_as_the_read_s_own_failure`).
863
+ Any other failure (one of the writes, or one not reported) marks
864
+ ``exc`` as one of unapplied writes (:func:`writes_unapplied`), and a
865
+ failure of the writes sent again is raised as one."""
866
+ unapplied = getattr(exc, "unapplied_commands", None)
867
+ failed = getattr(exc, "failed_command", None)
868
+ if not (isinstance(unapplied, list) and unapplied and failed is unapplied[-1]):
869
+ _ptc_emit_end_batch(pending, is_error=True)
870
+ _unapplied_writes(exc)
871
+ return
872
+ writes = unapplied[:-1]
873
+ if not writes:
874
+ # An older docx-studio kept the writes ahead of the read.
875
+ _ptc_emit_end_batch(pending, is_error=False)
876
+ return
877
+ try:
878
+ results, _skipped = self._client.execute_batch(
879
+ self._asset_id, writes, change_mode=change_mode, user=user
880
+ )
881
+ except Exception as again:
882
+ # The writes sent again go in requests of at most
883
+ # ``MAX_BATCH_COMMANDS``: those a failed request's earlier ones
884
+ # applied keep their ids, as a flush's do, and only the rest are
885
+ # the failure's unapplied writes.
886
+ _apply_proxy_id_rewrites(_extract_partial_results(again), proxy_id_refs)
887
+ _ptc_emit_end_batch(pending, is_error=True)
888
+ raise _unapplied_writes(again) from exc
889
+ _ptc_emit_end_batch(pending, is_error=False)
890
+ _apply_proxy_id_rewrites(results, proxy_id_refs)
891
+ _as_the_read_s_own_failure(exc, failed)
892
+
791
893
  def _reset_timer_locked(self) -> None:
792
894
  # Caller must hold self._lock.
793
895
  self._cancel_timer_locked()
@@ -852,6 +954,8 @@ class CommandBuffer:
852
954
  "raised by a background docx-sdk auto-flush that failed earlier; "
853
955
  "the commands in that batch were NOT applied",
854
956
  )
957
+ # Marked as unapplied writes by the flush (``_flush_in_order``):
958
+ # the read it surfaces in next raises it rather than fall back.
855
959
  with self._lock:
856
960
  self._deferred_flush_error = e
857
961
  sys.stderr.write(
@@ -14,6 +14,14 @@ from typing import Any
14
14
 
15
15
  from docx._batching import run_sync
16
16
 
17
+ # What this SDK declares in ``client.capabilities``: it holds the anchored table
18
+ # ids (``t:N/R:C/K…@FIRST.LAST``) a docx-studio gives a client declaring it, and
19
+ # sends them back as given. docx-studio then finds each held table by its
20
+ # paragraphs, wherever an edit moved it, and refuses the command with
21
+ # ``StaleTable`` when no one table holds them (``docx.errors.StaleTableError``).
22
+ # An older docx-studio gives none, so the SDK needs no check.
23
+ TABLE_ANCHORS_CLIENT_CAPABILITY = "tableAnchors"
24
+
17
25
  # Session -> the capabilities its server reported, read once.
18
26
  _capabilities: "weakref.WeakKeyDictionary[Any, dict[str, Any]]" = weakref.WeakKeyDictionary()
19
27
 
@@ -5,7 +5,7 @@ Wire format (matches ``apps/api/src/commands/types.ts``):
5
5
  POST /docs/:id/commands
6
6
  {
7
7
  "client": {"type": "python-sdk", "version": "<x.y.z>",
8
- "capabilities": ["trackedAuthoring", "trackedMinimalParagraphText"]},
8
+ "capabilities": ["trackedAuthoring", "trackedMinimalParagraphText", "tableAnchors"]},
9
9
  "commands": [
10
10
  {"type": "CreateParagraph", "text": "Hi", "at": {"kind": "documentEnd"}},
11
11
  ...
@@ -42,8 +42,9 @@ from requests.adapters import HTTPAdapter
42
42
  from urllib3.util.retry import Retry
43
43
 
44
44
  from docx._batching import call_abandoned, note_progress
45
- from docx._buffer import CommandBuffer
45
+ from docx._buffer import CommandBuffer, keep_reported_message
46
46
  from docx._tracked import TRACKED_AUTHORING_CAPABILITY, TRACKED_MINIMAL_PARAGRAPH_TEXT_CAPABILITY
47
+ from docx._capabilities import TABLE_ANCHORS_CLIENT_CAPABILITY
47
48
  from docx._timeouts import (
48
49
  DEFAULT_OP_TIMEOUT_SECONDS,
49
50
  LONG_RUNNING_COMMAND_TIMEOUT_SECONDS,
@@ -194,6 +195,7 @@ from docx.errors import (
194
195
  NotFoundError,
195
196
  NotNumberedListItemError,
196
197
  SessionError,
198
+ StaleTableError,
197
199
  _missing_block_id,
198
200
  )
199
201
  from docx.exceptions import InvalidSpanError
@@ -284,6 +286,7 @@ def _flush_outcome(
284
286
  f" applied{not_sent}.{offered} Re-read the document to see whether that"
285
287
  " request landed before re-applying anything."
286
288
  )
289
+ keep_reported_message(exc)
287
290
  exc.args = (f"{prefix}\n\n{exc.args[0] if exc.args else ''}", *exc.args[1:])
288
291
  return exc
289
292
  applied = min(start + landed, total)
@@ -333,6 +336,7 @@ def _flush_outcome(
333
336
  f" nothing{not_sent}. `exc.unapplied_commands` holds the {len(unapplied)}"
334
337
  f" that did not land{which}. {redo}"
335
338
  )
339
+ keep_reported_message(exc)
336
340
  exc.args = (f"{prefix}\n\n{exc.args[0] if exc.args else ''}", *exc.args[1:])
337
341
  return exc
338
342
 
@@ -838,6 +842,13 @@ def _http_post_json(
838
842
  err_obj,
839
843
  partial_applied,
840
844
  )
845
+ # A held ``Table`` whose table is gone (``docx.table`` anchored ids).
846
+ if err_obj.get("error") == "StaleTable":
847
+ raise _reported(
848
+ StaleTableError(str(err_obj.get("message") or base_msg)),
849
+ err_obj,
850
+ partial_applied,
851
+ )
841
852
  # ``Paragraph.restart_numbering`` on a paragraph that takes no number.
842
853
  if err_obj.get("error") == "NotNumberedListItem":
843
854
  raise _reported(
@@ -1198,10 +1209,11 @@ class HttpClient:
1198
1209
  "version": _sdk_version(),
1199
1210
  # Keeps its offsets as a tracked edit moves them, and reads a
1200
1211
  # paragraph's length back after a tracked ``paragraph.text``
1201
- # (``docx._tracked``).
1212
+ # (``docx._tracked``); holds anchored table ids (``docx.table``).
1202
1213
  "capabilities": [
1203
1214
  TRACKED_AUTHORING_CAPABILITY,
1204
1215
  TRACKED_MINIMAL_PARAGRAPH_TEXT_CAPABILITY,
1216
+ TABLE_ANCHORS_CLIENT_CAPABILITY,
1205
1217
  ],
1206
1218
  },
1207
1219
  "commands": [
@@ -24,6 +24,7 @@ from __future__ import annotations
24
24
 
25
25
  import io
26
26
  import re as _re
27
+ import warnings
27
28
  import zipfile
28
29
  from dataclasses import dataclass, field, replace
29
30
  from typing import Any, Iterable
@@ -32,6 +33,41 @@ from xml.etree import ElementTree as ET
32
33
 
33
34
  WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
34
35
  W_NS = f"{{{WORD_NS}}}"
36
+ W14_NS = "{http://schemas.microsoft.com/office/word/2010/wordml}"
37
+
38
+
39
+ class UnidentifiedTableRewriteWarning(UserWarning):
40
+ """Emitted when the export rescue skips its rewrites of a table the
41
+ script created because it cannot find that table in the exported
42
+ ``word/document.xml``: no one table holds the paragraphs its anchors
43
+ name (a table was added before it, or it is gone), or, its id carrying
44
+ no anchors (a docx-studio from before anchored table ids), the table at
45
+ its place is not provably it, or more than one table looks like it. The
46
+ table keeps the formatting docx-studio wrote for it; no other table is
47
+ patched in its place."""
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class CreatedTableIdentity:
52
+ """How the export rescue tells, in the exported body, a table the script
53
+ created (``Document.add_table``).
54
+
55
+ ``anchors``: the ``w14:paraId``s of its first and last own paragraphs,
56
+ from the id docx-studio gives it now; the table is the one holding them.
57
+ From a docx-studio without anchored ids, ``position`` (its ``t:N``) names
58
+ a candidate, which is taken only when it holds what the script knows of
59
+ its table and no other table of the body does, nested ones included:
60
+ ``rows`` × ``columns`` cells, and the ``texts`` the script wrote to them;
61
+ with none written, the size it was ``created`` with and an empty first
62
+ cell. Neither (``CreatedTableIdentity()``): not to be found.
63
+ """
64
+
65
+ anchors: tuple[str, ...] = ()
66
+ position: int | None = None
67
+ rows: int | None = None
68
+ columns: int | None = None
69
+ texts: tuple[tuple[int, int, str], ...] = ()
70
+ created: tuple[int, int] | None = None
35
71
 
36
72
 
37
73
  # Every well-known OOXML namespace that may appear in a DOCX Studio-exported
@@ -249,6 +285,134 @@ class CellAddress:
249
285
  part_key: str | None = None
250
286
 
251
287
 
288
+ def _own_paragraph_ids(tbl: ET.Element) -> set[str]:
289
+ """The ``w14:paraId`` of each paragraph ``tbl`` holds as its own: not
290
+ one of a table nested in it."""
291
+ ids: set[str] = set()
292
+ pending = list(tbl)
293
+ while pending:
294
+ element = pending.pop()
295
+ if element.tag == f"{W_NS}tbl":
296
+ continue
297
+ if element.tag == f"{W_NS}p":
298
+ para_id = element.get(f"{W14_NS}paraId")
299
+ if para_id:
300
+ ids.add(para_id)
301
+ continue
302
+ pending.extend(list(element))
303
+ return ids
304
+
305
+
306
+ def _cell_text(tbl: ET.Element, row: int, col: int) -> str | None:
307
+ """The text of the cell of ``tbl`` starting at grid (``row``, ``col``), its
308
+ own paragraphs' joined with ``"\\n"`` (a tab ``"\\t"``, a line break
309
+ ``"\\n"``); ``None`` when no cell starts there."""
310
+ rows = tbl.findall(f"{W_NS}tr")
311
+ if not 0 <= row < len(rows):
312
+ return None
313
+ cell = next((tc for start, tc in _cells_by_grid_column(rows[row]) if start == col), None)
314
+ if cell is None:
315
+ return None
316
+ paragraphs: list[str] = []
317
+ for paragraph in cell.findall(f"{W_NS}p"):
318
+ parts: list[str] = []
319
+ for run in paragraph.iter(f"{W_NS}r"):
320
+ for element in run:
321
+ if element.tag == f"{W_NS}t":
322
+ parts.append(element.text or "")
323
+ elif element.tag == f"{W_NS}tab":
324
+ parts.append("\t")
325
+ elif element.tag == f"{W_NS}br":
326
+ parts.append("\n")
327
+ paragraphs.append("".join(parts))
328
+ return "\n".join(paragraphs)
329
+
330
+
331
+ def _mismatch(tbl: ET.Element, identity: CreatedTableIdentity) -> str | None:
332
+ """Why the table ``tbl`` is not provably the created table ``identity``
333
+ tells (:class:`CreatedTableIdentity`); ``None`` when it is."""
334
+ if identity.rows is None or identity.columns is None:
335
+ return "the script does not know its table's size"
336
+ size = (len(tbl.findall(f"{W_NS}tr")), len(tbl.findall(f"{W_NS}tblGrid/{W_NS}gridCol")))
337
+ if size != (identity.rows, identity.columns):
338
+ return f"it holds {size[0]} × {size[1]} cells, the script's table {identity.rows} × {identity.columns}"
339
+ for row, col, text in identity.texts:
340
+ if _cell_text(tbl, row, col) != text:
341
+ return f"its cell ({row}, {col}) does not hold the text the script wrote there"
342
+ if not identity.texts and (identity.created != size or _cell_text(tbl, 0, 0) != ""):
343
+ return "the script wrote no text to tell it by, and it is not as the script created it"
344
+ return None
345
+
346
+
347
+ def _locate_body_tables(
348
+ body: ET.Element, created_tables: "dict[int, CreatedTableIdentity | None]"
349
+ ) -> dict[int, int | None]:
350
+ """Where each created table is among the body's tables now
351
+ (``created_tables``, by ``Table._doc_index``): the one table holding a
352
+ paragraph its anchors name, or, without anchors, the table at its
353
+ position when it is what the script knows of its table
354
+ (:func:`_mismatch`) and no other table of the body is, nested ones
355
+ included (with a look-alike, a position that may have shifted proves
356
+ nothing); ``None``, with an
357
+ :class:`UnidentifiedTableRewriteWarning`, for a table that is gone
358
+ (``None``), or that cannot be told."""
359
+ tables = body.findall(f"{W_NS}tbl")
360
+ every_table = list(body.iter(f"{W_NS}tbl"))
361
+ owned = [_own_paragraph_ids(tbl) for tbl in tables]
362
+ located: dict[int, int | None] = {}
363
+ for doc_index, identity in created_tables.items():
364
+ wanted = set(identity.anchors) if identity is not None else set()
365
+ holders = [index for index, ids in enumerate(owned) if wanted & ids]
366
+ located[doc_index] = holders[0] if len(holders) == 1 else None
367
+ reason = ""
368
+ if identity is None:
369
+ reason = "docx-studio refused its id, so the table is gone"
370
+ elif wanted:
371
+ if located[doc_index] is None:
372
+ held = "no" if not holders else str(len(holders))
373
+ reason = f"{held} tables of the export hold the paragraphs its anchors name"
374
+ elif identity.position is None or not 0 <= identity.position < len(tables):
375
+ reason = "its id carries no anchors (a docx-studio from before anchored table ids) and names no table of the export"
376
+ else:
377
+ alike = sum(1 for tbl in every_table if _mismatch(tbl, identity) is None)
378
+ mismatch = _mismatch(tables[identity.position], identity)
379
+ if alike > 1:
380
+ reason = (
381
+ f"ambiguous: {alike} tables of the export match what the script knows of it"
382
+ )
383
+ elif mismatch is None:
384
+ located[doc_index] = identity.position
385
+ else:
386
+ reason = (
387
+ f"the table at its place (t:{identity.position}) is not provably it: {mismatch}"
388
+ )
389
+ if located[doc_index] is None:
390
+ warnings.warn(
391
+ UnidentifiedTableRewriteWarning(
392
+ f"The export rescue skipped its rewrites of table {doc_index} this script created: "
393
+ f"{reason}. The table keeps the formatting docx-studio wrote for it, and no other "
394
+ "table was patched in its place."
395
+ ),
396
+ stacklevel=4,
397
+ )
398
+ return located
399
+
400
+
401
+ def _relocated_ops(ops: dict, located: dict[int, int | None]) -> dict:
402
+ """``ops`` with each body address's ``table_doc_index`` re-addressed to
403
+ where its table is now (``located``), dropping those of a table that
404
+ cannot be found; a header or footer address (``part_key``) as it is."""
405
+ out: dict = {}
406
+ for address, value in ops.items():
407
+ if getattr(address, "part_key", None) is not None:
408
+ out[address] = value
409
+ continue
410
+ index = located.get(address.table_doc_index)
411
+ if index is not None:
412
+ out[replace(address, table_doc_index=index)] = value
413
+ return out
414
+
415
+
252
416
  def _cells_by_grid_column(tr: ET.Element) -> list[tuple[int, ET.Element]]:
253
417
  """Each ``<w:tc>`` of a row with the grid column it starts at.
254
418
 
@@ -505,6 +669,26 @@ class PostExportOps:
505
669
  _header_footer_relocations: list["HeaderFooterRelocation"] = field(
506
670
  default_factory=list,
507
671
  )
672
+ # The tables ``Document.add_table`` created, by ``Table._doc_index``:
673
+ # the export reads each one's id, whose anchors find it in the exported
674
+ # body (:func:`_locate_body_tables`).
675
+ _body_tables: dict[int, Any] = field(default_factory=dict)
676
+
677
+ def register_body_table(self, doc_index: int, table: Any) -> None:
678
+ """Remember the table ``Document.add_table`` created at ``doc_index``."""
679
+ self._body_tables[doc_index] = table
680
+
681
+ def body_tables_rewritten(self) -> dict[int, Any]:
682
+ """The created tables a pending body rewrite addresses, by ``doc_index``
683
+ (``None`` for an index no created table holds)."""
684
+ indexes: set[int] = set()
685
+ for ops in (self._cell_format_ops, self._cell_run_format_ops, self._cell_hyperlink_ops):
686
+ indexes.update(address.table_doc_index for address in ops if address.part_key is None)
687
+ indexes.update(address.table_doc_index for address in self._cell_shading_ops)
688
+ indexes.update(self._table_borders_ops)
689
+ indexes.update(address.table_doc_index for address in self._row_height_ops)
690
+ indexes.update(address.table_doc_index for address in self._row_cant_split_ops)
691
+ return {index: self._body_tables.get(index) for index in sorted(indexes)}
508
692
 
509
693
  def has_pending(self) -> bool:
510
694
  return (
@@ -549,6 +733,7 @@ class PostExportOps:
549
733
  self._row_height_ops.clear()
550
734
  self._row_cant_split_ops.clear()
551
735
  self._header_footer_relocations.clear()
736
+ self._body_tables.clear()
552
737
 
553
738
  def add_row_cant_split(
554
739
  self,
@@ -1045,7 +1230,12 @@ class PostExportOps:
1045
1230
  ),
1046
1231
  )
1047
1232
 
1048
- def apply_to_docx(self, docx_bytes: bytes) -> bytes:
1233
+ def apply_to_docx(
1234
+ self,
1235
+ docx_bytes: bytes,
1236
+ *,
1237
+ created_tables: "dict[int, CreatedTableIdentity | None] | None" = None,
1238
+ ) -> bytes:
1049
1239
  """Return ``docx_bytes`` with every recorded rewrite applied.
1050
1240
 
1051
1241
  ``Document.export_docx`` calls it only while a rewrite is pending
@@ -1053,9 +1243,17 @@ class PostExportOps:
1053
1243
  it is: every ``w:sectPr`` in a paragraph ends a real section of the
1054
1244
  room (``Document.add_section`` writes them; the page-break markers
1055
1245
  DOCX Studio 1.8.1 exported in their place are gone with that engine).
1246
+
1247
+ ``created_tables`` tells, for each created table a body rewrite
1248
+ addresses (:meth:`body_tables_rewritten`), how to find it
1249
+ (:class:`CreatedTableIdentity`): the rewrite finds the table wherever
1250
+ it is in the exported body, and skips
1251
+ (:class:`UnidentifiedTableRewriteWarning`) one it cannot. Without it,
1252
+ a table is found by its index among the body's tables.
1056
1253
  """
1057
1254
  return _rewrite_word_document_xml(
1058
1255
  docx_bytes,
1256
+ created_tables=created_tables,
1059
1257
  cell_format_ops=self._cell_format_ops,
1060
1258
  cell_run_format_ops=self._cell_run_format_ops,
1061
1259
  cell_hyperlink_ops=self._cell_hyperlink_ops,
@@ -1089,6 +1287,7 @@ def _rewrite_word_document_xml(
1089
1287
  header_footer_relocations: "list[HeaderFooterRelocation] | None" = None,
1090
1288
  row_height_ops: "dict[RowHeightAddress, RowHeightProps] | None" = None,
1091
1289
  row_cant_split_ops: "dict[RowHeightAddress, bool] | None" = None,
1290
+ created_tables: "dict[int, CreatedTableIdentity | None] | None" = None,
1092
1291
  ) -> bytes:
1093
1292
  """Unzip the ``.docx``, apply rewrites to ``word/document.xml``,
1094
1293
  re-zip and return the result.
@@ -1209,6 +1408,23 @@ def _rewrite_word_document_xml(
1209
1408
  body, header_footer_relocations,
1210
1409
  )
1211
1410
 
1411
+ # The body rewrites address a created table by ``Table._doc_index``;
1412
+ # with anchors, each is re-addressed to where its paragraphs are in
1413
+ # the body now, and those of a table that cannot be found are dropped.
1414
+ if created_tables is not None:
1415
+ located = _locate_body_tables(body, created_tables)
1416
+ body_cell_format_ops = _relocated_ops(body_cell_format_ops, located)
1417
+ cell_run_format_ops = _relocated_ops(cell_run_format_ops, located)
1418
+ cell_hyperlink_ops = _relocated_ops(cell_hyperlink_ops, located)
1419
+ cell_shading_ops = _relocated_ops(cell_shading_ops, located)
1420
+ table_borders_ops = {
1421
+ index: edges
1422
+ for doc_index, edges in table_borders_ops.items()
1423
+ if (index := located.get(doc_index)) is not None
1424
+ }
1425
+ row_height_ops = _relocated_ops(row_height_ops or {}, located)
1426
+ row_cant_split_ops = _relocated_ops(row_cant_split_ops or {}, located)
1427
+
1212
1428
  # Body cell-format ops apply AFTER relocation so the body
1213
1429
  # table indices align with ``Table._doc_index``. Pre-relocation,
1214
1430
  # body table 0 was the misplaced header/footer table (which
@@ -332,6 +332,45 @@ def note_picture(run: "Run", picture_id: str, offset: int, *, before: str | None
332
332
  record.pictures.insert(index, (picture_id, offset))
333
333
 
334
334
 
335
+ def read_stamp() -> int:
336
+ """The moment a read gives its runs (``Paragraph.runs``): a run ``add_run``
337
+ creates after it has a higher ``seq``."""
338
+ return next(_SEQ)
339
+
340
+
341
+ def later_runs_at_end(run: "Run") -> int | None:
342
+ """How many runs after ``run`` end where it does, by which docx-studio
343
+ tells ``run`` from them (``run.laterRuns``) where the offset cannot: an
344
+ empty run ``add_run()`` made after it, or one of breaks alone, ends there
345
+ too.
346
+
347
+ For a run ``add_run`` created, the runs it created after it; for a run a
348
+ read gave (``Paragraph.runs``), the later runs of that read and the runs
349
+ ``add_run`` created since. Each where it ends now, not where it ended at
350
+ the read: a write through a run moves the live proxies (:func:`track_run`),
351
+ as clearing a run of text after a run of a break leaves an empty run that
352
+ ends where the break's run does. A read run holds the next run of its
353
+ read (``_next_read``), so the later ones stay live, and current, while it
354
+ is. None for a run proxy that is neither, which the SDK cannot tell from
355
+ the runs around it.
356
+ """
357
+ end = run._range[1]
358
+ records = _records_of(run)
359
+ own = getattr(run, "_run_record", None) or getattr(run, "_owner_record", None)
360
+ if own is not None and own in records:
361
+ return sum(1 for other in records[records.index(own) + 1 :] if other.range[1] == end)
362
+ read_seq = getattr(run, "_read_seq", None)
363
+ if read_seq is None:
364
+ return None
365
+ later = 0
366
+ following: Run | None = getattr(run, "_next_read", None)
367
+ while following is not None:
368
+ if following._range[1] == end:
369
+ later += 1
370
+ following = getattr(following, "_next_read", None)
371
+ return later + sum(1 for other in records if other.seq > read_seq and other.range[1] == end)
372
+
373
+
335
374
  def ends_last_at_its_end(run: "Run") -> bool:
336
375
  """True when no run ``add_run`` created after ``run`` ends where it does.
337
376