athena-python-docx 0.6.0__tar.gz → 0.6.2__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 (305) hide show
  1. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/PKG-INFO +1 -1
  2. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/__init__.py +1 -1
  3. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_buffer.py +5 -1
  4. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_http_doc.py +10 -3
  5. athena_python_docx-0.6.2/docx/_ptc.py +138 -0
  6. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/commands.py +40 -0
  7. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/comments.py +20 -0
  8. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/document.py +218 -6
  9. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/section.py +18 -0
  10. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/text.py +27 -0
  11. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/section.py +87 -20
  12. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/shape.py +26 -0
  13. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/table.py +118 -19
  14. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/paragraph.py +163 -8
  15. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/run.py +172 -8
  16. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/pyproject.toml +1 -1
  17. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/fake_session.py +34 -0
  18. athena_python_docx-0.6.0/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/03_runs_with_formatting.json +4 -4
  19. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -1
  20. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -1
  21. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -1
  22. athena_python_docx-0.6.2/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +7 -0
  23. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -1
  24. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -1
  25. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -1
  26. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -1
  27. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -1
  28. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/35_full_report.json +0 -4
  29. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -1
  30. athena_python_docx-0.6.0/tests/fidelity/op_snapshots/07_font_subscript_superscript.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +3 -3
  31. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -1
  32. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -1
  33. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -1
  34. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -1
  35. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -7
  36. athena_python_docx-0.6.2/tests/fidelity/op_snapshots/54_empty_everything.json +4 -0
  37. athena_python_docx-0.6.0/tests/fidelity/op_snapshots/03_runs_with_formatting.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/55_single_character_runs.json +2 -5
  38. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -6
  39. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -3
  40. athena_python_docx-0.6.0/tests/fidelity/op_snapshots/55_single_character_runs.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +3 -6
  41. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/68_invoice.json +0 -1
  42. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/69_newsletter.json +0 -9
  43. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -2
  44. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -6
  45. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -10
  46. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -1
  47. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -1
  48. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -1
  49. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -2
  50. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -1
  51. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -1
  52. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -1
  53. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -1
  54. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -3
  55. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -100
  56. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -1
  57. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -4
  58. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -3
  59. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -15
  60. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -22
  61. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -8
  62. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -4
  63. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -1
  64. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -3
  65. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -3
  66. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -17
  67. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -15
  68. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -11
  69. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -7
  70. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +2 -3
  71. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +6 -1
  72. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -2
  73. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -4
  74. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -6
  75. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -30
  76. athena_python_docx-0.6.2/tests/fidelity/test_e2e_against_staging.py +523 -0
  77. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/introspect.py +14 -0
  78. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/reports/GAP_ANALYSIS.md +3 -28
  79. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/reports/gap_report.json +9 -1659
  80. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/snapshots/athena_latest.json +83 -2
  81. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +4046 -3115
  82. athena_python_docx-0.6.2/tests/test_collapsed_range_format.py +190 -0
  83. athena_python_docx-0.6.2/tests/test_list_styles.py +226 -0
  84. athena_python_docx-0.6.2/tests/test_paragraph_text_len_cache.py +161 -0
  85. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_ptc.py +55 -3
  86. athena_python_docx-0.6.2/tests/test_silent_stub_add_paragraph_style.py +67 -0
  87. athena_python_docx-0.6.2/tests/test_silent_stub_add_run.py +90 -0
  88. athena_python_docx-0.6.2/tests/test_silent_stub_cell_add_paragraph.py +47 -0
  89. athena_python_docx-0.6.2/tests/test_silent_stub_comments_add_comment.py +71 -0
  90. athena_python_docx-0.6.2/tests/test_silent_stub_enum_section.py +83 -0
  91. athena_python_docx-0.6.2/tests/test_silent_stub_inline_shape.py +105 -0
  92. athena_python_docx-0.6.2/tests/test_silent_stub_insert_paragraph_before.py +61 -0
  93. athena_python_docx-0.6.2/tests/test_silent_stub_paragraph_strict.py +170 -0
  94. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_paragraph_style.py +17 -9
  95. athena_python_docx-0.6.2/tests/test_silent_stub_paragraph_style_strict.py +93 -0
  96. athena_python_docx-0.6.2/tests/test_silent_stub_row_col_cell.py +181 -0
  97. athena_python_docx-0.6.2/tests/test_silent_stub_run_add_break.py +82 -0
  98. athena_python_docx-0.6.2/tests/test_silent_stub_run_style.py +57 -0
  99. athena_python_docx-0.6.2/tests/test_silent_stub_run_style_strict.py +71 -0
  100. athena_python_docx-0.6.2/tests/test_silent_stub_run_text.py +71 -0
  101. athena_python_docx-0.6.2/tests/test_silent_stub_run_underline.py +123 -0
  102. athena_python_docx-0.6.2/tests/test_silent_stub_section_dimensions.py +96 -0
  103. athena_python_docx-0.6.2/tests/test_silent_stub_section_onoff.py +83 -0
  104. athena_python_docx-0.6.2/tests/test_silent_stub_table_layout.py +153 -0
  105. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_wire_contract.py +16 -1
  106. athena_python_docx-0.6.0/docx/_ptc.py +0 -173
  107. athena_python_docx-0.6.0/tests/fidelity/op_snapshots/54_empty_everything.json +0 -6
  108. athena_python_docx-0.6.0/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -18
  109. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/.gitignore +0 -0
  110. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/CLAUDE.md +0 -0
  111. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/README.md +0 -0
  112. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_batching.py +0 -0
  113. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_http.py +0 -0
  114. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_image_utils.py +0 -0
  115. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/api.py +0 -0
  116. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/client.py +0 -0
  117. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/__init__.py +0 -0
  118. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/style.py +0 -0
  119. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/table.py +0 -0
  120. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/errors.py +0 -0
  121. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/exceptions.py +0 -0
  122. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/opc/__init__.py +0 -0
  123. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/opc/coreprops.py +0 -0
  124. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/revisions.py +0 -0
  125. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/settings.py +0 -0
  126. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/shared.py +0 -0
  127. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/styles/__init__.py +0 -0
  128. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/styles/style.py +0 -0
  129. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/styles/styles.py +0 -0
  130. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/__init__.py +0 -0
  131. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/font.py +0 -0
  132. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/hyperlink.py +0 -0
  133. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/pagebreak.py +0 -0
  134. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/parfmt.py +0 -0
  135. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/tabstops.py +0 -0
  136. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/typing.py +0 -0
  137. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/scripts/publish.sh +0 -0
  138. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/scripts/release.sh +0 -0
  139. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/scripts/round_trip_smoke.py +0 -0
  140. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/__init__.py +0 -0
  141. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/conftest.py +0 -0
  142. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/METHODOLOGY.md +0 -0
  143. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/README.md +0 -0
  144. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/__init__.py +0 -0
  145. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/ab_probe_cases.py +0 -0
  146. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/ab_probe_runner.py +0 -0
  147. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/auto_gen_cases.py +0 -0
  148. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/binary_round_trip.py +0 -0
  149. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/cases.py +0 -0
  150. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/complex_cases.py +0 -0
  151. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/coverage_report.py +0 -0
  152. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/extract.py +0 -0
  153. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/extreme_cases.py +0 -0
  154. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/local_runner.py +0 -0
  155. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/mega_cases.py +0 -0
  156. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshot.py +0 -0
  157. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  158. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  159. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  160. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  161. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  162. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
  163. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  164. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  165. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  166. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  167. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  168. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  169. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  170. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  171. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  172. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  173. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  174. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  175. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  176. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  177. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
  178. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  179. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  180. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  181. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  182. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  183. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  184. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  185. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  186. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  187. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  188. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  189. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  190. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  191. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  192. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  193. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  194. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  195. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  196. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  197. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
  198. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  199. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  200. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  201. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  202. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  203. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  204. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  205. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  206. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  207. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  208. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  209. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  210. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  211. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  212. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  213. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  214. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  215. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  216. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  217. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  218. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  219. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  220. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  221. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  222. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  223. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  224. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  225. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  226. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  227. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  228. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
  229. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  230. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  231. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  232. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  233. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  234. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  235. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  236. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  237. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  238. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  239. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  240. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  241. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  242. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  243. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  244. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  245. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  246. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  247. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  248. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  249. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
  250. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  251. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/ours_spec.json +0 -0
  252. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/parity_crawl.py +0 -0
  253. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/parity_diff.json +0 -0
  254. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/real_world_cases.py +0 -0
  255. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/round_trip_tests.py +0 -0
  256. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/runner.py +0 -0
  257. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/stock_spec.json +0 -0
  258. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/README.md +0 -0
  259. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/__init__.py +0 -0
  260. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/baseline_gaps.json +0 -0
  261. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/compare.py +0 -0
  262. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/intentional_deviations.json +0 -0
  263. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/run_parity.py +0 -0
  264. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/test_parity_gap.py +0 -0
  265. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_batching_perf.py +0 -0
  266. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_buffer.py +0 -0
  267. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_command_dataclasses.py +0 -0
  268. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_commands.py +0 -0
  269. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_comments.py +0 -0
  270. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_document_create.py +0 -0
  271. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_http_transport.py +0 -0
  272. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_iter_inner_content.py +0 -0
  273. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_merged_cells.py +0 -0
  274. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_parity_misc.py +0 -0
  275. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_parity_round2.py +0 -0
  276. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_phase_a_behavior.py +0 -0
  277. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_phase_b_headers_footers.py +0 -0
  278. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_phase_c_tables.py +0 -0
  279. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_pr19766_review_fixes.py +0 -0
  280. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_python_docx_api_parity.py +0 -0
  281. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_revisions.py +0 -0
  282. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_add_picture.py +0 -0
  283. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_comments_get.py +0 -0
  284. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_document_audit.py +0 -0
  285. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_document_element.py +0 -0
  286. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_font_audit.py +0 -0
  287. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_header_footer.py +0 -0
  288. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_hyperlink.py +0 -0
  289. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_misc.py +0 -0
  290. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_parfmt.py +0 -0
  291. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_run_bool_setters.py +0 -0
  292. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_section_audit.py +0 -0
  293. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_settings.py +0 -0
  294. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_shared_audit.py +0 -0
  295. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_style.py +0 -0
  296. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_styles.py +0 -0
  297. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_table_audit.py +0 -0
  298. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_table_cell.py +0 -0
  299. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_table_dimensions.py +0 -0
  300. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_smoke_integration.py +0 -0
  301. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_style_acceptance.py +0 -0
  302. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_style_font.py +0 -0
  303. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_style_setters_contract.py +0 -0
  304. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_zod_wire_contract.py +0 -0
  305. {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.6.0
3
+ Version: 0.6.2
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.6.0"
9
+ __version__ = "0.6.2"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -278,7 +278,11 @@ class CommandBuffer:
278
278
  # PTC begin: one event per user-facing method call, before any
279
279
  # batching. Failures here can't crash the user's code path.
280
280
  try:
281
- cmd._ptc_call_id = _ptc.emit_begin(type(cmd).__name__, cmd.to_dict()) # type: ignore[attr-defined]
281
+ cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
282
+ type(cmd).__name__,
283
+ cmd.to_dict(),
284
+ asset_id=self._asset_id,
285
+ )
282
286
  except Exception: # noqa: BLE001
283
287
  pass
284
288
 
@@ -68,6 +68,8 @@ from docx.commands import (
68
68
  Insert,
69
69
  InsertLineBreak,
70
70
  InsertTab,
71
+ ListsAttach,
72
+ ListsCreate,
71
73
  ListsMerge,
72
74
  ListsSplit,
73
75
  MarkdownToFragment,
@@ -503,9 +505,14 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
503
505
  "header_footers.get": HeaderFootersGet,
504
506
  "header_footers.resolve": HeaderFootersResolve,
505
507
  "header_footers.refs.set_linked_to_previous": HeaderFootersRefsSetLinkedToPrevious,
506
- # Lists & Selection (SuperDoc 1.8.1 additions; not exposed on the
507
- # python-docx parity surface registered here for typed-bus
508
- # completeness when advanced callers reach for them).
508
+ # Lists & Selection. ``lists.create`` and ``lists.attach`` are
509
+ # consumed internally by ``Document.add_paragraph`` to make
510
+ # ``style="List Bullet" | "List Number"`` produce real list
511
+ # structure (not just a style attribute on a flat paragraph).
512
+ # ``lists.merge``/``lists.split`` stay typed-bus-only — no
513
+ # python-docx parity surface for them.
514
+ "lists.create": ListsCreate,
515
+ "lists.attach": ListsAttach,
509
516
  "lists.merge": ListsMerge,
510
517
  "lists.split": ListsSplit,
511
518
  "selection.current": SelectionCurrent,
@@ -0,0 +1,138 @@
1
+ """Programmatic Tool Calling (PTC) — client side.
2
+
3
+ Activated only when ``ATHENA_PTC_URL`` is set. The URL is a presigned
4
+ endpoint URL with an HMAC-signed token embedded as ``?t=…``; the token
5
+ carries the (thread, parent_tool_call_id, run) triple and an expiry.
6
+ The SDK doesn't need to know that triple — it just POSTs to the URL
7
+ verbatim, and the server derives identity from the token.
8
+
9
+ Without ``ATHENA_PTC_URL`` set, every call here is a no-op.
10
+
11
+ Emits are fire-and-forget on a background daemon thread:
12
+
13
+ - Never raise into user code.
14
+ - Never block the calling thread.
15
+ - Re-read ``ATHENA_PTC_URL`` on every emit so updates between sandbox
16
+ executions take effect immediately (the SDK module itself is cached
17
+ across runs).
18
+ - Snapshot the URL at ``emit_begin`` time and carry it to ``emit_end``
19
+ so late end events can't be misattributed if a new sandbox run
20
+ swapped in a different URL between begin and end.
21
+
22
+ This module is *intentionally byte-identical* across the docx / pptx /
23
+ xlsx SDKs — the three SDKs have no shared base. Duplicating ~100 LOC
24
+ of stdlib code is cheaper than spinning up a 4th release pipeline.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import queue
32
+ import threading
33
+ import time
34
+ import urllib.error
35
+ import urllib.request
36
+ import uuid
37
+ from typing import Any
38
+
39
+ _MAX_QUEUE = 4096
40
+ _MAX_BODY = 64 * 1024
41
+ _HTTP_TIMEOUT = 2.0
42
+
43
+ # Singleton state. PTC has exactly one outbox per process.
44
+ _outbox: queue.Queue[tuple[str, dict[str, Any]]] = queue.Queue(maxsize=_MAX_QUEUE)
45
+ _thread_lock = threading.Lock()
46
+ _thread_started = False
47
+ # call_id -> URL snapshot at begin time; lets emit_end target the
48
+ # original run's endpoint even if ATHENA_PTC_URL changed since.
49
+ _call_url: dict[str, str] = {}
50
+
51
+
52
+ def _drain() -> None:
53
+ while True:
54
+ item = _outbox.get()
55
+ if item is None:
56
+ return
57
+ url, body = item
58
+ try:
59
+ raw = json.dumps(body).encode("utf-8")
60
+ if len(raw) > _MAX_BODY:
61
+ key = "args" if body.get("phase") == "begin" else "result"
62
+ body[key] = {"__truncated__": True}
63
+ raw = json.dumps(body).encode("utf-8")
64
+ req = urllib.request.Request(
65
+ url,
66
+ data=raw,
67
+ method="POST",
68
+ headers={"Content-Type": "application/json"},
69
+ )
70
+ urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT).close()
71
+ except (urllib.error.URLError, OSError, ValueError):
72
+ pass # never propagate
73
+
74
+
75
+ def _ensure_thread() -> None:
76
+ global _thread_started
77
+ if _thread_started:
78
+ return
79
+ with _thread_lock:
80
+ if _thread_started:
81
+ return
82
+ threading.Thread(target=_drain, name="athena-ptc", daemon=True).start()
83
+ _thread_started = True
84
+
85
+
86
+ def _send(url: str, body: dict[str, Any]) -> None:
87
+ _ensure_thread()
88
+ try:
89
+ _outbox.put_nowait((url, body))
90
+ except queue.Full:
91
+ pass # drop on backpressure; never block user code
92
+
93
+
94
+ def emit_begin(
95
+ tool_name: str,
96
+ args: dict[str, Any],
97
+ *,
98
+ asset_id: str | None = None,
99
+ ) -> str:
100
+ call_id = uuid.uuid4().hex
101
+ url = os.environ.get("ATHENA_PTC_URL")
102
+ if not url:
103
+ return call_id
104
+ _call_url[call_id] = url
105
+ body: dict[str, Any] = {
106
+ "callId": call_id,
107
+ "toolName": tool_name,
108
+ "phase": "begin",
109
+ "args": args,
110
+ "ts": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
111
+ }
112
+ if asset_id is not None:
113
+ body["assetId"] = asset_id
114
+ _send(url, body)
115
+ return call_id
116
+
117
+
118
+ def emit_end(
119
+ *,
120
+ call_id: str,
121
+ tool_name: str,
122
+ result: dict[str, Any] | None,
123
+ is_error: bool,
124
+ ) -> None:
125
+ url = _call_url.pop(call_id, None)
126
+ if url is None:
127
+ return # PTC was disabled at begin, or begin wasn't emitted
128
+ _send(
129
+ url,
130
+ {
131
+ "callId": call_id,
132
+ "toolName": tool_name,
133
+ "phase": "end",
134
+ "result": result,
135
+ "isError": is_error,
136
+ "ts": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
137
+ },
138
+ )
@@ -619,6 +619,40 @@ class HeaderFootersRefsSetLinkedToPrevious(Command):
619
619
  # every other op.
620
620
 
621
621
 
622
+ @dataclass
623
+ class ListsCreate(Command):
624
+ """Create a new list — either an empty list at an anchor, or by
625
+ converting an existing paragraph (range) into a list.
626
+
627
+ SuperDoc: ``doc.lists.create``.
628
+
629
+ - ``mode="empty"`` requires ``at`` (BlockAddress).
630
+ - ``mode="fromParagraphs"`` requires ``target`` (BlockAddressOrRange).
631
+ - ``kind`` is ``"bullet"`` or ``"ordered"``.
632
+ """
633
+
634
+ mode: str = "fromParagraphs"
635
+ at: dict[str, Any] | None = None
636
+ target: dict[str, Any] | None = None
637
+ kind: str | None = None
638
+ level: int | None = None
639
+ preset: str | None = None
640
+
641
+
642
+ @dataclass
643
+ class ListsAttach(Command):
644
+ """Attach an existing paragraph to an existing list as a new item.
645
+
646
+ SuperDoc: ``doc.lists.attach``. ``attach_to`` is the list item to
647
+ attach next to (defines list membership); ``target`` is the
648
+ paragraph being converted.
649
+ """
650
+
651
+ attach_to: dict[str, Any] = dataclasses.field(default_factory=dict)
652
+ target: dict[str, Any] = dataclasses.field(default_factory=dict)
653
+ level: int | None = None
654
+
655
+
622
656
  @dataclass
623
657
  class ListsMerge(Command):
624
658
  """Merge a list item with an adjacent list / list item.
@@ -768,6 +802,12 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
768
802
  # describing the entities affected by accept/reject — callers
769
803
  # need that synchronously to invalidate their Revision proxies.
770
804
  "TrackChangesDecide",
805
+ # ListsCreate / ListsAttach return {item: {nodeId: <listItemId>}}.
806
+ # Document.add_paragraph reads listItemId to (1) update the
807
+ # Paragraph proxy's node_id so subsequent ops target the
808
+ # listItem, and (2) chain the next list item via lists.attach.
809
+ "ListsCreate",
810
+ "ListsAttach",
771
811
  }
772
812
  )
773
813
 
@@ -314,6 +314,26 @@ class Comments:
314
314
  Use :meth:`Document.add_comment` to thread a run-anchored
315
315
  target — this method creates an unanchored comment.
316
316
  """
317
+ # python-docx 1.x routes ``text`` / ``author`` / ``initials``
318
+ # through lxml's element setters which TypeError on non-str.
319
+ # Was: any value shipped to wire (text) or stashed as-is on
320
+ # the local info dict (author/initials) — caller's intent was
321
+ # lost when they passed a wrong type.
322
+ if not isinstance(text, str):
323
+ raise TypeError(
324
+ f"Comments.add_comment text requires str; got "
325
+ f"{type(text).__name__} {text!r}",
326
+ )
327
+ if not isinstance(author, str):
328
+ raise TypeError(
329
+ f"Comments.add_comment author requires str; got "
330
+ f"{type(author).__name__} {author!r}",
331
+ )
332
+ if initials is not None and not isinstance(initials, str):
333
+ raise TypeError(
334
+ f"Comments.add_comment initials requires str or None; "
335
+ f"got {type(initials).__name__} {initials!r}",
336
+ )
317
337
  result: object = run_sync(
318
338
  self._session.doc.comments.create({"text": text}),
319
339
  )
@@ -79,6 +79,49 @@ class _InlineShapeIdAdapter:
79
79
  )
80
80
 
81
81
 
82
+ def _classify_list_style(style_id: str | None) -> tuple[str, int] | None:
83
+ """Map a python-docx list-style id to a SuperDoc (kind, level) pair.
84
+
85
+ Returns ``None`` for non-list styles. Recognized styles:
86
+
87
+ - ``"List Bullet"`` → ``("bullet", 0)``
88
+ - ``"List Bullet 2"`` → ``("bullet", 1)`` (... up to ``5`` → level 4)
89
+ - ``"List Number"`` → ``("ordered", 0)``
90
+ - ``"List Number 2"`` → ``("ordered", 1)`` (... up to ``5`` → level 4)
91
+
92
+ Accepts both display-name (``"List Bullet 2"``) and style-id
93
+ (``"ListBullet2"``) forms. ``"List Paragraph"`` and ``"List Continue"``
94
+ are intentionally NOT classified as bullet/number lists — those styles
95
+ indicate "indented body text", not a numbered/bulleted list, and Word
96
+ renders them without a marker. Callers wanting those effects should
97
+ use the style as-is via the generic ``setStyle`` path.
98
+ """
99
+ if not isinstance(style_id, str) or not style_id:
100
+ return None
101
+ # Normalize: lower-case, strip spaces.
102
+ norm = style_id.lower().replace(" ", "")
103
+ if norm.startswith("listbullet"):
104
+ suffix = norm[len("listbullet"):]
105
+ kind = "bullet"
106
+ elif norm.startswith("listnumber"):
107
+ suffix = norm[len("listnumber"):]
108
+ kind = "ordered"
109
+ else:
110
+ return None
111
+ if suffix == "":
112
+ level = 0
113
+ else:
114
+ try:
115
+ n = int(suffix)
116
+ except ValueError:
117
+ return None
118
+ if n < 1 or n > 9:
119
+ return None
120
+ # "List Bullet 2" → level 1 (zero-indexed depth).
121
+ level = n - 1
122
+ return (kind, level)
123
+
124
+
82
125
  class IgnoredSaveTargetWarning(UserWarning):
83
126
  """Emitted when ``Document.save(path_or_stream=...)`` is called with a
84
127
  non-None argument.
@@ -123,6 +166,13 @@ class Document:
123
166
  self._pending_user_name: str | None = user_name
124
167
  self._pending_user_email: str | None = user_email
125
168
  self._pending_track_revisions: bool = bool(track_revisions)
169
+ # List-chain state. When the most-recent ``add_paragraph`` call
170
+ # produced a list item, subsequent ``add_paragraph(..., style="List
171
+ # Bullet"|"List Number")`` calls attach to it (lists.attach)
172
+ # instead of starting a new list (lists.create). Any non-list
173
+ # ``add_paragraph`` / ``add_heading`` / ``add_table`` resets this.
174
+ self._last_list_item_id: str | None = None
175
+ self._last_list_kind: str | None = None
126
176
 
127
177
  @classmethod
128
178
  def create(
@@ -596,9 +646,24 @@ class Document:
596
646
 
597
647
  # Coerce ParagraphStyle instances to their style id so the rest
598
648
  # of this method can treat `style` as an Optional[str].
599
- style_str: str | None = (
600
- style.style_id if isinstance(style, ParagraphStyle) else style
601
- )
649
+ # python-docx 1.x accepts ``str | ParagraphStyle | None`` and
650
+ # raises ``TypeError`` at the style-lookup layer for anything
651
+ # else. Mirror that — the previous code accepted ``style`` as
652
+ # the literal value when it wasn't a ParagraphStyle, so e.g.
653
+ # ``add_paragraph(style=42)`` shipped ``42`` as the style id.
654
+ style_str: str | None
655
+ if style is None:
656
+ style_str = None
657
+ elif isinstance(style, ParagraphStyle):
658
+ style_str = style.style_id
659
+ elif isinstance(style, str):
660
+ style_str = style
661
+ else:
662
+ raise TypeError(
663
+ f"Document.add_paragraph style requires str, "
664
+ f"ParagraphStyle, or None; got "
665
+ f"{type(style).__name__} {style!r}",
666
+ )
602
667
 
603
668
  # Use Superdoc's canonical primitive `doc.create.paragraph`. It
604
669
  # accepts an `at` anchor and a `text` body; an empty `text` creates
@@ -624,8 +689,9 @@ class Document:
624
689
  # heavy lifting.
625
690
  normalized: str = self._normalize_text(text)
626
691
  has_control_chars = any(ch in "\t\n\r" for ch in normalized)
692
+ wire_text: str = "" if has_control_chars else normalized
627
693
  params: dict = {
628
- "text": "" if has_control_chars else normalized,
694
+ "text": wire_text,
629
695
  "at": {"kind": "documentEnd"},
630
696
  }
631
697
  # When inside ``with doc.batch():`` we pre-assign a client UUID
@@ -650,8 +716,13 @@ class Document:
650
716
  raise RuntimeError(
651
717
  f"Superdoc did not return a nodeId for add_paragraph: {result!r}",
652
718
  )
719
+ # Seed the text-length cache so the next ``p.add_run(...)``
720
+ # call can skip its ``get_node_by_id`` HTTP round-trip.
653
721
  para = Paragraph(
654
- session=self._session, node_id=node_id, node_type="paragraph",
722
+ session=self._session,
723
+ node_id=node_id,
724
+ node_type="paragraph",
725
+ text_len=len(wire_text),
655
726
  )
656
727
  if client_node_id is not None:
657
728
  self._register_proxy_for_batch(client_node_id, para)
@@ -668,8 +739,142 @@ class Document:
668
739
  # or transport error otherwise leaves the user with a
669
740
  # paragraph that quietly missed the style they asked for.
670
741
  para.style = style_str
742
+
743
+ # python-docx parity: ``add_paragraph(text, style="List Bullet")``
744
+ # is the canonical way to author a bulleted/numbered list (the
745
+ # upstream library exposes no separate list primitive — the style
746
+ # is the list). On Word/OOXML the style's ``<w:numPr/>`` makes
747
+ # bullets appear; on SuperDoc/ProseMirror the style attribute
748
+ # alone does NOT, because lists are a distinct node type
749
+ # (``bulletList > listItem > paragraph``) rather than a flat
750
+ # paragraph with a style.
751
+ #
752
+ # To make the python-docx contract render correctly we follow
753
+ # up the paragraph create + setStyle with SuperDoc's
754
+ # ``doc.lists.create`` / ``doc.lists.attach`` so the paragraph
755
+ # is structurally converted into a list item under a real
756
+ # bulletList / orderedList node. Consecutive list-styled
757
+ # ``add_paragraph`` calls (of the same kind) chain into a
758
+ # single list via ``lists.attach``; the first one starts a new
759
+ # list via ``lists.create(mode="fromParagraphs")``.
760
+ list_info = _classify_list_style(style_str)
761
+ if list_info is not None:
762
+ kind, level = list_info
763
+ self._convert_paragraph_to_list_item(
764
+ paragraph_node_id=node_id,
765
+ kind=kind,
766
+ level=level,
767
+ )
768
+ else:
769
+ # Any non-list paragraph breaks the chain — the next list
770
+ # item will start a fresh list.
771
+ self._last_list_item_id = None
772
+ self._last_list_kind = None
671
773
  return para
672
774
 
775
+ def _convert_paragraph_to_list_item(
776
+ self,
777
+ *,
778
+ paragraph_node_id: str,
779
+ kind: str,
780
+ level: int,
781
+ ) -> None:
782
+ """Promote a flat paragraph into a list item.
783
+
784
+ Chains with the previous list (same ``kind``) via
785
+ ``lists.attach``; otherwise starts a new list via
786
+ ``lists.create(mode="fromParagraphs")``. Updates the
787
+ ``_last_list_item_id`` / ``_last_list_kind`` chain state so the
788
+ next list-styled ``add_paragraph`` call attaches to this item.
789
+
790
+ Failures don't raise — the paragraph + setStyle already
791
+ landed, and surfacing a structural-promotion error here would
792
+ roll back work the caller can't undo. We emit a console warning
793
+ and reset the chain so subsequent items start fresh rather than
794
+ attaching to an orphaned id.
795
+ """
796
+ para_target: dict = {
797
+ "kind": "block",
798
+ "nodeType": "paragraph",
799
+ "nodeId": paragraph_node_id,
800
+ }
801
+ try:
802
+ if (
803
+ self._last_list_item_id is not None
804
+ and self._last_list_kind == kind
805
+ ):
806
+ result = run_sync(
807
+ self._session.doc.lists.attach(
808
+ {
809
+ "attach_to": {
810
+ "kind": "block",
811
+ "nodeType": "listItem",
812
+ "nodeId": self._last_list_item_id,
813
+ },
814
+ "target": para_target,
815
+ "level": level,
816
+ },
817
+ ),
818
+ )
819
+ else:
820
+ result = run_sync(
821
+ self._session.doc.lists.create(
822
+ {
823
+ "mode": "fromParagraphs",
824
+ "target": para_target,
825
+ "kind": kind,
826
+ "level": level,
827
+ },
828
+ ),
829
+ )
830
+ except Exception as e: # noqa: BLE001
831
+ _log_warn(
832
+ f"list-item promotion failed for paragraph "
833
+ f"{paragraph_node_id!r} ({kind}, level {level}): {e}; "
834
+ f"paragraph remains flat with the style attribute set.",
835
+ )
836
+ self._last_list_item_id = None
837
+ self._last_list_kind = None
838
+ return
839
+
840
+ item_id = self._extract_list_item_id(result)
841
+ if item_id:
842
+ self._last_list_item_id = item_id
843
+ self._last_list_kind = kind
844
+ else:
845
+ # Couldn't read back the listItem id — break the chain so the
846
+ # next call doesn't attach to a stale or missing target.
847
+ _log_warn(
848
+ f"list op returned no item.nodeId for paragraph "
849
+ f"{paragraph_node_id!r}; list chain reset. Response: "
850
+ f"{result!r}",
851
+ )
852
+ self._last_list_item_id = None
853
+ self._last_list_kind = None
854
+
855
+ def _reset_list_chain(self) -> None:
856
+ """Break the consecutive-list-items chain. Called by any block
857
+ insertion that isn't a list-styled paragraph (heading, table,
858
+ picture, page break, section break) so the next list item
859
+ starts a fresh list rather than attaching to a stale predecessor.
860
+ """
861
+ self._last_list_item_id = None
862
+ self._last_list_kind = None
863
+
864
+ @staticmethod
865
+ def _extract_list_item_id(result: object) -> str | None:
866
+ """Pull ``item.nodeId`` out of a ``lists.create`` / ``lists.attach``
867
+ SuperDoc response. Returns ``None`` if the shape is unexpected."""
868
+ if not isinstance(result, dict):
869
+ return None
870
+ item = result.get("item")
871
+ if not isinstance(item, dict):
872
+ return None
873
+ node_id = item.get("nodeId") or item.get("node_id")
874
+ if isinstance(node_id, str) and node_id:
875
+ return node_id
876
+ return None
877
+
673
878
  def add_heading(
674
879
  self,
675
880
  text: str = "",
@@ -679,6 +884,7 @@ class Document:
679
884
  from docx.text.paragraph import Paragraph
680
885
 
681
886
  self._ensure_open()
887
+ self._reset_list_chain()
682
888
  if not 0 <= level <= 9:
683
889
  raise ValidationError(
684
890
  f"level must be in 0..9; got {level}",
@@ -722,6 +928,7 @@ class Document:
722
928
  session=self._session,
723
929
  node_id=node_id,
724
930
  node_type="paragraph",
931
+ text_len=len(wire_text),
725
932
  )
726
933
  if client_node_id is not None:
727
934
  self._register_proxy_for_batch(client_node_id, paragraph)
@@ -753,7 +960,10 @@ class Document:
753
960
  f"Superdoc did not return a nodeId for add_heading: {result!r}",
754
961
  )
755
962
  heading = Paragraph(
756
- session=self._session, node_id=node_id, node_type="heading",
963
+ session=self._session,
964
+ node_id=node_id,
965
+ node_type="heading",
966
+ text_len=len(wire_text),
757
967
  )
758
968
  if client_node_id is not None:
759
969
  self._register_proxy_for_batch(client_node_id, heading)
@@ -771,6 +981,7 @@ class Document:
771
981
  from docx.table import Table
772
982
 
773
983
  self._ensure_open()
984
+ self._reset_list_chain()
774
985
  if rows < 1 or cols < 1:
775
986
  raise ValidationError(
776
987
  f"rows and cols must be >= 1; got rows={rows} cols={cols}",
@@ -858,6 +1069,7 @@ class Document:
858
1069
  from docx.text.run import _build_inline_shape_info
859
1070
 
860
1071
  self._ensure_open()
1072
+ self._reset_list_chain()
861
1073
 
862
1074
  image_bytes: bytes
863
1075
  fallback_path: str = ""
@@ -4,6 +4,17 @@ from __future__ import annotations
4
4
 
5
5
  from enum import Enum
6
6
 
7
+ from docx.enum.text import _warn_enum_coercion
8
+
9
+ __all__ = [
10
+ "WD_HEADER_FOOTER",
11
+ "WD_HEADER_FOOTER_INDEX",
12
+ "WD_ORIENT",
13
+ "WD_ORIENTATION",
14
+ "WD_SECTION",
15
+ "WD_SECTION_START",
16
+ ]
17
+
7
18
 
8
19
  class WD_ORIENTATION(Enum):
9
20
  PORTRAIT = "portrait"
@@ -31,6 +42,13 @@ class WD_SECTION_START(Enum):
31
42
  return "evenPage"
32
43
  if self == WD_SECTION_START.ODD_PAGE:
33
44
  return "oddPage"
45
+ if self == WD_SECTION_START.NEW_PAGE:
46
+ return "nextPage"
47
+ # NEW_COLUMN — SuperDoc 1.8 doesn't have a column-break section
48
+ # type, so we collapse to "nextPage" but emit
49
+ # PendingEnumCoercionWarning so callers don't silently get a
50
+ # page break when they asked for a column break.
51
+ _warn_enum_coercion(self, "nextPage")
34
52
  return "nextPage"
35
53
 
36
54
 
@@ -13,6 +13,19 @@ import warnings
13
13
  from enum import Enum
14
14
 
15
15
 
16
+ _INT_TO_ALIGN: "dict[int, str]" = {
17
+ 0: "LEFT",
18
+ 1: "CENTER",
19
+ 2: "RIGHT",
20
+ 3: "JUSTIFY",
21
+ 4: "DISTRIBUTE",
22
+ 5: "JUSTIFY_MED",
23
+ 7: "JUSTIFY_HI",
24
+ 8: "JUSTIFY_LOW",
25
+ 9: "THAI_JUSTIFY",
26
+ }
27
+
28
+
16
29
  class PendingEnumCoercionWarning(UserWarning):
17
30
  """Emitted when an enum member's :meth:`to_superdoc` collapses the
18
31
  value to a different SuperDoc-supported primitive.
@@ -58,6 +71,20 @@ class WD_ALIGN_PARAGRAPH(Enum):
58
71
  JUSTIFY_LOW = "justifyLow"
59
72
  THAI_JUSTIFY = "thaiJustify"
60
73
 
74
+ @classmethod
75
+ def from_int(cls, value: int) -> "WD_ALIGN_PARAGRAPH | None":
76
+ """Coerce a python-docx-style int alignment to the enum.
77
+
78
+ python-docx 1.x's ``WD_ALIGN_PARAGRAPH`` is keyed on int (its
79
+ ``BaseXmlEnum`` mapping: LEFT=0, CENTER=1, RIGHT=2, JUSTIFY=3,
80
+ DISTRIBUTE=4, JUSTIFY_MED=5, JUSTIFY_HI=7, JUSTIFY_LOW=8,
81
+ THAI_JUSTIFY=9). Callers passing ``paragraph.alignment = 1``
82
+ get CENTER. We mirror that without changing our enum's
83
+ string-valued definition.
84
+ """
85
+ name = _INT_TO_ALIGN.get(value)
86
+ return getattr(cls, name) if name is not None else None
87
+
61
88
  def to_superdoc(self) -> str:
62
89
  # Superdoc only supports left/center/right/justify; coerce the
63
90
  # distributed/thai variants to "justify" so set_alignment doesn't