athena-python-docx 0.4.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/CLAUDE.md +48 -5
  2. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/PKG-INFO +7 -3
  3. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/__init__.py +3 -1
  4. athena_python_docx-0.5.1/docx/_buffer.py +248 -0
  5. athena_python_docx-0.5.1/docx/_http_doc.py +518 -0
  6. athena_python_docx-0.5.1/docx/_image_utils.py +63 -0
  7. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/api.py +9 -20
  8. athena_python_docx-0.5.1/docx/client.py +208 -0
  9. athena_python_docx-0.5.1/docx/commands.py +752 -0
  10. athena_python_docx-0.5.1/docx/comments.py +319 -0
  11. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/document.py +195 -59
  12. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/enum/section.py +17 -0
  13. athena_python_docx-0.5.1/docx/enum/style.py +227 -0
  14. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/enum/text.py +13 -3
  15. athena_python_docx-0.5.1/docx/exceptions.py +42 -0
  16. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/opc/coreprops.py +62 -3
  17. athena_python_docx-0.5.1/docx/section.py +689 -0
  18. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/shape.py +97 -10
  19. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/shared.py +12 -12
  20. athena_python_docx-0.5.1/docx/styles/style.py +360 -0
  21. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/styles/styles.py +90 -0
  22. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/table.py +328 -22
  23. athena_python_docx-0.5.1/docx/text/font.py +15 -0
  24. athena_python_docx-0.5.1/docx/text/pagebreak.py +96 -0
  25. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/text/paragraph.py +262 -44
  26. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/text/parfmt.py +4 -57
  27. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/text/run.py +248 -48
  28. athena_python_docx-0.5.1/docx/text/tabstops.py +238 -0
  29. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/pyproject.toml +14 -3
  30. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/scripts/release.sh +8 -8
  31. athena_python_docx-0.5.1/scripts/round_trip_smoke.py +232 -0
  32. athena_python_docx-0.5.1/tests/fidelity/ab_probe_cases.py +520 -0
  33. athena_python_docx-0.5.1/tests/fidelity/ab_probe_runner.py +537 -0
  34. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/binary_round_trip.py +19 -6
  35. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/fake_session.py +276 -50
  36. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/local_runner.py +5 -2
  37. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshot.py +2 -1
  38. athena_python_docx-0.5.1/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +12 -0
  39. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +2 -2
  40. athena_python_docx-0.5.1/tests/fidelity/op_snapshots/29_section_headers_linked.json +6 -0
  41. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/35_full_report.json +1 -1
  42. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +2 -0
  43. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/56_everything_in_one.json +1 -1
  44. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +2 -0
  45. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +5 -0
  46. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex10_complex_bom.json +3 -3
  47. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +10 -0
  48. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +5 -5
  49. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega01_book_chapter.json +4 -0
  50. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega02_research_proposal.json +7 -0
  51. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega03_financial_statement.json +1 -1
  52. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +3 -0
  53. athena_python_docx-0.5.1/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +12 -0
  54. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +4 -0
  55. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +20 -0
  56. athena_python_docx-0.4.0/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json → athena_python_docx-0.5.1/tests/fidelity/op_snapshots/rw11_header_text.json +1 -0
  57. athena_python_docx-0.4.0/tests/fidelity/op_snapshots/29_section_headers_linked.json → athena_python_docx-0.5.1/tests/fidelity/op_snapshots/rw12_first_page_footer.json +2 -1
  58. athena_python_docx-0.5.1/tests/fidelity/op_snapshots/rw13_even_page_header.json +5 -0
  59. athena_python_docx-0.5.1/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +13 -0
  60. athena_python_docx-0.5.1/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +5 -0
  61. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/ours_spec.json +17 -142
  62. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/parity_diff.json +3 -2
  63. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/real_world_cases.py +69 -0
  64. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/runner.py +4 -1
  65. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/stock_spec.json +16 -16
  66. athena_python_docx-0.5.1/tests/parity/README.md +170 -0
  67. athena_python_docx-0.5.1/tests/parity/__init__.py +5 -0
  68. athena_python_docx-0.5.1/tests/parity/baseline_gaps.json +7 -0
  69. athena_python_docx-0.5.1/tests/parity/compare.py +768 -0
  70. athena_python_docx-0.5.1/tests/parity/intentional_deviations.json +189 -0
  71. athena_python_docx-0.5.1/tests/parity/introspect.py +362 -0
  72. athena_python_docx-0.5.1/tests/parity/reports/GAP_ANALYSIS.md +396 -0
  73. athena_python_docx-0.5.1/tests/parity/reports/gap_report.json +5524 -0
  74. athena_python_docx-0.5.1/tests/parity/run_parity.py +212 -0
  75. athena_python_docx-0.5.1/tests/parity/snapshots/athena_latest.json +12717 -0
  76. athena_python_docx-0.5.1/tests/parity/snapshots/upstream_python_docx_1.2.0.json +68840 -0
  77. athena_python_docx-0.5.1/tests/parity/test_parity_gap.py +183 -0
  78. athena_python_docx-0.5.1/tests/test_buffer.py +174 -0
  79. athena_python_docx-0.5.1/tests/test_command_dataclasses.py +144 -0
  80. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/test_commands.py +12 -2
  81. athena_python_docx-0.5.1/tests/test_comments.py +238 -0
  82. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/test_document_create.py +4 -26
  83. athena_python_docx-0.5.1/tests/test_http_transport.py +177 -0
  84. athena_python_docx-0.5.1/tests/test_iter_inner_content.py +75 -0
  85. athena_python_docx-0.5.1/tests/test_merged_cells.py +76 -0
  86. athena_python_docx-0.5.1/tests/test_parity_misc.py +227 -0
  87. athena_python_docx-0.5.1/tests/test_parity_round2.py +566 -0
  88. athena_python_docx-0.5.1/tests/test_phase_a_behavior.py +162 -0
  89. athena_python_docx-0.5.1/tests/test_phase_b_headers_footers.py +193 -0
  90. athena_python_docx-0.5.1/tests/test_phase_c_tables.py +138 -0
  91. athena_python_docx-0.5.1/tests/test_pr19766_review_fixes.py +307 -0
  92. athena_python_docx-0.5.1/tests/test_style_acceptance.py +113 -0
  93. athena_python_docx-0.5.1/tests/test_style_font.py +168 -0
  94. athena_python_docx-0.5.1/tests/test_style_setters_contract.py +125 -0
  95. athena_python_docx-0.5.1/tests/test_wire_contract.py +426 -0
  96. athena_python_docx-0.5.1/tests/test_zod_wire_contract.py +109 -0
  97. athena_python_docx-0.5.1/uv.lock +715 -0
  98. athena_python_docx-0.4.0/docx/_http_doc.py +0 -181
  99. athena_python_docx-0.4.0/docx/client.py +0 -316
  100. athena_python_docx-0.4.0/docx/enum/style.py +0 -64
  101. athena_python_docx-0.4.0/docx/section.py +0 -358
  102. athena_python_docx-0.4.0/docx/styles/style.py +0 -77
  103. athena_python_docx-0.4.0/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -7
  104. athena_python_docx-0.4.0/tests/test_http_transport.py +0 -172
  105. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/.gitignore +0 -0
  106. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/README.md +0 -0
  107. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/_batching.py +0 -0
  108. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/_http.py +0 -0
  109. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/enum/__init__.py +0 -0
  110. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/enum/table.py +0 -0
  111. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/errors.py +0 -0
  112. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/opc/__init__.py +0 -0
  113. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/settings.py +0 -0
  114. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/styles/__init__.py +0 -0
  115. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/text/__init__.py +0 -0
  116. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/text/hyperlink.py +0 -0
  117. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/docx/typing.py +0 -0
  118. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/scripts/publish.sh +0 -0
  119. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/__init__.py +0 -0
  120. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/conftest.py +0 -0
  121. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/METHODOLOGY.md +0 -0
  122. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/README.md +0 -0
  123. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/__init__.py +0 -0
  124. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/auto_gen_cases.py +0 -0
  125. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/cases.py +0 -0
  126. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/complex_cases.py +0 -0
  127. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/coverage_report.py +0 -0
  128. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/extract.py +0 -0
  129. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/extreme_cases.py +0 -0
  130. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/mega_cases.py +0 -0
  131. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  132. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  133. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  134. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  135. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  136. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  137. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  138. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  139. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  140. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  141. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  142. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
  143. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  144. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  145. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  146. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  147. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  148. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  149. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  150. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  151. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  152. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  153. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  154. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  155. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  156. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  157. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  158. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  159. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
  160. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  161. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  162. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  163. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  164. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  165. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  166. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  167. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  168. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  169. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  170. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  171. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  172. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  173. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  174. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  175. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  176. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  177. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  178. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  179. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  180. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  181. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  182. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  183. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  184. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
  185. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  186. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  187. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  188. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  189. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  190. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  191. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  192. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  193. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  194. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  195. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  196. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  197. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  198. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  199. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  200. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  201. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  202. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  203. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  204. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  205. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  206. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  207. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  208. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  209. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  210. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  211. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  212. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  213. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  214. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  215. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  216. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  217. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  218. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  219. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  220. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  221. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  222. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  223. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  224. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  225. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  226. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  227. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  228. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  229. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  230. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
  231. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  232. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  233. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  234. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  235. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  236. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  237. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  238. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  239. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  240. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  241. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  242. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  243. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  244. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  245. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  246. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  247. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  248. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  249. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  250. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  251. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  252. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  253. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  254. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  255. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  256. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  257. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  258. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  259. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  260. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/parity_crawl.py +0 -0
  261. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/fidelity/round_trip_tests.py +0 -0
  262. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/test_python_docx_api_parity.py +0 -0
  263. {athena_python_docx-0.4.0 → athena_python_docx-0.5.1}/tests/test_smoke_integration.py +0 -0
