athena-python-docx 0.7.0__tar.gz → 0.9.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 (312) hide show
  1. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/CLAUDE.md +48 -2
  2. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/PKG-INFO +1 -1
  3. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_buffer.py +127 -75
  5. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_http_doc.py +175 -6
  6. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_ptc.py +32 -49
  7. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/commands.py +9 -2
  8. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/document.py +41 -39
  9. athena_python_docx-0.9.0/docx/errors.py +133 -0
  10. athena_python_docx-0.9.0/docx/oxml/__init__.py +148 -0
  11. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/table.py +90 -19
  12. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/pyproject.toml +1 -1
  13. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -6
  14. athena_python_docx-0.9.0/tests/fidelity/op_snapshots/17_table_basic.json +4 -0
  15. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -6
  16. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +2 -1
  17. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/23_nested_table.json +0 -1
  18. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -1
  19. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/35_full_report.json +12 -10
  20. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -100
  21. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -1
  22. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -4
  23. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -1
  24. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -13
  25. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +2 -1
  26. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +3 -1
  27. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -1
  28. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +800 -401
  29. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -19
  30. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -1
  31. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -3
  32. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -30
  33. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +18 -10
  34. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -10
  35. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -100
  36. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -1
  37. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -4
  38. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -50
  39. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -2
  40. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +90 -66
  41. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +240 -81
  42. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -1
  43. athena_python_docx-0.9.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json +211 -0
  44. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +120 -90
  45. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +18 -15
  46. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +18 -19
  47. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +28 -23
  48. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +32 -21
  49. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +40 -31
  50. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +8 -7
  51. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +64 -32
  52. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -12
  53. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -13
  54. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -8
  55. athena_python_docx-0.7.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json → athena_python_docx-0.9.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +58 -47
  56. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -2
  57. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/baseline_gaps.json +1 -1
  58. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/compare.py +16 -1
  59. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/reports/GAP_ANALYSIS.md +3 -4
  60. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/reports/gap_report.json +3 -12
  61. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/snapshots/athena_latest.json +374 -59
  62. athena_python_docx-0.9.0/tests/test_block_not_found_error.py +287 -0
  63. athena_python_docx-0.9.0/tests/test_cell_text_plain_fastpath.py +60 -0
  64. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_command_dataclasses.py +10 -1
  65. athena_python_docx-0.9.0/tests/test_insert_deferred.py +138 -0
  66. athena_python_docx-0.9.0/tests/test_oxml_shim.py +123 -0
  67. athena_python_docx-0.9.0/tests/test_partial_failure_cascade.py +346 -0
  68. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_ptc.py +246 -10
  69. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/uv.lock +1 -1
  70. athena_python_docx-0.7.0/docx/errors.py +0 -45
  71. athena_python_docx-0.7.0/tests/fidelity/op_snapshots/17_table_basic.json +0 -5
  72. athena_python_docx-0.7.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -181
  73. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/.gitignore +0 -0
  74. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/README.md +0 -0
  75. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_batching.py +0 -0
  76. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_http.py +0 -0
  77. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_image_utils.py +0 -0
  78. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/api.py +0 -0
  79. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/client.py +0 -0
  80. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/comments.py +0 -0
  81. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/__init__.py +0 -0
  82. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/section.py +0 -0
  83. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/style.py +0 -0
  84. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/table.py +0 -0
  85. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/text.py +0 -0
  86. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/exceptions.py +0 -0
  87. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/opc/__init__.py +0 -0
  88. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/opc/coreprops.py +0 -0
  89. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/revisions.py +0 -0
  90. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/section.py +0 -0
  91. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/settings.py +0 -0
  92. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/shape.py +0 -0
  93. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/shared.py +0 -0
  94. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/styles/__init__.py +0 -0
  95. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/styles/style.py +0 -0
  96. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/styles/styles.py +0 -0
  97. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/__init__.py +0 -0
  98. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/font.py +0 -0
  99. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/hyperlink.py +0 -0
  100. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/pagebreak.py +0 -0
  101. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/paragraph.py +0 -0
  102. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/parfmt.py +0 -0
  103. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/run.py +0 -0
  104. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/tabstops.py +0 -0
  105. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/typing.py +0 -0
  106. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/scripts/publish.sh +0 -0
  107. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/scripts/release.sh +0 -0
  108. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/scripts/round_trip_smoke.py +0 -0
  109. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/__init__.py +0 -0
  110. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/conftest.py +0 -0
  111. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/METHODOLOGY.md +0 -0
  112. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/README.md +0 -0
  113. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/__init__.py +0 -0
  114. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/ab_probe_cases.py +0 -0
  115. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/ab_probe_runner.py +0 -0
  116. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/auto_gen_cases.py +0 -0
  117. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/binary_round_trip.py +0 -0
  118. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/cases.py +0 -0
  119. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/complex_cases.py +0 -0
  120. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/coverage_report.py +0 -0
  121. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/extract.py +0 -0
  122. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/extreme_cases.py +0 -0
  123. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/fake_session.py +0 -0
  124. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/local_runner.py +0 -0
  125. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/mega_cases.py +0 -0
  126. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshot.py +0 -0
  127. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  128. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  129. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  130. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  131. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  132. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  133. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  134. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  135. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  136. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  137. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  138. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
  139. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  140. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  141. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  142. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  143. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  144. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  145. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  146. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  147. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  148. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  149. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  150. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  151. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  152. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  153. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  154. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  155. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  156. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  157. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  158. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  159. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  160. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  161. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  162. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  163. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  164. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  165. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  166. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  167. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  168. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  169. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  170. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  171. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  172. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  173. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  174. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  175. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  176. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  177. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  178. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  179. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  180. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  181. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  182. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  183. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  184. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  185. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  186. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  187. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  188. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  189. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  190. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  191. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  192. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  193. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  194. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  195. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  196. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  197. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  198. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  199. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  200. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  201. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  202. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  203. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  204. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  205. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  206. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  207. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  208. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  209. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  210. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  211. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  212. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  213. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  214. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  215. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  216. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  217. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  218. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  219. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  220. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  221. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  222. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  223. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  224. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  225. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  226. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  227. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  228. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  229. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  230. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  231. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  232. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  233. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  234. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  235. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/ours_spec.json +0 -0
  236. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/parity_crawl.py +0 -0
  237. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/parity_diff.json +0 -0
  238. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/real_world_cases.py +0 -0
  239. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/round_trip_tests.py +0 -0
  240. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/runner.py +0 -0
  241. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/stock_spec.json +0 -0
  242. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  243. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/README.md +0 -0
  244. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/__init__.py +0 -0
  245. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/intentional_deviations.json +0 -0
  246. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/introspect.py +0 -0
  247. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/run_parity.py +0 -0
  248. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  249. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/test_parity_gap.py +0 -0
  250. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_batching_perf.py +0 -0
  251. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_buffer.py +0 -0
  252. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_collapsed_range_format.py +0 -0
  253. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_commands.py +0 -0
  254. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_comments.py +0 -0
  255. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_document_create.py +0 -0
  256. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_http_transport.py +0 -0
  257. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_hyperlink_coalescing.py +0 -0
  258. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_iter_inner_content.py +0 -0
  259. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_list_styles.py +0 -0
  260. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_merged_cells.py +0 -0
  261. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_paragraph_text_len_cache.py +0 -0
  262. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_parity_misc.py +0 -0
  263. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_parity_round2.py +0 -0
  264. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_phase_a_behavior.py +0 -0
  265. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_phase_b_headers_footers.py +0 -0
  266. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_phase_c_tables.py +0 -0
  267. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_pr19766_review_fixes.py +0 -0
  268. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_python_docx_api_parity.py +0 -0
  269. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_revisions.py +0 -0
  270. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  271. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_add_picture.py +0 -0
  272. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_add_run.py +0 -0
  273. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  274. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  275. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_comments_get.py +0 -0
  276. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_document_audit.py +0 -0
  277. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_document_element.py +0 -0
  278. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_enum_section.py +0 -0
  279. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_font_audit.py +0 -0
  280. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_header_footer.py +0 -0
  281. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_hyperlink.py +0 -0
  282. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_inline_shape.py +0 -0
  283. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  284. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_misc.py +0 -0
  285. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  286. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  287. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  288. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_parfmt.py +0 -0
  289. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  290. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_add_break.py +0 -0
  291. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  292. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_style.py +0 -0
  293. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  294. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_text.py +0 -0
  295. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_underline.py +0 -0
  296. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_section_audit.py +0 -0
  297. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_section_dimensions.py +0 -0
  298. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_section_onoff.py +0 -0
  299. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_settings.py +0 -0
  300. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_shared_audit.py +0 -0
  301. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_style.py +0 -0
  302. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_styles.py +0 -0
  303. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_audit.py +0 -0
  304. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_cell.py +0 -0
  305. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  306. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_layout.py +0 -0
  307. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_smoke_integration.py +0 -0
  308. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_style_acceptance.py +0 -0
  309. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_style_font.py +0 -0
  310. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_style_setters_contract.py +0 -0
  311. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_wire_contract.py +0 -0
  312. {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_zod_wire_contract.py +0 -0
@@ -39,6 +39,18 @@ These standard python-docx members don't apply to a Superdoc-backed SDK:
39
39
  `Comment.tables` — SuperDoc models a comment as a single text body,
40
40
  not a `BlockItemContainer`. Use `Comment.text` instead. The
41
41
  paragraph/table-bearing surface raises `CommentsNotImplementedError`.
42
+ - **`docx.oxml.*`** — the entire raw-OOXML module tree (``ns.qn``,
43
+ ``OxmlElement``, ``CT_*`` classes, etc.) is unavailable because the
44
+ SDK is HTTP-only against a SuperDoc Y.Doc; there is no local lxml
45
+ tree to manipulate. As of 0.8.0, ``docx.oxml`` is a real stub
46
+ package that raises a typed
47
+ :class:`docx.oxml.OxmlNotAvailableError` (subclass of
48
+ ``ImportError``) on any attribute access, with an inline pointer at
49
+ the high-level API (``Document.add_paragraph``, ``Run.bold``,
50
+ ``_Cell.text``, …) or the typed command surface in
51
+ ``docx.commands``. Pre-0.8.0 this used to fail with the stdlib's
52
+ generic ``ModuleNotFoundError: No module named 'docx.oxml'`` and
53
+ agents had to retry to discover the gap.
42
54
 
43
55
  ### Phase-3b upstream-blocked surface
44
56
 
@@ -149,13 +161,47 @@ This is a **thin HTTP client** that mimics the sync python-docx API.
149
161
  the context-manager exit drain explicitly; `docx.flush_all()` is
150
162
  the Daytona-prelude hook that flushes every live buffer in the
151
163
  process.
152
- - Other response-bearing ops without a client id (`Insert`,
153
- `ListsCreate`, `ListsAttach`, `CommentsCreate`, `CommentsPatch`,
164
+ - **Insert deferral (0.8.0+):** `Insert` was removed from the
165
+ response-bearing set because every in-tree caller
166
+ (`Paragraph._insert_run_text_segments`, `Run.add_text`, `_Cell.text`)
167
+ ignores the response — the Run proxy wraps a known client-side range
168
+ and doesn't need the server's echoed node id. Net result: a styled
169
+ run (one `add_run` + 6 format setters) ships as 1 HTTP request
170
+ instead of 2. The remaining response-bearing ops without a client id
171
+ (`ListsCreate`, `ListsAttach`, `CommentsCreate`, `CommentsPatch`,
154
172
  `TrackChangesDecide`) keep their eager-flush semantics so callers
155
173
  reading back ids still see real data. A `Document.add_paragraph`
156
174
  with `style="List Bullet"` flushes the queued CreateParagraph in
157
175
  the same batch as the follow-up `ListsCreate` — still one HTTP
158
176
  request per logical operation.
177
+ - **Plain-text cell fast path (0.8.0+):** `_Cell.text = value` skips
178
+ the eager `doc.markdownToFragment` query when ``value`` contains no
179
+ markdown control characters (the dominant case for tabular data —
180
+ numbers, labels, currency strings). The fragment is built locally
181
+ and the follow-up `doc.insert` is buffered, so plain cell
182
+ assignments are 0 HTTP requests until the next flush. See
183
+ ``docx.table._is_plain_text`` for the trigger predicate.
184
+ - **Partial-failure cascade fix (0.9.0+):** when an HTTP batch raises
185
+ on a 207 partial-failure (e.g. one cell-paragraph mistake mid-script),
186
+ the SDK now rewrites ``proxy_id_refs`` for the ``applied`` prefix
187
+ the server reports as successful — BEFORE re-raising. Pre-0.9.0 the
188
+ rewrite was inside the success-path-only branch, so every Create that
189
+ committed server-side left its Python proxy stuck on the client UUID;
190
+ the next batch shipped dead client UUIDs and the cascade restarted.
191
+ Preview-session ``thread_bafba02b`` turned one cell-paragraph error
192
+ into thirteen downstream "Block not found" failures + a
193
+ ``DOCUMENT_IDENTITY_CONFLICT`` before this fix. The path goes
194
+ ``_http_post_json`` (attaches ``applied[]`` via
195
+ ``DocxError.with_partial_applied``) → ``CommandBuffer.flush`` /
196
+ ``_eager_flush_with`` (catches ``DocxError``, drains the prefix
197
+ through ``_apply_proxy_id_rewrites``, re-raises). See
198
+ ``tests/test_partial_failure_cascade.py`` for the contract.
199
+ - **Table-query hint (0.9.0+):** ``BlockNotFoundError`` on
200
+ ``TablesGet`` / ``TablesGetCells`` / etc. used to fall through with
201
+ no hint (the cell-paragraph workaround doesn't apply). Now carries
202
+ a dedicated "stale client-table-UUID" hint pointing at the actual
203
+ recovery (``doc.save()`` to drain pending + re-anchor proxies). See
204
+ ``_TABLE_CLIENT_ID_HINT`` in ``docx/_http_doc.py``.
159
205
  - The path-proxy in `_http_doc.py` is an internal translation layer:
160
206
  call sites that look like `await self._session.doc.create.paragraph(p)`
161
207
  resolve to `CommandBuffer.call(CreateParagraph(**p))`. Rewriting call
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -6,7 +6,7 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.7.0"
9
+ __version__ = "0.9.0"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -35,11 +35,94 @@ from typing import TYPE_CHECKING, Any
35
35
 
36
36
  from docx import _ptc
37
37
  from docx.commands import Command, is_response_bearing, must_flush_immediately
38
+ from docx.errors import DocxError
39
+
40
+
41
+ def _apply_proxy_id_rewrites(
42
+ results: list[Any],
43
+ proxy_id_refs: "dict[str, list[tuple[object, str]]]",
44
+ ) -> None:
45
+ """Walk ``results`` and rewrite registered proxy ids from client UUIDs
46
+ to the real SuperDoc ids the applier echoed back.
47
+
48
+ Shared by the success path in :meth:`CommandBuffer.flush` /
49
+ :meth:`_eager_flush_with` AND by the **partial-failure recovery
50
+ path** (the SDK now reads ``DocxError.partial_applied`` on raise
51
+ and applies the same rewrite for the prefix that committed before
52
+ the server hit its first failure). Without this, every Create that
53
+ succeeded server-side would leave its Python proxy stuck on the
54
+ client UUID and the next batch would ship the dead id straight
55
+ back to the server — that cascade is what turned one cell-
56
+ paragraph mistake in preview-session ``thread_bafba02b`` into
57
+ thirteen downstream "Block not found" errors.
58
+
59
+ Tolerant of legacy / transitional applier results that don't
60
+ include ``client_node_id`` / ``real_node_id`` echoes — we simply
61
+ leave those proxies on their client id; the applier's per-batch
62
+ ``clientIdMap`` rewrite still resolves them server-side on the
63
+ *next* call.
64
+ """
65
+ if not proxy_id_refs:
66
+ return
67
+ for result in results:
68
+ if not isinstance(result, dict):
69
+ continue
70
+ for cli_key, real_key in (
71
+ ("client_node_id", "real_node_id"),
72
+ ("client_entity_id", "real_entity_id"),
73
+ ):
74
+ cli = result.get(cli_key)
75
+ real = result.get(real_key)
76
+ if not (isinstance(cli, str) and isinstance(real, str)):
77
+ continue
78
+ refs = proxy_id_refs.pop(cli, [])
79
+ for proxy, attr in refs:
80
+ try:
81
+ setattr(proxy, attr, real)
82
+ except Exception: # noqa: BLE001
83
+ # A proxy that rejected setattr (slots without the
84
+ # attr, frozen dataclass, etc.) silently keeps its
85
+ # client_id — the applier's rewriter will still
86
+ # resolve it correctly on the next batch.
87
+ pass
88
+
89
+
90
+ def _extract_partial_results(exc: BaseException) -> list[Any]:
91
+ """Pull per-command ``result`` dicts out of a partial-failure error.
92
+
93
+ The HTTP layer attaches the server's ``applied`` array to
94
+ :class:`DocxError` (and subclasses) as ``partial_applied``. Each
95
+ entry is the wire-format ``{index, type, result}`` triple. We
96
+ return just the ``result`` dicts so the rewrite path can share its
97
+ success-path code without caring whether it's running on a happy
98
+ batch or a salvaged prefix.
99
+ """
100
+ applied = getattr(exc, "partial_applied", None)
101
+ if not isinstance(applied, list):
102
+ return []
103
+ out: list[Any] = []
104
+ for entry in applied:
105
+ if isinstance(entry, dict):
106
+ result = entry.get("result")
107
+ if isinstance(result, dict):
108
+ out.append(result)
109
+ return out
38
110
 
39
111
  if TYPE_CHECKING:
40
112
  from docx._http_doc import HttpClient
41
113
 
42
114
 
115
+ # Only commands whose class name is in this set produce PTC sub-tool-cards.
116
+ # Every Command subclass is a candidate, but each emit is now a synchronous
117
+ # HTTP POST to agora (see ``_ptc._send``), so emitting one card per low-level
118
+ # mutation produces hundreds of sub-cards per script and pays full network
119
+ # RTT on every one. The allow-list keeps the per-action signal — one card
120
+ # per logical paragraph creation — without the spam or the cumulative
121
+ # latency. Asset-level creation events (``CreateDocument``) are emitted
122
+ # from ``Document.create`` directly, not through this buffer path.
123
+ _PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph"})
124
+
125
+
43
126
  def _ptc_emit_end_batch(cmds: list[Command], *, is_error: bool) -> None:
44
127
  """Emit a PTC ``end`` event for every cmd that received a ``begin``.
45
128
 
@@ -85,9 +168,7 @@ def _unregister(buffer: "CommandBuffer") -> None:
85
168
  """
86
169
  with _registry_lock:
87
170
  _active_buffers[:] = [
88
- ref
89
- for ref in _active_buffers
90
- if (b := ref()) is not None and b is not buffer
171
+ ref for ref in _active_buffers if (b := ref()) is not None and b is not buffer
91
172
  ]
92
173
 
93
174
 
@@ -170,14 +251,10 @@ class CommandBuffer:
170
251
  # real nodeId after flush. Populated by ``add_paragraph`` /
171
252
  # ``add_heading`` / etc. when they queue a Create with a
172
253
  # client-assigned id. Drained on every flush.
173
- self._proxy_id_refs: dict[
174
- str, list[tuple[object, str]]
175
- ] = {}
254
+ self._proxy_id_refs: dict[str, list[tuple[object, str]]] = {}
176
255
  _register(self)
177
256
 
178
- def register_proxy_id_ref(
179
- self, client_id: str, proxy: object, attr: str = "_node_id"
180
- ) -> None:
257
+ def register_proxy_id_ref(self, client_id: str, proxy: object, attr: str = "_node_id") -> None:
181
258
  """Register that ``proxy.<attr>`` should be rewritten from
182
259
  ``client_id`` to the real node id once the queue flushes.
183
260
 
@@ -215,9 +292,7 @@ class CommandBuffer:
215
292
  ``"direct"``, ``"tracked"``, or ``None`` to clear.
216
293
  """
217
294
  if mode is not None and mode not in ("direct", "tracked"):
218
- raise ValueError(
219
- f"change_mode must be 'direct', 'tracked', or None; got {mode!r}"
220
- )
295
+ raise ValueError(f"change_mode must be 'direct', 'tracked', or None; got {mode!r}")
221
296
  with self._lock:
222
297
  current = self._change_mode
223
298
  if current == mode:
@@ -269,16 +344,19 @@ class CommandBuffer:
269
344
  f"CommandBuffer for {self._asset_id} is closed",
270
345
  )
271
346
 
272
- # PTC begin: one event per user-facing method call, before any
273
- # batching. Failures here can't crash the user's code path.
274
- try:
275
- cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
276
- type(cmd).__name__,
277
- cmd.to_dict(),
278
- asset_id=self._asset_id,
279
- )
280
- except Exception: # noqa: BLE001
281
- pass
347
+ # PTC begin: emit only for commands in the allow-list. Other
348
+ # commands still flow through the buffer (and through the batched
349
+ # HTTP POST), they just don't produce a sub-tool-card. ``emit_end``
350
+ # is automatically skipped because no ``_ptc_call_id`` was set.
351
+ if type(cmd).__name__ in _PTC_EMIT_TOOLS:
352
+ try:
353
+ cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
354
+ type(cmd).__name__,
355
+ cmd.to_dict(),
356
+ asset_id=self._asset_id,
357
+ )
358
+ except Exception: # noqa: BLE001
359
+ pass
282
360
 
