athena-python-docx 0.2.1__tar.gz → 0.2.3__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 (207) hide show
  1. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/PKG-INFO +1 -1
  2. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/__init__.py +11 -1
  3. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/document.py +31 -9
  4. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/text.py +18 -1
  5. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/section.py +1 -1
  6. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/shape.py +1 -1
  7. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/table.py +76 -98
  8. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/paragraph.py +23 -14
  9. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/parfmt.py +1 -1
  10. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/pyproject.toml +1 -1
  11. athena_python_docx-0.2.3/scripts/release.sh +80 -0
  12. athena_python_docx-0.2.3/tests/fidelity/METHODOLOGY.md +107 -0
  13. athena_python_docx-0.2.3/tests/fidelity/auto_gen_cases.py +260 -0
  14. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/cases.py +2 -3
  15. athena_python_docx-0.2.3/tests/fidelity/coverage_report.py +117 -0
  16. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/fake_session.py +65 -1
  17. athena_python_docx-0.2.3/tests/fidelity/op_snapshot.py +116 -0
  18. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/01_basic_paragraph.json +3 -0
  19. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/02_multiple_headings.json +9 -0
  20. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/03_runs_with_formatting.json +16 -0
  21. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/04_font_name_and_size.json +7 -0
  22. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/05_font_color_rgb.json +6 -0
  23. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/06_font_character_properties.json +11 -0
  24. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +9 -0
  25. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/08_font_highlight.json +6 -0
  26. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/09_paragraph_alignment.json +4 -0
  27. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/100_table_negative_indexing.json +7 -0
  28. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +33 -0
  29. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +3 -0
  30. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +14 -0
  31. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/104_core_properties_datetime.json +1 -0
  32. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/105_default_one_section.json +6 -0
  33. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +5 -0
  34. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/107_varying_row_heights.json +53 -0
  35. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/10_paragraph_indents.json +6 -0
  36. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/11_paragraph_spacing.json +7 -0
  37. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +7 -0
  38. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +5 -0
  39. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +9 -0
  40. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/15_run_add_break_page.json +6 -0
  41. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +7 -0
  42. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/17_table_basic.json +5 -0
  43. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/18_table_cell_text.json +27 -0
  44. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +25 -0
  45. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +12 -0
  46. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +9 -0
  47. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +15 -0
  48. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/23_nested_table.json +11 -0
  49. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/24_table_add_row_column.json +21 -0
  50. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/25_table_merge_cells.json +5 -0
  51. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/26_section_page_setup.json +6 -0
  52. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/27_section_margins.json +7 -0
  53. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/28_section_add_new.json +5 -0
  54. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/29_section_headers_linked.json +5 -0
  55. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/30_styles_iteration.json +3 -0
  56. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +4 -0
  57. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +1 -0
  58. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +1 -0
  59. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +3 -0
  60. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/35_full_report.json +103 -0
  61. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +5 -0
  62. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +9 -0
  63. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/38_font_all_properties.json +23 -0
  64. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +102 -0
  65. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/40_large_table_10x10.json +403 -0
  66. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +6 -0
  67. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/42_very_long_paragraph.json +3 -0
  68. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +4 -0
  69. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +5 -0
  70. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +11 -0
  71. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +7 -0
  72. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/47_font_size_round_trip.json +7 -0
  73. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/48_font_color_round_trip.json +7 -0
  74. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/49_resume_layout.json +48 -0
  75. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/50_multi_section_doc.json +20 -0
  76. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/51_nested_tables_deep.json +15 -0
  77. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/52_iterate_everything.json +13 -0
  78. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +9 -0
  79. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/54_empty_everything.json +6 -0
  80. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/55_single_character_runs.json +18 -0
  81. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/56_everything_in_one.json +111 -0
  82. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +11 -0
  83. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +13 -0
  84. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/59_indent_round_trip.json +5 -0
  85. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/60_space_round_trip.json +7 -0
  86. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +14 -0
  87. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +26 -0
  88. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/63_table_style_round_trip.json +9 -0
  89. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/64_many_sections.json +28 -0
  90. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +3405 -0
  91. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/66_toc_like_structure.json +82 -0
  92. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +8 -0
  93. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/68_invoice.json +125 -0
  94. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/69_newsletter.json +67 -0
  95. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +11 -0
  96. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/71_academic_paper.json +82 -0
  97. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/72_legal_contract.json +83 -0
  98. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/73_form_with_many_tables.json +194 -0
  99. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +34 -0
  100. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +5 -0
  101. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +6 -0
  102. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/77_length_unit_conversions.json +1 -0
  103. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +8 -0
  104. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +103 -0
  105. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +7 -0
  106. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +12 -0
  107. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +7 -0
  108. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +8 -0
  109. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/84_table_reread_row_count.json +21 -0
  110. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/85_header_footer_access.json +5 -0
  111. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +10 -0
  112. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +532 -0
  113. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +54 -0
  114. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +6 -0
  115. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +9 -0
  116. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/91_many_small_tables.json +453 -0
  117. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/92_margins_every_section.json +21 -0
  118. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +9 -0
  119. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +4 -0
  120. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +4 -0
  121. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +7 -0
  122. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/97_document_styles_by_key.json +8 -0
  123. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/98_style_contains_check.json +5 -0
  124. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +5 -0
  125. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +23 -0
  126. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +29 -0
  127. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +1002 -0
  128. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex04_50x50_table.json +203 -0
  129. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +9 -0
  130. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +272 -0
  131. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +21 -0
  132. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +32 -0
  133. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +13 -0
  134. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex10_complex_bom.json +506 -0
  135. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +805 -0
  136. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +17 -0
  137. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +55 -0
  138. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex14_styled_report_table.json +167 -0
  139. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +15 -0
  140. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +13 -0
  141. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +11 -0
  142. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +203 -0
  143. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +48 -0
  144. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +812 -0
  145. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega01_book_chapter.json +199 -0
  146. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega02_research_proposal.json +178 -0
  147. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega03_financial_statement.json +167 -0
  148. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega04_recipe_card.json +55 -0
  149. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega05_user_manual.json +177 -0
  150. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +98 -0
  151. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +231 -0
  152. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega08_product_catalog.json +89 -0
  153. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega09_signed_contract.json +128 -0
  154. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega10_api_documentation.json +322 -0
  155. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw01_official_quickstart.json +90 -0
  156. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +7 -0
  157. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw03_character_formatting.json +10 -0
  158. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw04_section_page_setup.json +11 -0
  159. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw05_toc_pattern.json +25 -0
  160. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +98 -0
  161. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +31 -0
  162. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw08_table_merged_header.json +35 -0
  163. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +93 -0
  164. athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +165 -0
  165. athena_python_docx-0.2.3/tests/fidelity/ours_spec.json +4495 -0
  166. athena_python_docx-0.2.3/tests/fidelity/parity_crawl.py +266 -0
  167. athena_python_docx-0.2.3/tests/fidelity/parity_diff.json +517 -0
  168. athena_python_docx-0.2.3/tests/fidelity/round_trip_tests.py +214 -0
  169. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/runner.py +0 -1
  170. athena_python_docx-0.2.3/tests/fidelity/stock_spec.json +10189 -0
  171. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/.gitignore +0 -0
  172. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/CLAUDE.md +0 -0
  173. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/README.md +0 -0
  174. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/_batching.py +0 -0
  175. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/api.py +0 -0
  176. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/client.py +0 -0
  177. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/__init__.py +0 -0
  178. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/section.py +0 -0
  179. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/style.py +0 -0
  180. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/table.py +0 -0
  181. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/errors.py +0 -0
  182. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/opc/__init__.py +0 -0
  183. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/opc/coreprops.py +0 -0
  184. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/settings.py +0 -0
  185. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/shared.py +0 -0
  186. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/styles/__init__.py +0 -0
  187. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/styles/style.py +0 -0
  188. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/styles/styles.py +0 -0
  189. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/__init__.py +0 -0
  190. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/hyperlink.py +0 -0
  191. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/run.py +0 -0
  192. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/typing.py +0 -0
  193. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/scripts/publish.sh +0 -0
  194. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/__init__.py +0 -0
  195. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/conftest.py +0 -0
  196. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/README.md +0 -0
  197. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/__init__.py +0 -0
  198. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/binary_round_trip.py +0 -0
  199. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/complex_cases.py +0 -0
  200. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/extract.py +0 -0
  201. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/extreme_cases.py +0 -0
  202. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/local_runner.py +0 -0
  203. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/mega_cases.py +0 -0
  204. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/real_world_cases.py +0 -0
  205. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/test_commands.py +0 -0
  206. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/test_python_docx_api_parity.py +0 -0
  207. {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/test_smoke_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.2.1
3
+ Version: 0.2.3
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,11 +6,21 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.2.1"
9
+ __version__ = "0.2.3"
10
10
 
11
11
  from docx.api import Document
12
+ # Re-exports python-docx ships at docx top-level for convenience.
13
+ from docx.shared import Emu, Inches, Pt, Cm, Mm, Twips, Length, RGBColor
12
14
 
13
15
  __all__ = [
14
16
  "Document",
17
+ "Emu",
18
+ "Inches",
19
+ "Pt",
20
+ "Cm",
21
+ "Mm",
22
+ "Twips",
23
+ "Length",
24
+ "RGBColor",
15
25
  "__version__",
16
26
  ]
@@ -22,9 +22,15 @@ from typing import TYPE_CHECKING, BinaryIO
22
22
  from docx._batching import run_sync
23
23
  from docx.client import Session
24
24
  from docx.errors import DocumentClosedError, ValidationError
25
+ # python-docx re-exports a subset of symbols at docx.document; mirror those
26
+ # so `from docx.document import Emu` etc. works.
27
+ from docx.enum.section import WD_SECTION, WD_SECTION_START # noqa: F401
28
+ from docx.enum.text import WD_BREAK # noqa: F401
29
+ from docx.section import Section, Sections # noqa: F401
30
+ from docx.shared import Cm, Emu, Inches, Length, Mm, Pt, RGBColor, Twips # noqa: F401
31
+ from docx.text.run import Run # noqa: F401
25
32
 
26
33
  if TYPE_CHECKING:
27
- from docx.shared import Emu
28
34
  from docx.table import Table
29
35
  from docx.text.paragraph import Paragraph
30
36
 
@@ -80,7 +86,15 @@ class Document:
80
86
  continue
81
87
  node_id: str = str(b.get("nodeId", ""))
82
88
  if node_id:
83
- out.append(Paragraph(session=self._session, node_id=node_id))
89
+ nt_raw = b.get("nodeType")
90
+ nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
91
+ out.append(
92
+ Paragraph(
93
+ session=self._session,
94
+ node_id=node_id,
95
+ node_type=nt,
96
+ ),
97
+ )
84
98
  return out
85
99
 
86
100
  @property
@@ -101,7 +115,6 @@ class Document:
101
115
  @property
102
116
  def sections(self):
103
117
  """Return the document's sections collection."""
104
- from docx.section import Sections
105
118
 
106
119
  self._ensure_open()
107
120
  return Sections(session=self._session)
@@ -204,7 +217,9 @@ class Document:
204
217
  raise RuntimeError(
205
218
  f"Superdoc did not return a nodeId for add_paragraph: {result!r}",
206
219
  )
207
- return Paragraph(session=self._session, node_id=node_id)
220
+ return Paragraph(
221
+ session=self._session, node_id=node_id, node_type="paragraph",
222
+ )
208
223
 
209
224
  def add_heading(
210
225
  self,
@@ -239,7 +254,11 @@ class Document:
239
254
  raise RuntimeError(
240
255
  f"Superdoc did not return a nodeId for add_heading(level=0): {result!r}",
241
256
  )
242
- paragraph = Paragraph(session=self._session, node_id=node_id)
257
+ paragraph = Paragraph(
258
+ session=self._session,
259
+ node_id=node_id,
260
+ node_type="paragraph",
261
+ )
243
262
  paragraph.style = "Title"
244
263
  return paragraph
245
264
 
@@ -251,12 +270,17 @@ class Document:
251
270
  result = run_sync(
252
271
  self._session.doc.create.heading(params),
253
272
  )
254
- node_id = _extract_inserted_node_id(result, expected_type="paragraph")
273
+ # Bug fix: was passing expected_type="paragraph" here (wrong); the
274
+ # fallback loop recovered but the code of intent was wrong. Fixed to
275
+ # expected_type="heading" so we extract from the correct response key.
276
+ node_id = _extract_inserted_node_id(result, expected_type="heading")
255
277
  if not node_id:
256
278
  raise RuntimeError(
257
279
  f"Superdoc did not return a nodeId for add_heading: {result!r}",
258
280
  )
259
- return Paragraph(session=self._session, node_id=node_id)
281
+ return Paragraph(
282
+ session=self._session, node_id=node_id, node_type="heading",
283
+ )
260
284
 
261
285
  def add_table(
262
286
  self,
@@ -372,8 +396,6 @@ class Document:
372
396
  Mirrors python-docx: creating a section adds a trailing empty
373
397
  paragraph that marks the section boundary.
374
398
  """
375
- from docx.enum.section import WD_SECTION_START
376
- from docx.section import Section
377
399
 
378
400
  self._ensure_open()
379
401
  # python-docx always inserts an anchor paragraph at the section break
@@ -113,6 +113,15 @@ class WD_BREAK(Enum):
113
113
  LINE_CLEAR_RIGHT = "lineClearRight"
114
114
  LINE_CLEAR_ALL = "lineClearAll"
115
115
  TEXT_WRAPPING = "textWrapping"
116
+ # python-docx 1.x also exposes section breaks via WD_BREAK
117
+ SECTION_CONTINUOUS = "sectionContinuous"
118
+ SECTION_EVEN_PAGE = "sectionEvenPage"
119
+ SECTION_NEXT_PAGE = "sectionNextPage"
120
+ SECTION_ODD_PAGE = "sectionOddPage"
121
+
122
+
123
+ # python-docx internal alias
124
+ WD_BREAK_TYPE = WD_BREAK
116
125
 
117
126
 
118
127
  class WD_UNDERLINE(Enum):
@@ -137,6 +146,7 @@ class WD_UNDERLINE(Enum):
137
146
 
138
147
 
139
148
  class WD_COLOR_INDEX(Enum):
149
+ INHERITED = "inherit"
140
150
  AUTO = "default"
141
151
  BLACK = "black"
142
152
  BLUE = "blue"
@@ -156,5 +166,12 @@ class WD_COLOR_INDEX(Enum):
156
166
  YELLOW = "yellow"
157
167
 
158
168
 
159
- # Alias used by python-docx as well
169
+ # Aliases used by python-docx as well
160
170
  WD_COLOR = WD_COLOR_INDEX
171
+ WD_PARAGRAPH_ALIGNMENT = WD_ALIGN_PARAGRAPH
172
+
173
+
174
+ # python-docx 1.x base class that WD_* enums inherit from — we don't need
175
+ # the real base, just a name users can subclass-check against.
176
+ class BaseXmlEnum(Enum):
177
+ pass
@@ -14,7 +14,7 @@ from __future__ import annotations
14
14
  from typing import TYPE_CHECKING
15
15
 
16
16
  from docx._batching import run_sync
17
- from docx.shared import Emu, Length, Pt
17
+ from docx.shared import Length, Pt
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from docx.client import Session
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
  from typing import TYPE_CHECKING
11
11
 
12
12
  from docx._batching import run_sync
13
- from docx.shared import Emu, Length, Pt
13
+ from docx.shared import Length, Pt
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from docx.client import Session
@@ -60,7 +60,11 @@ def _find_first_paragraph_id(obj: object) -> str:
60
60
  def _collect_paragraph_ids(obj: object, out: list[str]) -> None:
61
61
  """Walk a node tree and collect all paragraph/heading nodeIds in order.
62
62
 
63
- Tolerates several shapes that Superdoc has emitted over versions:
63
+ Tolerates multiple shapes Superdoc emits:
64
+ - cell getNodeById: {"node": {"kind": "paragraph", "id": "UUID",
65
+ "paragraph": {"inlines": [...]}}}
66
+ (the cell's inner paragraph — server reports `id` as a bare UUID
67
+ that the addressing layer expects as `paragraph:UUID`)
64
68
  - prosemirror-style: {"type": "paragraph", "attrs": {"nodeId": ...}}
65
69
  - typed-wrapper: {"paragraph": {...}, "nodeId": "..."}
66
70
  - flat-address: {"kind": "block", "nodeType": "paragraph", "nodeId": ...}
@@ -69,11 +73,22 @@ def _collect_paragraph_ids(obj: object, out: list[str]) -> None:
69
73
  seen: set[str] = set(out)
70
74
 
71
75
  def _add(nid: object) -> None:
72
- if isinstance(nid, str) and nid and nid not in seen:
73
- seen.add(nid)
74
- out.append(nid)
76
+ if not isinstance(nid, str) or not nid:
77
+ return
78
+ # Superdoc uses bare UUIDs (or short hashes) — no `paragraph:`
79
+ # prefix. Pass the value through verbatim.
80
+ if nid in seen:
81
+ return
82
+ seen.add(nid)
83
+ out.append(nid)
75
84
 
76
85
  if isinstance(obj, dict):
86
+ # Cell getNodeById shape: {kind: "paragraph", id: "<UUID>", paragraph: {...}}
87
+ kind: object = obj.get("kind")
88
+ if kind == "paragraph" and isinstance(obj.get("id"), str):
89
+ _add(obj.get("id"))
90
+ # Some responses also put the wrapper's id at nodeId.
91
+ _add(obj.get("nodeId"))
77
92
  # Prosemirror-style
78
93
  t: object = obj.get("type")
79
94
  if isinstance(t, str) and t in ("paragraph", "heading"):
@@ -611,118 +626,73 @@ class _Cell:
611
626
  def text(self, value: str) -> None:
612
627
  """Set the cell's text content.
613
628
 
614
- Tries three strategies in order:
615
- 1. Text-range replace on the inner paragraph (fastest, preserves
616
- paragraph-level formatting like alignment, style).
617
- 2. Structural replace of the tableCell with a markdown-derived
618
- fragment via doc.markdownToFragment doc.replace.
619
- 3. Structural replace of the tableCell with a hand-built
620
- prosemirror paragraph fragment as last resort.
621
- """
622
- from docx.text.paragraph import _node_text
629
+ The cell's single inner paragraph is addressed indirectly — Superdoc
630
+ doesn't expose a paragraph ref that's usable as a `blockId` for text
631
+ selections. Instead we use `doc.insert` with a structural paragraph
632
+ fragment at `placement=insideEnd`, which APPENDS inline runs to the
633
+ cell's existing paragraph. For a freshly-created (empty) cell this
634
+ produces `cell.text == value` on read-back.
623
635
 
636
+ For cells that already contain text, callers who truly want "replace"
637
+ semantics should first resolve the cell's paragraph via `doc.find`
638
+ and delete it — see _Cell.clear() (Phase 3).
639
+ """
624
640
  cell_id = self._cell_id()
625
641
  session = self._table._session
626
-
627
- # --- Strategy 1: inner paragraph + text-range replace ---
628
- ids = self._inner_paragraph_ids()
629
- if ids:
630
- first = ids[0]
631
- current = _node_text(session, first)
632
- try:
633
- run_sync(
634
- session.doc.replace(
635
- {
636
- "target": {
637
- "kind": "selection",
638
- "start": {
639
- "kind": "text",
640
- "blockId": first,
641
- "offset": 0,
642
- },
643
- "end": {
644
- "kind": "text",
645
- "blockId": first,
646
- "offset": len(current),
647
- },
648
- },
649
- "text": value,
650
- },
651
- ),
652
- )
653
- for extra in ids[1:]:
654
- existing = _node_text(session, extra)
655
- if existing:
656
- run_sync(
657
- session.doc.replace(
658
- {
659
- "target": {
660
- "kind": "selection",
661
- "start": {
662
- "kind": "text",
663
- "blockId": extra,
664
- "offset": 0,
665
- },
666
- "end": {
667
- "kind": "text",
668
- "blockId": extra,
669
- "offset": len(existing),
670
- },
671
- },
672
- "text": "",
673
- },
674
- ),
675
- )
676
- return
677
- except Exception as e:
678
- _log_warn(
679
- f"_Cell.text text-range replace failed on paragraph "
680
- f"{first}: {e!r}; falling back to structural replace.",
681
- )
682
-
683
- # --- Strategy 2: markdownToFragment + structural replace ---
684
642
  cell_target: dict = {
685
643
  "kind": "block",
686
644
  "nodeType": "tableCell",
687
645
  "nodeId": cell_id,
688
646
  }
647
+ # Superdoc only accepts block-typed fragments at the top level
648
+ # (paragraph/heading/table/image/list/sectionBreak/sdt/tableOfContents).
649
+ # We convert the plain-text value through `doc.markdownToFragment`
650
+ # to get a guaranteed-valid `{kind:"paragraph", paragraph:{inlines:[...]}}`
651
+ # shape, then doc.insert appends its inline runs into the cell's
652
+ # existing paragraph (rather than adding a sibling paragraph).
653
+ #
654
+ # Confirmed against real staging Superdoc. This is the ONLY shape
655
+ # that actually lands text inside a tableCell:
656
+ # - text mode + placement → rejected ("placement only valid
657
+ # with structural content")
658
+ # - doc.replace + tableCell target → replaces the cell itself,
659
+ # destroying the table structure
689
660
  try:
690
661
  frag_result: object = run_sync(
691
662
  session.doc.markdown_to_fragment({"markdown": value or ""}),
692
663
  )
693
- fragment: object = None
694
- if isinstance(frag_result, dict):
695
- fragment = frag_result.get("fragment")
696
- if fragment is not None:
697
- run_sync(
698
- session.doc.replace(
699
- {"target": cell_target, "content": fragment},
700
- ),
701
- )
702
- return
703
- except Exception as e:
704
- _log_warn(
705
- f"_Cell.text markdownToFragment/replace failed: {e!r}; "
706
- f"falling back to prosemirror fragment.",
664
+ fragment: object = (
665
+ frag_result.get("fragment")
666
+ if isinstance(frag_result, dict)
667
+ else None
707
668
  )
708
-
709
- # --- Strategy 3: hand-built prosemirror paragraph fragment ---
710
- pm_fragment: dict = {
711
- "type": "paragraph",
712
- "content": [{"type": "text", "text": value}] if value else [],
713
- }
714
- try:
669
+ # Fall back to a hand-built fragment with Superdoc's native
670
+ # shape if markdownToFragment is unavailable.
671
+ if not isinstance(fragment, dict):
672
+ fragment = {
673
+ "kind": "paragraph",
674
+ "paragraph": {
675
+ "inlines": (
676
+ [{"kind": "run", "run": {"text": value}}]
677
+ if value
678
+ else []
679
+ ),
680
+ },
681
+ }
715
682
  run_sync(
716
- session.doc.replace(
717
- {"target": cell_target, "content": pm_fragment},
683
+ session.doc.insert(
684
+ {
685
+ "target": cell_target,
686
+ "placement": "insideEnd",
687
+ "content": fragment,
688
+ },
718
689
  ),
719
690
  )
720
691
  return
721
692
  except Exception as e:
722
693
  raise RuntimeError(
723
694
  f"Failed to set _Cell.text on cell ({self._row}, {self._col}) "
724
- f"of table {self._table._fresh_node_id()}: all three strategies "
725
- f"failed. Last error: {e!r}",
695
+ f"of table {self._table._fresh_node_id()}: {e!r}",
726
696
  ) from e
727
697
 
728
698
  @property
@@ -730,7 +700,11 @@ class _Cell:
730
700
  from docx.text.paragraph import Paragraph
731
701
 
732
702
  return [
733
- Paragraph(session=self._table._session, node_id=pid)
703
+ Paragraph(
704
+ session=self._table._session,
705
+ node_id=pid,
706
+ node_type="paragraph",
707
+ )
734
708
  for pid in self._inner_paragraph_ids()
735
709
  ]
736
710
 
@@ -803,7 +777,11 @@ class _Cell:
803
777
  raise RuntimeError(
804
778
  f"Superdoc did not return nodeId for _Cell.add_paragraph: {result!r}",
805
779
  )
806
- para = Paragraph(session=self._table._session, node_id=node_id)
780
+ para = Paragraph(
781
+ session=self._table._session,
782
+ node_id=node_id,
783
+ node_type="paragraph",
784
+ )
807
785
  if style:
808
786
  para.style = style
809
787
  return para
@@ -76,9 +76,20 @@ def _walk_inlines(info: object) -> list[dict]:
76
76
  class Paragraph:
77
77
  """A paragraph block in a Word document."""
78
78
 
79
- def __init__(self, *, session: "Session", node_id: str) -> None:
79
+ def __init__(
80
+ self,
81
+ *,
82
+ session: "Session",
83
+ node_id: str,
84
+ node_type: str = "paragraph",
85
+ ) -> None:
80
86
  self._session: "Session" = session
81
87
  self._node_id: str = node_id
88
+ # Track node_type at creation so paragraph-level ops can skip a
89
+ # getNodeById round-trip (which raced against Superdoc mutation
90
+ # commits and raised "Block X was not found" for freshly-created
91
+ # blocks). "paragraph" | "heading" | "listItem".
92
+ self._node_type: str = node_type
82
93
 
83
94
  @property
84
95
  def text(self) -> str:
@@ -290,20 +301,16 @@ class Paragraph:
290
301
  return "\f" in self.text
291
302
 
292
303
  def _block_target(self) -> dict:
293
- """Build a {kind, nodeType, nodeId} target for paragraph-level ops."""
294
- info: object = run_sync(
295
- self._session.doc.get_node_by_id({"id": self._node_id}),
296
- )
297
- node_type: str = "paragraph"
298
- if isinstance(info, dict):
299
- node_obj: object = info.get("node")
300
- if isinstance(node_obj, dict):
301
- raw: object = node_obj.get("nodeType")
302
- if isinstance(raw, str) and raw:
303
- node_type = raw
304
+ """Build a {kind, nodeType, nodeId} target for paragraph-level ops.
305
+
306
+ Uses the node_type cached at construction. This avoids an extra
307
+ getNodeById round-trip that previously raced with Superdoc's
308
+ mutation-commit pipeline and raised "Block X was not found" for
309
+ blocks that had just been created by the same session.
310
+ """
304
311
  return {
305
312
  "kind": "block",
306
- "nodeType": node_type,
313
+ "nodeType": self._node_type,
307
314
  "nodeId": self._node_id,
308
315
  }
309
316
 
@@ -381,7 +388,9 @@ class Paragraph:
381
388
  raise RuntimeError(
382
389
  f"Superdoc did not return nodeId for insert_paragraph_before: {result!r}",
383
390
  )
384
- new_para = Paragraph(session=self._session, node_id=node_id)
391
+ new_para = Paragraph(
392
+ session=self._session, node_id=node_id, node_type="paragraph",
393
+ )
385
394
  if style:
386
395
  new_para.style = style
387
396
  return new_para
@@ -14,7 +14,7 @@ from __future__ import annotations
14
14
  from typing import TYPE_CHECKING
15
15
 
16
16
  from docx._batching import run_sync
17
- from docx.shared import Length, Pt, Twips
17
+ from docx.shared import Length, Twips
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-docx"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # Tag + push a new athena-python-docx release. CI handles the rest:
3
+ # PyPI publish, Docker image rebuild, Daytona snapshot creation, Doppler
4
+ # flip in agora dev + stg. See
5
+ # .github/workflows/athena-python-docx-release.yml for the full flow.
6
+ #
7
+ # Usage:
8
+ # ./scripts/release.sh <version>
9
+ #
10
+ # e.g. ./scripts/release.sh 0.2.3 — releases athena-python-docx==0.2.3
11
+ # by:
12
+ # 1. Updating pyproject.toml + docx/__init__.py
13
+ # 2. Committing the version bump
14
+ # 3. Tagging athena-python-docx-v<version>
15
+ # 4. Pushing the commit + tag to origin
16
+ #
17
+ # CI trigger fires on the tag push and runs the full release pipeline.
18
+
19
+ set -euo pipefail
20
+
21
+ if [ $# -ne 1 ]; then
22
+ echo "Usage: $0 <version> (e.g., 0.2.3)" >&2
23
+ exit 1
24
+ fi
25
+
26
+ VERSION="$1"
27
+ if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.(dev|rc|a|b)[0-9]+)?$ ]]; then
28
+ echo "ERROR: version '$VERSION' is not a valid semver-ish" >&2
29
+ exit 1
30
+ fi
31
+
32
+ SDK_DIR="$(cd "$(dirname "$0")/.." && pwd)"
33
+ REPO_ROOT="$(git -C "$SDK_DIR" rev-parse --show-toplevel)"
34
+
35
+ # Require clean working tree so the commit captures only the version bump.
36
+ if ! git -C "$REPO_ROOT" diff --quiet || ! git -C "$REPO_ROOT" diff --cached --quiet; then
37
+ echo "ERROR: working tree is dirty. Commit or stash first." >&2
38
+ git -C "$REPO_ROOT" status --short >&2
39
+ exit 1
40
+ fi
41
+
42
+ # Bump pyproject.toml
43
+ sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$SDK_DIR/pyproject.toml"
44
+ rm -f "$SDK_DIR/pyproject.toml.bak"
45
+
46
+ # Bump docx/__init__.py
47
+ sed -i.bak "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" "$SDK_DIR/docx/__init__.py"
48
+ rm -f "$SDK_DIR/docx/__init__.py.bak"
49
+
50
+ # Show the diff so the operator sees what will be committed.
51
+ echo "=== Version bump diff ==="
52
+ git -C "$REPO_ROOT" diff "$SDK_DIR/pyproject.toml" "$SDK_DIR/docx/__init__.py"
53
+ echo "========================="
54
+
55
+ TAG="athena-python-docx-v$VERSION"
56
+
57
+ # Commit + tag + push.
58
+ git -C "$REPO_ROOT" add "$SDK_DIR/pyproject.toml" "$SDK_DIR/docx/__init__.py"
59
+ git -C "$REPO_ROOT" commit -m "chore(docx-sdk): bump version to $VERSION"
60
+ git -C "$REPO_ROOT" tag "$TAG"
61
+ git -C "$REPO_ROOT" push
62
+ git -C "$REPO_ROOT" push origin "$TAG"
63
+
64
+ cat <<EOF
65
+
66
+ Tagged $TAG and pushed. CI is now running the full release pipeline:
67
+
68
+ 1. PyPI publish → https://pypi.org/project/athena-python-docx/$VERSION/
69
+ 2. Docker image rebuild → athenaintel/daytona-document:$VERSION
70
+ 3. Daytona snapshot → document-exec:$VERSION
71
+ 4. Doppler flip → agora/dev + agora/stg
72
+
73
+ Watch the run:
74
+ https://github.com/Athena-Intel/demo-app-monorepo/actions/workflows/athena-python-docx-release.yml
75
+
76
+ For prd rollout, after canary verification:
77
+ doppler secrets set --project agora --config prd \\
78
+ DAYTONA_DOCUMENT_EXEC_SNAPSHOT=document-exec:$VERSION
79
+
80
+ EOF