athena-python-docx 0.30.0__tar.gz → 0.31.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 (455) hide show
  1. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/AGENTS.md +23 -4
  2. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/PKG-INFO +1 -1
  3. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_buffer.py +107 -3
  5. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_capabilities.py +8 -0
  6. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_http_doc.py +15 -3
  7. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_postproc.py +217 -1
  8. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/comments.py +7 -2
  9. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/document.py +60 -6
  10. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/drawing/__init__.py +4 -1
  11. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/errors.py +18 -1
  12. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/revisions.py +4 -1
  13. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/section.py +16 -5
  14. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/shape.py +4 -1
  15. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/styles/style.py +107 -18
  16. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/table.py +173 -29
  17. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/hyperlink.py +10 -1
  18. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/paragraph.py +18 -5
  19. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/run.py +3 -2
  20. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/pyproject.toml +1 -1
  21. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/METHODOLOGY.md +35 -9
  22. athena_python_docx-0.31.0/tests/fidelity/op_snapshots/rw18_header_table_properties.json +34 -0
  23. athena_python_docx-0.31.0/tests/fidelity/op_snapshots/rw19_header_table_rows_columns.json +38 -0
  24. athena_python_docx-0.31.0/tests/fidelity/op_snapshots/rw20_header_table_merges.json +28 -0
  25. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/real_world_cases.py +74 -0
  26. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fixtures/field_reads.json +320 -20
  27. athena_python_docx-0.31.0/tests/live/baseline.json +478 -0
  28. athena_python_docx-0.31.0/tests/live/fixture_scripts.py +68 -0
  29. athena_python_docx-0.31.0/tests/live/reader.py +676 -0
  30. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/live/runner.py +140 -15
  31. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/live/test_live_runner.py +220 -2
  32. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/intentional_deviations.json +46 -2
  33. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_merge.py +17 -7
  34. athena_python_docx-0.31.0/tests/test_cell_nested_tables.py +82 -0
  35. athena_python_docx-0.31.0/tests/test_comments_first_read.py +101 -0
  36. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_export_docx_clean.py +1 -1
  37. athena_python_docx-0.31.0/tests/test_export_rescue_anchors.py +348 -0
  38. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_field_reads_python_docx_parity.py +20 -6
  39. athena_python_docx-0.31.0/tests/test_flushed_write_failures.py +590 -0
  40. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_http_transport.py +1 -1
  41. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_hyperlink_coalescing.py +40 -0
  42. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_iter_inner_content.py +35 -0
  43. athena_python_docx-0.31.0/tests/test_nested_table_refused.py +95 -0
  44. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_paragraph_delete.py +1 -0
  45. athena_python_docx-0.31.0/tests/test_style_tab_stops_read.py +127 -0
  46. athena_python_docx-0.31.0/tests/test_table_anchors.py +219 -0
  47. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_table_read_reuse.py +29 -0
  48. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/uv.lock +7 -7
  49. athena_python_docx-0.30.0/tests/live/baseline.json +0 -258
  50. athena_python_docx-0.30.0/tests/live/reader.py +0 -243
  51. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/.gitignore +0 -0
  52. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/CLAUDE.md +0 -0
  53. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/DOCX_EXEC_LAB.md +0 -0
  54. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/README.md +0 -0
  55. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_athena_extension.py +0 -0
  56. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_batching.py +0 -0
  57. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_citations.py +0 -0
  58. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_execution.py +0 -0
  59. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_http.py +0 -0
  60. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_image_utils.py +0 -0
  61. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_ptc.py +0 -0
  62. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_references.py +0 -0
  63. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_run_order.py +0 -0
  64. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_table_styles.py +0 -0
  65. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_timeouts.py +0 -0
  66. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/_tracked.py +0 -0
  67. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/api.py +0 -0
  68. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/blkcntnr.py +0 -0
  69. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/bookmarks.py +0 -0
  70. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/charts.py +0 -0
  71. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/client.py +0 -0
  72. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/commands.py +0 -0
  73. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/__init__.py +0 -0
  74. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/base.py +0 -0
  75. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/dml.py +0 -0
  76. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/section.py +0 -0
  77. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/shape.py +0 -0
  78. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/style.py +0 -0
  79. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/table.py +0 -0
  80. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/enum/text.py +0 -0
  81. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/exceptions.py +0 -0
  82. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/fields.py +0 -0
  83. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/footnotes.py +0 -0
  84. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/image/__init__.py +0 -0
  85. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/image/exceptions.py +0 -0
  86. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/image/image.py +0 -0
  87. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/math.py +0 -0
  88. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/opc/__init__.py +0 -0
  89. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/opc/coreprops.py +0 -0
  90. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/oxml/__init__.py +0 -0
  91. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/sdt.py +0 -0
  92. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/session.py +0 -0
  93. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/settings.py +0 -0
  94. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/shared.py +0 -0
  95. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/styles/__init__.py +0 -0
  96. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/styles/_table.py +0 -0
  97. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/styles/latent.py +0 -0
  98. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/styles/styles.py +0 -0
  99. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/__init__.py +0 -0
  100. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/font.py +0 -0
  101. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/pagebreak.py +0 -0
  102. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/parfmt.py +0 -0
  103. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/text/tabstops.py +0 -0
  104. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/toc.py +0 -0
  105. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/docx/typing.py +0 -0
  106. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/docx_exec_lab.py +0 -0
  107. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
  108. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
  109. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/docx_exec_lab_server.py +0 -0
  110. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/dump_wire_fixtures.py +0 -0
  111. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/release.sh +0 -0
  112. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/round_trip_smoke.py +0 -0
  113. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/smoke_test_block_not_found.py +0 -0
  114. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/scripts/validate_find_replace_asset.py +0 -0
  115. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/__init__.py +0 -0
  116. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/conftest.py +0 -0
  117. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/README.md +0 -0
  118. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/__init__.py +0 -0
  119. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/ab_probe_cases.py +0 -0
  120. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/ab_probe_runner.py +0 -0
  121. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/auto_gen_cases.py +0 -0
  122. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/binary_round_trip.py +0 -0
  123. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/cases.py +0 -0
  124. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/complex_cases.py +0 -0
  125. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/coverage_report.py +0 -0
  126. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/extract.py +0 -0
  127. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/extreme_cases.py +0 -0
  128. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/fake_session.py +0 -0
  129. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/firm_templates/README.md +0 -0
  130. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/firm_templates/__init__.py +0 -0
  131. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/firm_templates/_runner.py +0 -0
  132. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/firm_templates/extractor.py +0 -0
  133. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  134. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  135. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/local_runner.py +0 -0
  136. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/mega_cases.py +0 -0
  137. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshot.py +0 -0
  138. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  139. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  140. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  141. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  142. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  143. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  144. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  145. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  146. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  147. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  148. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  149. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  150. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  151. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  152. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  153. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  154. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  155. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  156. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  157. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  158. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  159. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  160. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  161. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  162. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  163. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  164. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  165. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  166. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  167. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  168. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  169. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  170. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  171. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  172. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  173. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  174. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  175. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  176. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  177. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  178. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  179. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  180. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  181. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  182. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  183. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  184. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  185. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  186. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  187. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  188. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  189. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  190. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  191. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  192. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  193. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  194. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  195. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  196. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  197. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  198. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  199. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  200. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  201. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  202. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  203. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  204. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  205. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  206. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  207. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  208. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  209. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  210. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  211. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  212. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  213. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  214. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  215. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  216. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  217. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  218. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  219. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  220. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  221. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  222. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  223. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  224. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  225. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  226. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  227. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  228. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  229. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  230. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  231. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  232. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  233. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  234. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  235. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  236. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  237. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  238. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  239. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  240. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  241. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  242. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  243. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  244. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  245. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  246. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  247. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  248. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  249. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  250. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  251. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  252. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  253. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  254. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  255. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  256. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  257. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  258. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  259. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  260. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  261. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  262. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  263. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  264. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  265. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  266. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  267. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  268. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  269. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  270. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  271. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  272. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  273. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  274. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  275. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  276. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  277. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  278. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  279. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  280. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  281. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  282. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  283. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  284. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  285. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw16_header_footer_tables.json +0 -0
  286. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/op_snapshots/rw17_header_letterhead_filled.json +0 -0
  287. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/ours_spec.json +0 -0
  288. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/parity_crawl.py +0 -0
  289. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/parity_diff.json +0 -0
  290. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/round_trip_tests.py +0 -0
  291. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/stock_spec.json +0 -0
  292. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  293. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fixtures/note_reference_reads.json +0 -0
  294. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fixtures/ruby_reads.json +0 -0
  295. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/fixtures/symbol_reads.json +0 -0
  296. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/live/__init__.py +0 -0
  297. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/live/sdk_driver.py +0 -0
  298. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/live/stock_driver.py +0 -0
  299. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/README.md +0 -0
  300. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/__init__.py +0 -0
  301. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/baseline_gaps.json +0 -0
  302. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/compare.py +0 -0
  303. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/introspect.py +0 -0
  304. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  305. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/reports/gap_report.json +0 -0
  306. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/run_parity.py +0 -0
  307. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/snapshots/athena_latest.json +0 -0
  308. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  309. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/parity/test_parity_gap.py +0 -0
  310. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_add_section.py +0 -0
  311. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_add_section_extract_items.py +0 -0
  312. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_athena_extension_creates_session_open.py +0 -0
  313. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_athena_extensions_contract.py +0 -0
  314. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_athena_extensions_registry.py +0 -0
  315. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_batch_chunking.py +0 -0
  316. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_batching_perf.py +0 -0
  317. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_block_not_found_error.py +0 -0
  318. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_buffer.py +0 -0
  319. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  320. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_add_table_not_supported.py +0 -0
  321. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_borders_spec.py +0 -0
  322. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_fastpath_runs.py +0 -0
  323. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
  324. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
  325. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_inner_format_stash.py +0 -0
  326. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_inner_run_format_stash.py +0 -0
  327. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_inner_run_guard.py +0 -0
  328. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_paragraphs_listed.py +0 -0
  329. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_text_plain_fastpath.py +0 -0
  330. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_cell_text_replace_semantics.py +0 -0
  331. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_citations.py +0 -0
  332. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_collapsed_range_format.py +0 -0
  333. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_color_format_theme.py +0 -0
  334. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_command_dataclasses.py +0 -0
  335. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_commands.py +0 -0
  336. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_comment_paragraphs.py +0 -0
  337. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_comments.py +0 -0
  338. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_content_control_value.py +0 -0
  339. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_core_properties.py +0 -0
  340. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_document_asset_id_property.py +0 -0
  341. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_document_clear.py +0 -0
  342. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_document_create.py +0 -0
  343. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_document_create_from_template.py +0 -0
  344. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_document_factory_validation.py +0 -0
  345. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_docx_exec_lab.py +0 -0
  346. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_docx_exec_lab_server.py +0 -0
  347. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_drawing_image.py +0 -0
  348. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
  349. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_empty_hyperlinks.py +0 -0
  350. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_enum_parity.py +0 -0
  351. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_execution_scope.py +0 -0
  352. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_field_refresh.py +0 -0
  353. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_field_runs.py +0 -0
  354. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_find_replace_session_open.py +0 -0
  355. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_image_url_data_uri.py +0 -0
  356. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_inline_page_break.py +0 -0
  357. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_inline_shape_alt_text.py +0 -0
  358. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_inline_shape_size.py +0 -0
  359. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_inline_shape_type.py +0 -0
  360. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_inline_shapes_floating.py +0 -0
  361. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_insert_deferred.py +0 -0
  362. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_list_styles.py +0 -0
  363. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_merged_cell_secondary_slot.py +0 -0
  364. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_merged_cells.py +0 -0
  365. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_missing_modules.py +0 -0
  366. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_note_reference_reads_python_docx_parity.py +0 -0
  367. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_oxml_shim.py +0 -0
  368. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_page_break.py +0 -0
  369. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_paragraph_runs_hyperlinks.py +0 -0
  370. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_paragraph_text_len_cache.py +0 -0
  371. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_parity_misc.py +0 -0
  372. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_parity_round2.py +0 -0
  373. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_partial_failure_cascade.py +0 -0
  374. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_pdf_export_and_page_renders.py +0 -0
  375. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_phase_a_behavior.py +0 -0
  376. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_phase_b_headers_footers.py +0 -0
  377. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_phase_c_tables.py +0 -0
  378. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_positional_cell_id.py +0 -0
  379. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
  380. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
  381. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_postproc_ref_restore.py +0 -0
  382. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_pr19766_review_fixes.py +0 -0
  383. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_ptc.py +0 -0
  384. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_python_docx_api_parity.py +0 -0
  385. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_read_run_ranges.py +0 -0
  386. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_repeated_node_read.py +0 -0
  387. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_restart_numbering.py +0 -0
  388. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_revisions.py +0 -0
  389. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_round_trip_smoke_script.py +0 -0
  390. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_ruby_reads_python_docx_parity.py +0 -0
  391. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_add_picture_anchor.py +0 -0
  392. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_edges.py +0 -0
  393. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_formatting_wire.py +0 -0
  394. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_identity.py +0 -0
  395. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_mark_inheritance.py +0 -0
  396. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_table_style_objects.py +0 -0
  397. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_text_python_docx.py +0 -0
  398. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_run_text_refreshes_paragraph.py +0 -0
  399. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_sdk_reads_served.py +0 -0
  400. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_section_read_lengths.py +0 -0
  401. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  402. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_add_picture.py +0 -0
  403. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_add_run.py +0 -0
  404. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  405. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  406. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_comments_get.py +0 -0
  407. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_document_audit.py +0 -0
  408. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_document_element.py +0 -0
  409. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_enum_section.py +0 -0
  410. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_font_audit.py +0 -0
  411. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_header_footer.py +0 -0
  412. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_hyperlink.py +0 -0
  413. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_inline_shape.py +0 -0
  414. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  415. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_misc.py +0 -0
  416. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  417. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  418. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  419. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_parfmt.py +0 -0
  420. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  421. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_run_add_break.py +0 -0
  422. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  423. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_run_style.py +0 -0
  424. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  425. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_run_text.py +0 -0
  426. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_run_underline.py +0 -0
  427. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_section_audit.py +0 -0
  428. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_section_dimensions.py +0 -0
  429. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_section_onoff.py +0 -0
  430. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_settings.py +0 -0
  431. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_shared_audit.py +0 -0
  432. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_style.py +0 -0
  433. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_styles.py +0 -0
  434. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_table_audit.py +0 -0
  435. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_table_cell.py +0 -0
  436. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  437. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_silent_stub_table_layout.py +0 -0
  438. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_smoke_integration.py +0 -0
  439. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_style_acceptance.py +0 -0
  440. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_style_font.py +0 -0
  441. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_style_formatting_writes.py +0 -0
  442. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_style_metadata_writes.py +0 -0
  443. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_style_names.py +0 -0
  444. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_style_setters_contract.py +0 -0
  445. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_symbol_reads_python_docx_parity.py +0 -0
  446. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_table_set_cell_perf.py +0 -0
  447. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_table_style_id_resolution.py +0 -0
  448. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_temporarily_unavailable.py +0 -0
  449. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_text_control_characters.py +0 -0
  450. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_tracked_revisions_offsets.py +0 -0
  451. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_uv_lock_version.py +0 -0
  452. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_validate_find_replace_asset_script.py +0 -0
  453. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_wire_contract.py +0 -0
  454. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_xml_attr_guard.py +0 -0
  455. {athena_python_docx-0.30.0 → athena_python_docx-0.31.0}/tests/test_zod_wire_contract.py +0 -0