283
361
  if must_flush_immediately(cmd) and not self._has_client_id(cmd):
284
362
  return self._eager_flush_with(cmd)
@@ -300,12 +378,9 @@ class CommandBuffer:
300
378
  legacy callers) keep their eager-flush semantics so callers
301
379
  that read the response still see real data.
302
380
  """
303
- return (
304
- is_response_bearing(cmd)
305
- and (
306
- getattr(cmd, "client_node_id", None) is not None
307
- or getattr(cmd, "client_entity_id", None) is not None
308
- )
381
+ return is_response_bearing(cmd) and (
382
+ getattr(cmd, "client_node_id", None) is not None
383
+ or getattr(cmd, "client_entity_id", None) is not None
309
384
  )
310
385
 
311
386
  def flush(self) -> list[Any]:
@@ -339,37 +414,22 @@ class CommandBuffer:
339
414
  change_mode=change_mode,
340
415
  user=user,
341
416
  )
417
+ except DocxError as exc:
418
+ # Partial-failure cascade protection: even though the batch
419
+ # raised, the server's ``applied`` prefix tells us which
420
+ # commands DID commit before the failure. Rewrite the
421
+ # corresponding proxy ids before re-raising so the next
422
+ # batch ships REAL ids for the survivors (vs. dead client
423
+ # UUIDs that would cascade into another "Block not found").
424
+ # See ``_apply_proxy_id_rewrites`` for the full rationale.
425
+ _apply_proxy_id_rewrites(_extract_partial_results(exc), proxy_id_refs)
426
+ _ptc_emit_end_batch(pending, is_error=True)
427
+ raise
342
428
  except Exception:
343
429
  _ptc_emit_end_batch(pending, is_error=True)
344
430
  raise
345
431
  _ptc_emit_end_batch(pending, is_error=False)
346
- # Rewrite registered proxies from client-side UUIDs to the real
347
- # SuperDoc ids the applier echoed back. Defensive: tolerate
348
- # missing fields (legacy or transitional applier without the
349
- # client-id support).
350
- if proxy_id_refs:
351
- for result in results:
352
- if not isinstance(result, dict):
353
- continue
354
- for cli_key, real_key in (
355
- ("client_node_id", "real_node_id"),
356
- ("client_entity_id", "real_entity_id"),
357
- ):
358
- cli = result.get(cli_key)
359
- real = result.get(real_key)
360
- if not (isinstance(cli, str) and isinstance(real, str)):
361
- continue
362
- refs = proxy_id_refs.pop(cli, [])
363
- for proxy, attr in refs:
364
- try:
365
- setattr(proxy, attr, real)
366
- except Exception: # noqa: BLE001
367
- # A proxy that rejected setattr (slots without
368
- # the attr, frozen dataclass, etc.) silently
369
- # keeps its client_id — the rewriter in the
370
- # applier will still resolve it correctly
371
- # for the next batch.
372
- pass
432
+ _apply_proxy_id_rewrites(results, proxy_id_refs)
373
433
  return results
374
434
 
375
435
  def close(self) -> None:
@@ -409,30 +469,22 @@ class CommandBuffer:
409
469
  change_mode=change_mode,
410
470
  user=user,
411
471
  )
472
+ except DocxError as exc:
473
+ # Same partial-failure cascade protection as :meth:`flush` —
474
+ # rewrite proxy ids for the prefix the server reports as
475
+ # ``applied`` before re-raising. Critical here because the
476
+ # eager-flush path is hit by every query in user code (e.g.
477
+ # ``table.cell(0, 0)`` → ``tables.get``), so a failure on
478
+ # the trailing ``cmd`` would otherwise abandon every
479
+ # buffered Create's rewrite at once.
480
+ _apply_proxy_id_rewrites(_extract_partial_results(exc), proxy_id_refs)
481
+ _ptc_emit_end_batch(all_cmds, is_error=True)
482
+ raise
412
483
  except Exception:
413
484
  _ptc_emit_end_batch(all_cmds, is_error=True)
414
485
  raise
415
486
  _ptc_emit_end_batch(all_cmds, is_error=False)
416
- # Apply the same proxy-rewrite pass as ``flush()`` — see comment
417
- # there for the contract.
418
- if proxy_id_refs:
419
- for result in results:
420
- if not isinstance(result, dict):
421
- continue
422
- for cli_key, real_key in (
423
- ("client_node_id", "real_node_id"),
424
- ("client_entity_id", "real_entity_id"),
425
- ):
426
- cli = result.get(cli_key)
427
- real = result.get(real_key)
428
- if not (isinstance(cli, str) and isinstance(real, str)):
429
- continue
430
- refs = proxy_id_refs.pop(cli, [])
431
- for proxy, attr in refs:
432
- try:
433
- setattr(proxy, attr, real)
434
- except Exception: # noqa: BLE001
435
- pass
487
+ _apply_proxy_id_rewrites(results, proxy_id_refs)
436
488
  if not results:
437
489
  return {}
438
490
  return results[-1]
@@ -112,6 +112,7 @@ from docx.commands import (
112
112
  )
113
113
  from docx.errors import (
114
114
  AuthenticationError,
115
+ BlockNotFoundError,
115
116
  DocxError,
116
117
  NotFoundError,
117
118
  SessionError,
@@ -134,6 +135,124 @@ _NOT_FOUND_ERROR_NAMES: frozenset[str] = frozenset(
134
135
  )
135
136
 
136
137
 
138
+ def _looks_like_block_not_found(err_obj: dict) -> bool:
139
+ """Detect SuperDoc's ``Block "<type>:<id>" was not found`` shape.
140
+
141
+ Triggers the typed :class:`BlockNotFoundError` so agent code can
142
+ distinguish a missing-block miss (cell-inner-paragraph addressing
143
+ bug, stale id from a different session) from a generic transport
144
+ or validation error.
145
+ """
146
+ msg = err_obj.get("message")
147
+ if not isinstance(msg, str):
148
+ return False
149
+ lower = msg.lower()
150
+ if "not found" not in lower:
151
+ return False
152
+ # Be conservative: require either ``block "`` (the SuperDoc CLI's
153
+ # quoted-id format) or ``block <id>`` followed by ``not found``.
154
+ return 'block "' in lower or ('block ' in lower and ' was not found' in lower)
155
+
156
+
157
+ # Commands that target a paragraph block (or its inline range) and
158
+ # therefore trip the SuperDoc 1.8.1 cell-paragraph addressing gap when
159
+ # the target paragraph is nested inside a ``tableCell``. Used by the
160
+ # ``BlockNotFoundError`` hint logic to decide whether to surface the
161
+ # materialization workaround. Membership is checked against the
162
+ # ``err_obj["type"]`` field that ``apps/api`` echoes back on partial
163
+ # failure, so the gate covers both error-message shapes the SuperDoc
164
+ # CLI emits:
165
+ #
166
+ # * ``Block "paragraph:<uuid>" was not found.`` (SetParagraph*, etc.)
167
+ # * ``Block "<uuid>" not found.`` (Insert)
168
+ #
169
+ # Without this gate, the hint either fired for stale-id misses on
170
+ # unrelated command types (Greptile #20555) or skipped the Insert form
171
+ # entirely (preview session
172
+ # thread_b952794f, where 3 of 4 failures were bare-UUID Inserts and
173
+ # the agent burned 4 retries without ever seeing the workaround).
174
+ _PARAGRAPH_TARGETING_COMMANDS: frozenset[str] = frozenset(
175
+ {
176
+ "Insert",
177
+ "FormatApply",
178
+ "SetParagraphAlignment",
179
+ "ClearParagraphAlignment",
180
+ "SetParagraphStyle",
181
+ "SetParagraphIndentation",
182
+ "ClearParagraphIndentation",
183
+ "SetParagraphSpacing",
184
+ "ClearParagraphSpacing",
185
+ "SetParagraphKeepOptions",
186
+ "SetParagraphFlowOptions",
187
+ "SetParagraphTabStop",
188
+ "ClearParagraphTabStops",
189
+ "Replace",
190
+ "InsertLineBreak",
191
+ "InsertTab",
192
+ }
193
+ )
194
+
195
+
196
+ _CELL_PARAGRAPH_HINT: str = (
197
+ "\n\nHint: SuperDoc 1.8.1 cannot format paragraphs nested inside "
198
+ "table cells via SetParagraphAlignment / SetParagraphStyle / "
199
+ "SetParagraphIndentation / SetParagraphSpacing / doc.insert with "
200
+ "a paragraph-block target, or any FormatApply on a cell-inner run. "
201
+ "The cell's inner paragraph id is returned by cell.getNodeById but "
202
+ "isn't a top-level addressable block. Materialize the cell's "
203
+ 'paragraph first via ``cell.text = "value"``, then re-read '
204
+ "``cell.paragraphs[0]`` and apply format ops to that post-"
205
+ "materialization Paragraph proxy. Tracked upstream at "
206
+ "docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md § 'cell-inner paragraph "
207
+ "addressing'."
208
+ )
209
+
210
+
211
+ # ``tables.get`` / ``tables.getCells`` / ``tables.getProperties`` carry
212
+ # the table id at the top-level ``nodeId`` field — and the agent code
213
+ # that triggers them (``table.cell(0, 0)``, ``table.rows``, etc.) runs
214
+ # *immediately* after ``doc.add_table(...)``. The first such query
215
+ # triggers an eager flush that drains the buffered CreateTable in the
216
+ # same HTTP batch, so the applier's per-batch ``clientIdMap`` rewrite
217
+ # normally catches it. But when an EARLIER batch (e.g. a doomed cell-
218
+ # paragraph mutation a few lines up in the user script) raised before
219
+ # the SDK could rewrite ``proxy_id_refs``, the Table proxy still holds
220
+ # the client UUID. The next batch ships the dead id straight to SuperDoc
221
+ # and we land here. The hint points the agent at the canonical recovery
222
+ # pattern (force a save() to reseat the proxy refs, or split the
223
+ # offending mutation into its own execution).
224
+ _TABLE_QUERY_COMMANDS: frozenset[str] = frozenset(
225
+ {
226
+ "TablesGet",
227
+ "TablesGetCells",
228
+ "TablesGetProperties",
229
+ "TablesSetStyle",
230
+ "TablesSetLayout",
231
+ "TablesSetTableOptions",
232
+ "TablesSetCellProperties",
233
+ "TablesSetColumnWidth",
234
+ "TablesSetRowHeight",
235
+ "TablesInsertRow",
236
+ "TablesInsertColumn",
237
+ "TablesMergeCells",
238
+ }
239
+ )
240
+
241
+
242
+ _TABLE_CLIENT_ID_HINT: str = (
243
+ "\n\nHint: this looks like a stale client-side table UUID "
244
+ '(``t_xxxxxxxxxxxx``). Either an earlier batch in this execution '
245
+ "raised before the SDK could rewrite the Table proxy's id from the "
246
+ "client UUID to the real SuperDoc id, OR the table belongs to a "
247
+ "prior execution whose state has been discarded. Recover by calling "
248
+ "``doc.save()`` to drain pending mutations + re-anchor live "
249
+ "proxies, then re-query via ``doc.tables`` to get fresh proxies "
250
+ "with real ids. If you saw a cell-paragraph error in the same "
251
+ "execution, address that first — its partial-failure is what "
252
+ "abandoned the rewrite."
253
+ )
254
+
255
+
137
256
  def _looks_like_not_found(parsed: dict) -> bool:
138
257
  """Inspect a docx-studio partial-failure dict and decide whether it