@@ -34,6 +34,35 @@ These standard python-docx members don't apply to a Superdoc-backed SDK:
34
34
  - `Document.core_properties.last_modified_by` — we use Keryx attribution instead
35
35
  - `Document.settings` — Word app settings (not surfaced by Superdoc)
36
36
  - `InlineShape.chart` — charts (Phase 2+)
37
+ - `Comment.add_paragraph` / `Comment.add_table` /
38
+ `Comment.iter_inner_content` / `Comment.paragraphs` /
39
+ `Comment.tables` — SuperDoc models a comment as a single text body,
40
+ not a `BlockItemContainer`. Use `Comment.text` instead. The
41
+ paragraph/table-bearing surface raises `CommentsNotImplementedError`.
42
+
43
+ ### Phase-3b upstream-blocked surface
44
+
45
+ These are *shipped at the surface* but partially or fully no-op at
46
+ runtime, pending SuperDoc SDK changes outside this repo. The
47
+ upstream constraints have been verified against
48
+ [SuperDoc's published docs](https://docs.superdoc.dev/document-api/reference/styles/apply)
49
+ and the bundled TypeScript types in
50
+ `node_modules/@superdoc-dev/sdk/dist/generated/client.d.ts`. See
51
+ `docx-studio/PYTHON_DOCX_PARITY_GAPS.md` § *What unblocks the
52
+ remaining work* for the exact wire-op shape needed to unblock each.
53
+
54
+ - **24 setters on `BaseStyle` / `CharacterStyle` / `ParagraphStyle`**
55
+ (`name`, `style_id`, `hidden`, `locked`, `priority`, `quick_style`,
56
+ `unhide_when_used`, `base_style`, `next_paragraph_style`) emit
57
+ `PendingStyleMutationWarning` and stash on `_overrides`.
58
+ SuperDoc 1.7's `doc.styles.apply` is constrained to
59
+ `target.scope: "docDefaults"` — no per-style metadata variant.
60
+ Behavior pinned by `tests/test_style_setters_contract.py` (11
61
+ tests); when SuperDoc lands a metadata op, those tests will need
62
+ to flip to assert real bus calls.
63
+ - **`Styles.latent_styles`** returns an empty `_LatentStyles` —
64
+ `DocInfoResult.styles` only surfaces `paragraphStyles[]` (a flat
65
+ usage-count list), not the OOXML `<w:latentStyles>` block.
37
66
 
38
67
  ### Intentional deviations (additions / different semantics)
39
68
 
@@ -56,14 +85,28 @@ If there is a genuine technical reason why a deviation from python-docx is neces
56
85
  3. Get explicit confirmation that the deviation is acceptable
57
86
  4. Document the deviation in the "Intentionally omitted" list above
58
87
 
59
- ## Architecture
88
+ ## Architecture (0.5.0+)
60
89
 
61
- This is an **async-Superdoc-SDK-backed client** that mimics the sync python-docx API.
90
+ This is a **thin HTTP client** that mimics the sync python-docx API.
62
91
 
63
92
  - Sync façade (matches python-docx) — `doc.save()`, `paragraph.add_run()`
64
- - Under the hood, a persistent event-loop thread in `_batching.py` runs `AsyncSuperDocClient` coroutines
65
- - No XML manipulation — calls translate to Superdoc SDK ops (insert, find, replace, tables.*, format.apply, create.image, hyperlinks.wrap)
66
- - Mutations write directly to Keryx Y.Doc; users and other agents see them live
93
+ - Every internal call constructs a typed `Command` dataclass
94
+ (`docx.commands`) and ships it through a `CommandBuffer`
95
+ (`docx._buffer`) which POSTs to docx-studio's `/docs/:id/commands`.
96
+ - HTTP transport: `requests.Session` with a Retry (3 attempts, 0.5s
97
+ backoff, 429/502/503/504). The legacy `transport="direct"` (embedded
98
+ Superdoc CLI + y-websocket from Python) was removed in 0.5.0; agent
99
+ pods no longer embed Superdoc.
100
+ - Batching: queries and response-bearing creates flush eagerly (and
101
+ drain pending pure mutations in the same batch); pure mutations
102
+ buffer with a 100 ms idle timer. `Document.save()` and the context
103
+ manager exit drain explicitly. `docx.flush_all()` is the Daytona
104
+ prelude hook — flushes every live buffer in the process.
105
+ - The path-proxy in `_http_doc.py` is an internal translation layer:
106
+ call sites that look like `await self._session.doc.create.paragraph(p)`
107
+ resolve to `CommandBuffer.call(CreateParagraph(**p))`. Rewriting call
108
+ sites to construct `Command` dataclasses directly is a possible
109
+ follow-up — the wire format is typed end-to-end either way.
67
110
 
68
111
  ## Development
69
112
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -11,14 +11,18 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Python: >=3.11
14
- Requires-Dist: httpx>=0.27
15
- Requires-Dist: superdoc-sdk>=1.6.0.dev6
14
+ Requires-Dist: requests>=2.28
15
+ Requires-Dist: urllib3>=1.26.6
16
16
  Provides-Extra: dev
17
17
  Requires-Dist: mypy>=1.8; extra == 'dev'
18
18
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
19
19
  Requires-Dist: pytest>=8.0; extra == 'dev'
20
20
  Requires-Dist: python-docx>=1.1; extra == 'dev'
21
+ Requires-Dist: responses>=0.24; extra == 'dev'
21
22
  Requires-Dist: ruff>=0.3; extra == 'dev'
23
+ Provides-Extra: fidelity
24
+ Requires-Dist: httpx>=0.27; extra == 'fidelity'
25
+ Requires-Dist: superdoc-sdk>=1.8.0; extra == 'fidelity'
22
26
  Description-Content-Type: text/markdown
23
27
 
24
28
  # athena-python-docx
@@ -6,9 +6,10 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.4.0"
9
+ __version__ = "0.5.1"
10
10
 
11
11
  from docx.api import Document
12
+ from docx._buffer import flush_all
12
13
  # Re-exports python-docx ships at docx top-level for convenience.
13
14
  from docx.shared import Emu, Inches, Pt, Cm, Mm, Twips, Length, RGBColor
14
15
 
@@ -22,5 +23,6 @@ __all__ = [
22
23
  "Twips",
23
24
  "Length",
24
25
  "RGBColor",
26
+ "flush_all",
25
27
  "__version__",
26
28
  ]
@@ -0,0 +1,248 @@
1
+ """HTTP command buffer for the docx-studio SDK.
2
+
3
+ The buffer sits between SDK call sites and the HTTP transport. Every SDK
4
+ mutation routes through it; we flush eagerly for queries and response-bearing
5
+ ops (creates), and idle-batch pure mutations so a burst of setters becomes
6
+ one HTTP request instead of N.
7
+
8
+ Concurrency model: the SDK serializes calls through ``_batching.run_sync``
9
+ (one persistent event-loop thread), so the buffer's primary thread is the
10
+ loop thread. The auto-flush timer fires on a *different* thread, so we
11
+ guard the pending list with an RLock and treat the timer flush as a
12
+ best-effort coalesce — if the loop thread races us we just flush twice
13
+ (or zero times; the next loop-thread call drains it).
14
+
15
+ `flush_all` is a process-wide hook used by the Daytona sandbox prelude to
16
+ make sure pending writes hit Keryx before the sandbox is suspended. It walks
17
+ a weak-ref registry so dead Workbook instances don't keep their buffers
18
+ alive.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import sys
24
+ import threading
25
+ import weakref
26
+ from contextlib import contextmanager
27
+ from typing import TYPE_CHECKING, Any, Generator
28
+
29
+ from docx.commands import Command, must_flush_immediately
30
+
31
+ if TYPE_CHECKING:
32
+ from docx._http_doc import HttpClient
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Process-wide registry for flush_all()
37
+ # ---------------------------------------------------------------------------
38
+
39
+ _active_buffers: list[weakref.ref["CommandBuffer"]] = []
40
+ _registry_lock = threading.Lock()
41
+
42
+
43
+ def _register(buffer: "CommandBuffer") -> None:
44
+ with _registry_lock:
45
+ _active_buffers.append(weakref.ref(buffer))
46
+
47
+
48
+ def _unregister(buffer: "CommandBuffer") -> None:
49
+ """Drop ``buffer`` from the active-registry list.
50
+
51
+ Called from :meth:`CommandBuffer.close` so a long-lived process that
52
+ opens and closes many Documents doesn't accumulate dead weak-refs in
53
+ the registry between ``flush_all`` calls.
54
+ """
55
+ with _registry_lock:
56
+ _active_buffers[:] = [
57
+ ref
58
+ for ref in _active_buffers
59
+ if (b := ref()) is not None and b is not buffer
60
+ ]
61
+
62
+
63
+ def flush_all() -> None:
64
+ """Flush every live CommandBuffer in this process.
65
+
66
+ Used by the Daytona sandbox prelude after user code returns, so
67
+ buffered mutations make it to Keryx before the sandbox is suspended.
68
+ Safe to call when no Buffers exist (no-op). Failures are logged and
69
+ swallowed — we don't want one stuck buffer to mask the rest.
70
+ """
71
+ with _registry_lock:
72
+ snapshot = list(_active_buffers)
73
+ # Compact dead refs while we hold the lock.
74
+ live = [ref for ref in snapshot if ref() is not None]
75
+ _active_buffers[:] = live
76
+
77
+ for ref in live:
78
+ buf = ref()
79
+ if buf is None:
80
+ continue
81
+ try:
82
+ buf.flush()
83
+ except Exception as e: # noqa: BLE001
84
+ sys.stderr.write(
85
+ f"[docx-sdk] flush_all: buffer {buf.asset_id} flush failed: {e}\n",
86
+ )
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # CommandBuffer
91
+ # ---------------------------------------------------------------------------
92
+
93
+ # Idle window before a pure-mutation buffer auto-flushes. Short by design —
94
+ # the SDK's main consumer is agent code that fires sequential property
95
+ # setters within milliseconds of each other; we just need to coalesce those
96
+ # without holding writes back from Keryx for human-perceptible time.
97
+ DEFAULT_AUTO_FLUSH_SECONDS: float = 0.1
98
+
99
+
100
+ class CommandBuffer:
101
+ """Buffers commands for one Document/asset.
102
+
103
+ Behaviour:
104
+ - Queries and response-bearing mutations (creates, inserts that return
105
+ ids) flush immediately. Pending pure mutations are flushed in the
106
+ same batch so ordering is preserved.
107
+ - Pure mutations (formatters, setters) are queued. An idle timer flushes
108
+ them after :data:`DEFAULT_AUTO_FLUSH_SECONDS` of inactivity, OR on
109
+ the next eager call, OR on explicit ``flush()``.
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ client: "HttpClient",
115
+ asset_id: str,
116
+ *,
117
+ auto_flush_seconds: float = DEFAULT_AUTO_FLUSH_SECONDS,
118
+ ) -> None:
119
+ self._client: "HttpClient" = client
120
+ self._asset_id: str = asset_id
121
+ self._pending: list[Command] = []
122
+ self._lock: threading.RLock = threading.RLock()
123
+ self._timer: threading.Timer | None = None
124
+ self._auto_flush_seconds: float = auto_flush_seconds
125
+ self._closed: bool = False
126
+ _register(self)
127
+
128
+ @property
129
+ def asset_id(self) -> str:
130
+ return self._asset_id
131
+
132
+ @property
133
+ def pending_count(self) -> int:
134
+ with self._lock:
135
+ return len(self._pending)
136
+
137
+ def call(self, cmd: Command) -> Any:
138
+ """Execute or buffer ``cmd``.
139
+
140
+ For eager commands (queries, creates), drains the pending queue and
141
+ runs ``cmd`` in the same batch; returns the per-cmd result dict.
142
+
143
+ For pure mutations, appends to the queue, resets the idle timer,
144
+ and returns ``None``. The caller MUST NOT rely on the return value
145
+ of a buffered mutation — it's not available until flush.
146
+ """
147
+ if self._closed:
148
+ raise RuntimeError(
149
+ f"CommandBuffer for {self._asset_id} is closed",
150
+ )
151
+
152
+ if must_flush_immediately(cmd):
153
+ return self._eager_flush_with(cmd)
154
+
155
+ with self._lock:
156
+ self._pending.append(cmd)
157
+ self._reset_timer_locked()
158
+ return None
159
+
160
+ def flush(self) -> list[Any]:
161
+ """Flush pending commands as one HTTP batch.
162
+
163
+ Returns the list of per-command result dicts. Empty list if nothing
164
+ was pending. Cancels any active idle timer.
165
+ """
166
+ with self._lock:
167
+ self._cancel_timer_locked()
168
+ pending = self._pending
169
+ self._pending = []
170
+ if not pending:
171
+ return []
172
+ return self._client.execute_batch(self._asset_id, pending)
173
+
174
+ @contextmanager
175
+ def batch(self) -> Generator[None, None, None]:
176
+ """Group calls into one HTTP batch.
177
+
178
+ Inside the ``with`` block, eager commands still flush immediately
179
+ (we can't buffer reads — the caller is awaiting their result), but
180
+ pure mutations accumulate without their idle timer firing.
181
+ On exit, drains anything left.
182
+ """
183
+ # Cancel the idle timer for the duration of the block; we'll flush
184
+ # explicitly on exit. New adds inside the block go straight onto
185
+ # _pending without rescheduling the timer.
186
+ with self._lock:
187
+ self._cancel_timer_locked()
188
+ old_window = self._auto_flush_seconds
189
+ self._auto_flush_seconds = 0.0 # disable scheduling
190
+ try:
191
+ yield
192
+ finally:
193
+ with self._lock:
194
+ self._auto_flush_seconds = old_window
195
+ self.flush()
196
+
197
+ def close(self) -> None:
198
+ """Flush and disable. Idempotent."""
199
+ if self._closed:
200
+ return
201
+ try:
202
+ self.flush()
203
+ finally:
204
+ self._closed = True
205
+ _unregister(self)
206
+
207
+ # ----- internals -----
208
+
209
+ def _eager_flush_with(self, cmd: Command) -> Any:
210
+ """Drain pending + run ``cmd`` in one batch; return cmd's result."""
211
+ with self._lock:
212
+ self._cancel_timer_locked()
213
+ pending = self._pending
214
+ self._pending = []
215
+ all_cmds: list[Command] = [*pending, cmd]
216
+ results: list[Any] = self._client.execute_batch(self._asset_id, all_cmds)
217
+ if not results:
218
+ return {}
219
+ return results[-1]
220
+
221
+ def _reset_timer_locked(self) -> None:
222
+ # Caller must hold self._lock.
223
+ self._cancel_timer_locked()
224
+ if self._auto_flush_seconds <= 0:
225
+ return # disabled (e.g. inside batch())
226
+ self._timer = threading.Timer(
227
+ self._auto_flush_seconds,
228
+ self._auto_flush,
229
+ )
230
+ self._timer.daemon = True
231
+ self._timer.start()
232
+
233
+ def _cancel_timer_locked(self) -> None:
234
+ # Caller must hold self._lock.
235
+ if self._timer is not None:
236
+ self._timer.cancel()
237
+ self._timer = None
238
+
239
+ def _auto_flush(self) -> None:
240
+ try:
241
+ self.flush()
242
+ except Exception as e: # noqa: BLE001
243
+ sys.stderr.write(
244
+ f"[docx-sdk] auto-flush failed for {self._asset_id}: {e}\n",
245
+ )
246
+
247
+
248
+ __all__ = ["CommandBuffer", "flush_all", "DEFAULT_AUTO_FLUSH_SECONDS"]