@@ -986,13 +986,32 @@ architecture or a DOCX Studio constraint:
986
986
  `Table.autofit` reads `False` (python-docx leaves autofit on); its columns
987
987
  share 9,360 twips, where python-docx shares the section's text width; and
988
988
  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`;
989
+ cells too, where python-docx sets the `w:gridCol` alone. Its cell
990
+ paragraphs carry their own spacing (`w:spacing w:after="0" w:line="240"
991
+ w:lineRule="auto"`: no space after, single lines), so a cell paragraph's
992
+ `paragraph_format.space_after` reads `0` and `line_spacing` `1.0`, where
993
+ python-docx's `add_table` writes none. It also draws borders and a zebra
994
+ fill, which no python-docx read sees. Recorded as `live_deviations` for
995
+ the script cases' `tables.autofit`, `tables.widths`, `tables.cell_format`
996
+ and `tables.cell_paragraph_format` in `tests/parity/intentional_deviations.json`;
993
997
  an imported table's gaps stay gaps, and so do the two cases whose
994
998
  `cell_format` also differs for another cause (`except`).
995
999
 
1000
+ - **A held `Table` raises `StaleTableError` once its table is gone.** The SDK
1001
+ holds the anchored ids docx-studio gives it (`t:N/R:C/K…@FIRST.LAST`: the
1002
+ table's place, then the paraIds of its first and last paragraphs), and
1003
+ docx-studio finds the table by those paragraphs, so a table an edit moved (a
1004
+ row added above a table nested in a cell, a table added before it, a merge
1005
+ moving a nested table into the merged cell) is still the one read and
1006
+ written, as python-docx's proxies follow the elements they hold. When no
1007
+ table holds them (the table was deleted, or the cell holding it had its
1008
+ text replaced, which removes the tables it held), docx-studio refuses the
1009
+ batch and the SDK raises `StaleTableError`: "the table this proxy named is
1010
+ gone; re-read `document.tables`". python-docx writes the detached element
1011
+ and changes nothing; raising tells the script instead of losing its write
1012
+ (coordinator, 2026-10-14). A held `_Cell` or `_Row` is still found by its
1013
+ place in its table.
1014
+
996
1015
  - **`_Cell.merge` raises `InvalidSpanError` for every span that would cut
997
1016
  through a merged cell.** It merges what python-docx merges and refuses
998
1017
  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.31.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.31.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
@@ -37,6 +37,7 @@ from datetime import datetime, timezone
37
37
  from typing import TYPE_CHECKING
38
38
 
39
39
  from docx._batching import run_sync
40
+ from docx._buffer import writes_unapplied
40
41
  from docx.blkcntnr import BlockItemContainer
41
42
  from docx.errors import DocxError, NotFoundError
42
43
 
@@ -330,7 +331,9 @@ class Comment(BlockItemContainer):
330
331
  comment range listed without one, with empty text)."""
331
332
  try:
332
333
  result: object = run_sync(self._session.doc.comments.get({"id": self.comment_id}))
333
- except NotFoundError:
334
+ except NotFoundError as exc:
335
+ if writes_unapplied(exc):
336
+ raise
334
337
  result = None
335
338
  read = result.get("text") if isinstance(result, dict) else None
336
339
  if isinstance(read, str):
@@ -540,7 +543,9 @@ class Comments:
540
543
  result: object = run_sync(
541
544
  self._session.doc.comments.get({"id": str(comment_id)}),
542
545
  )
543
- except NotFoundError:
546
+ except NotFoundError as exc:
547
+ if writes_unapplied(exc):
548
+ raise
544
549
  return None
545
550
  if isinstance(result, dict):
546
551
  # DOCX Studio may surface the id under ``id`` or ``commentId`` —