139
258
  describes a "no such entity" miss (vs. a real transport / validation
@@ -280,19 +399,69 @@ def _http_post_json(
280
399
  parsed = json.loads(body) if body else {}
281
400
  except json.JSONDecodeError:
282
401
  parsed = {"raw": body}
402
+ # Extract the ``applied`` prefix so callers can rewrite
403
+ # ``proxy_id_refs`` for commands that DID succeed before the
404
+ # batch hit its first failure. Without this, the SDK throws
405
+ # away every successful Create's client-UUID → real-id mapping
406
+ # the moment ONE command fails, and the next batch keeps
407
+ # shipping dead client UUIDs. That cascade turned the preview-
408
+ # session thread_bafba02b's first cell-paragraph mistake into
409
+ # thirteen downstream "Block not found" errors and finally a
410
+ # ``DOCUMENT_IDENTITY_CONFLICT``. The structure is wire-shape:
411
+ # ``[{index, type, result: {client_node_id, real_node_id, …}}]``.
412
+ applied_raw = parsed.get("applied") if isinstance(parsed, dict) else None
413
+ partial_applied: list[dict] = []
414
+ if isinstance(applied_raw, list):
415
+ partial_applied = [a for a in applied_raw if isinstance(a, dict)]
283
416
  # If the failing command's error looks like "no such entity",
284
417
  # raise a typed :class:`NotFoundError` so speculative-read call
285
418
  # sites (``Comments.get``) can coerce it to ``None`` without
286
419
  # swallowing real transport / validation failures.
287
420
  err_obj = parsed.get("error") if isinstance(parsed, dict) else None
288
- if isinstance(err_obj, dict) and _looks_like_not_found(err_obj):
289
- raise NotFoundError(
290
- f"docx-studio batch reported a partial failure: {parsed!r}",
291
- payload=err_obj,
292
- )
421
+ if isinstance(err_obj, dict):
422
+ base_msg = f"docx-studio batch reported a partial failure: {parsed!r}"
423
+ # ``BlockNotFoundError`` is the narrower miss — caller is most
424
+ # likely targeting a cell-inner paragraph or stale-session
425
+ # block id. Surface the typed exception plus an agent-readable
426
+ # workaround so the next attempt doesn't repeat the same
427
+ # mistake.
428
+ #
429
+ # The cell-paragraph hint applies when the failing command is
430
+ # a paragraph-targeting op (Insert, FormatApply, SetParagraph*,
431
+ # …). We can't gate on the ``paragraph:`` prefix alone — the
432
+ # bare-UUID ``Block "<uuid>" not found.`` shape that SuperDoc's
433
+ # CLI emits for ``Insert`` failures is the dominant form of
434
+ # this bug in practice (preview-session thread_b952794f hit
435
+ # it 3 of 4 times without ever seeing the workaround under the
436
+ # prefix-only gate).
437
+ if _looks_like_block_not_found(err_obj):
438
+ cmd_type = err_obj.get("type")
439
+ paragraph_targeting = (
440
+ isinstance(cmd_type, str)
441
+ and cmd_type in _PARAGRAPH_TARGETING_COMMANDS
442
+ )
443
+ table_query = (
444
+ isinstance(cmd_type, str)
445
+ and cmd_type in _TABLE_QUERY_COMMANDS
446
+ )
447
+ if paragraph_targeting:
448
+ hint = _CELL_PARAGRAPH_HINT
449
+ elif table_query:
450
+ hint = _TABLE_CLIENT_ID_HINT
451
+ else:
452
+ hint = ""
453
+ raise BlockNotFoundError(
454
+ base_msg + hint,
455
+ payload=err_obj,
456
+ ).with_partial_applied(partial_applied)
457
+ if _looks_like_not_found(err_obj):
458
+ raise NotFoundError(
459
+ base_msg,
460
+ payload=err_obj,
461
+ ).with_partial_applied(partial_applied)
293
462
  raise DocxError(
294
463
  f"docx-studio batch reported a partial failure: {parsed!r}",
295
- )
464
+ ).with_partial_applied(partial_applied)
296
465
 
297
466
  if 200 <= resp.status_code < 300:
298
467
  try: