athena-python-docx 0.11.2__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/CLAUDE.md +76 -0
  2. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/PKG-INFO +1 -1
  3. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_http_doc.py +18 -8
  5. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/commands.py +36 -1
  6. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/document.py +143 -0
  7. athena_python_docx-0.12.0/docx/oxml/__init__.py +228 -0
  8. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/section.py +65 -3
  9. athena_python_docx-0.12.0/docx/session.py +27 -0
  10. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/table.py +243 -29
  11. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/paragraph.py +114 -3
  12. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/parfmt.py +60 -35
  13. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/run.py +31 -6
  14. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/pyproject.toml +1 -1
  15. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +1 -1
  16. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +1 -11
  17. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_athena_extensions_registry.py +1 -0
  18. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_block_not_found_error.py +18 -13
  19. athena_python_docx-0.12.0/tests/test_oxml_shim.py +198 -0
  20. athena_python_docx-0.11.2/docx/oxml/__init__.py +0 -148
  21. athena_python_docx-0.11.2/tests/test_oxml_shim.py +0 -123
  22. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/.gitignore +0 -0
  23. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/README.md +0 -0
  24. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_athena_extension.py +0 -0
  25. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_batching.py +0 -0
  26. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_buffer.py +0 -0
  27. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_http.py +0 -0
  28. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_image_utils.py +0 -0
  29. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_ptc.py +0 -0
  30. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_table_styles.py +0 -0
  31. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/api.py +0 -0
  32. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/bookmarks.py +0 -0
  33. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/charts.py +0 -0
  34. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/client.py +0 -0
  35. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/comments.py +0 -0
  36. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/__init__.py +0 -0
  37. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/section.py +0 -0
  38. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/style.py +0 -0
  39. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/table.py +0 -0
  40. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/text.py +0 -0
  41. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/errors.py +0 -0
  42. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/exceptions.py +0 -0
  43. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/fields.py +0 -0
  44. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/footnotes.py +0 -0
  45. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/math.py +0 -0
  46. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/opc/__init__.py +0 -0
  47. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/opc/coreprops.py +0 -0
  48. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/revisions.py +0 -0
  49. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/sdt.py +0 -0
  50. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/settings.py +0 -0
  51. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/shape.py +0 -0
  52. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/shared.py +0 -0
  53. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/styles/__init__.py +0 -0
  54. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/styles/style.py +0 -0
  55. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/styles/styles.py +0 -0
  56. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/__init__.py +0 -0
  57. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/font.py +0 -0
  58. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/hyperlink.py +0 -0
  59. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/pagebreak.py +0 -0
  60. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/tabstops.py +0 -0
  61. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/toc.py +0 -0
  62. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/typing.py +0 -0
  63. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/publish.sh +0 -0
  64. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/release.sh +0 -0
  65. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/round_trip_smoke.py +0 -0
  66. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/smoke_test_block_not_found.py +0 -0
  67. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/__init__.py +0 -0
  68. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/conftest.py +0 -0
  69. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/METHODOLOGY.md +0 -0
  70. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/README.md +0 -0
  71. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/__init__.py +0 -0
  72. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/ab_probe_cases.py +0 -0
  73. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/ab_probe_runner.py +0 -0
  74. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/auto_gen_cases.py +0 -0
  75. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/binary_round_trip.py +0 -0
  76. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/cases.py +0 -0
  77. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/complex_cases.py +0 -0
  78. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/coverage_report.py +0 -0
  79. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/extract.py +0 -0
  80. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/extreme_cases.py +0 -0
  81. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/fake_session.py +0 -0
  82. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/README.md +0 -0
  83. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/__init__.py +0 -0
  84. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/_runner.py +0 -0
  85. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/extractor.py +0 -0
  86. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  87. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  88. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/local_runner.py +0 -0
  89. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/mega_cases.py +0 -0
  90. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshot.py +0 -0
  91. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  92. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  93. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  94. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  95. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  96. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  97. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  98. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  99. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  100. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  101. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  102. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  103. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  104. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  105. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  106. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  107. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  108. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  109. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  110. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  111. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  112. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  113. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  114. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  115. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  116. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  117. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  118. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  119. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  120. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  121. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  122. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  123. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  124. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  125. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  126. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  127. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  128. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  129. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  130. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  131. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  132. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  133. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  134. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  135. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  136. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  137. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  138. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  139. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  140. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  141. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  142. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  143. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  144. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  145. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  146. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  147. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  148. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  149. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  150. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  151. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  152. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  153. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  154. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  155. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  156. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  157. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  158. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  159. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  160. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  161. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  162. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  163. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  164. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  165. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  166. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  167. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  168. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  169. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  170. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  171. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  172. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  173. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  174. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  175. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  176. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  177. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  178. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  179. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  180. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  181. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  182. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  183. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  184. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  185. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  186. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  187. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  188. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  189. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  190. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  191. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  192. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  193. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  194. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  195. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  196. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  197. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  198. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  199. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  200. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  201. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  202. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  203. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  204. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  205. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  206. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  207. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  208. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  209. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  210. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  211. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  212. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  213. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  214. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  215. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  216. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  217. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  218. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  219. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  220. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  221. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  222. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  223. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  224. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  225. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  226. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  227. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  228. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  229. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  230. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  231. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  232. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  233. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  234. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  235. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  236. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/ours_spec.json +0 -0
  237. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/parity_crawl.py +0 -0
  238. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/parity_diff.json +0 -0
  239. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/real_world_cases.py +0 -0
  240. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/round_trip_tests.py +0 -0
  241. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/runner.py +0 -0
  242. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/stock_spec.json +0 -0
  243. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  244. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/README.md +0 -0
  245. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/__init__.py +0 -0
  246. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/baseline_gaps.json +0 -0
  247. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/compare.py +0 -0
  248. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/intentional_deviations.json +0 -0
  249. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/introspect.py +0 -0
  250. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  251. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/reports/gap_report.json +0 -0
  252. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/run_parity.py +0 -0
  253. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/snapshots/athena_latest.json +0 -0
  254. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  255. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/test_parity_gap.py +0 -0
  256. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_athena_extensions_contract.py +0 -0
  257. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_batching_perf.py +0 -0
  258. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_buffer.py +0 -0
  259. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  260. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_cell_add_table_not_supported.py +0 -0
  261. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_cell_text_plain_fastpath.py +0 -0
  262. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_collapsed_range_format.py +0 -0
  263. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_command_dataclasses.py +0 -0
  264. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_commands.py +0 -0
  265. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_comments.py +0 -0
  266. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_asset_id_property.py +0 -0
  267. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_create.py +0 -0
  268. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_create_from_template.py +0 -0
  269. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_factory_validation.py +0 -0
  270. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
  271. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_http_transport.py +0 -0
  272. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_hyperlink_coalescing.py +0 -0
  273. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_insert_deferred.py +0 -0
  274. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_iter_inner_content.py +0 -0
  275. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_list_styles.py +0 -0
  276. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_merged_cell_secondary_slot.py +0 -0
  277. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_merged_cells.py +0 -0
  278. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_paragraph_text_len_cache.py +0 -0
  279. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_parity_misc.py +0 -0
  280. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_parity_round2.py +0 -0
  281. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_partial_failure_cascade.py +0 -0
  282. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_phase_a_behavior.py +0 -0
  283. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_phase_b_headers_footers.py +0 -0
  284. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_phase_c_tables.py +0 -0
  285. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_pr19766_review_fixes.py +0 -0
  286. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_ptc.py +0 -0
  287. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_python_docx_api_parity.py +0 -0
  288. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_revisions.py +0 -0
  289. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  290. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_add_picture.py +0 -0
  291. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_add_run.py +0 -0
  292. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  293. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  294. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_comments_get.py +0 -0
  295. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_document_audit.py +0 -0
  296. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_document_element.py +0 -0
  297. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_enum_section.py +0 -0
  298. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_font_audit.py +0 -0
  299. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_header_footer.py +0 -0
  300. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_hyperlink.py +0 -0
  301. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_inline_shape.py +0 -0
  302. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  303. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_misc.py +0 -0
  304. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  305. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  306. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  307. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_parfmt.py +0 -0
  308. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  309. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_add_break.py +0 -0
  310. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  311. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_style.py +0 -0
  312. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  313. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_text.py +0 -0
  314. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_underline.py +0 -0
  315. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_section_audit.py +0 -0
  316. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_section_dimensions.py +0 -0
  317. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_section_onoff.py +0 -0
  318. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_settings.py +0 -0
  319. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_shared_audit.py +0 -0
  320. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_style.py +0 -0
  321. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_styles.py +0 -0
  322. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_audit.py +0 -0
  323. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_cell.py +0 -0
  324. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  325. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_layout.py +0 -0
  326. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_smoke_integration.py +0 -0
  327. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_style_acceptance.py +0 -0
  328. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_style_font.py +0 -0
  329. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_style_setters_contract.py +0 -0
  330. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_table_set_cell_perf.py +0 -0
  331. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_table_style_id_resolution.py +0 -0
  332. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_temporarily_unavailable.py +0 -0
  333. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_wire_contract.py +0 -0
  334. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_zod_wire_contract.py +0 -0
  335. {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/uv.lock +0 -0
@@ -349,6 +349,82 @@ Issue numbers reference `python-openxml/python-docx`.
349
349
  wontfix upstream but the request keeps recurring. Routes through
350
350
  `ExportPDF` (SuperDoc handles the actual conversion).
351
351
 
352
+ - **`Document.export_docx(path=None, *, include_revisions=False)
353
+ -> bytes`** (0.11.4+) — export the document's current state as
354
+ ``.docx`` bytes, optionally writing to ``path``. Closes the gap
355
+ left by ``Document.save(path)`` raising
356
+ ``LocalSaveTargetNotSupportedError`` in this SDK: parity test
357
+ harnesses and CI workflows need a programmatic .docx export to
358
+ diff against ``python-docx`` output, and pointing callers at
359
+ Olympus's Export DOCX action breaks every headless flow.
360
+ Routes through ``ExportDocx`` → SuperDoc's ``exportDocx``;
361
+ raises ``DocxError`` when the server build doesn't expose it
362
+ rather than silently writing zero bytes.
363
+
364
+ ### 0.11.4 behavior fixes for cell-inner paragraphs
365
+
366
+ Format ops on cell-inner paragraphs (`paragraph.alignment`,
367
+ `paragraph.style`, every `paragraph_format` setter, and
368
+ `paragraph.add_hyperlink`) used to cascade-fail the entire HTTP
369
+ batch. SuperDoc 1.8.1's
370
+ ``SetParagraphAlignment``/``SetParagraphStyle``/``SetParagraphIndentation``/``SetParagraphSpacing``/``CreateHyperlink``
371
+ reject cell-inner block targets with ``BlockNotFoundError``, and the
372
+ applier's all-or-nothing batch model meant one cell-paragraph
373
+ mistake mid-script nuked every other unrelated create/format command
374
+ queued before it (``applied: []``).
375
+
376
+ As of 0.11.4 these setters check the paragraph proxy's ``_in_cell``
377
+ flag (set by ``_Cell.add_paragraph`` and ``_Cell.paragraphs``) and
378
+ emit ``docx.text.paragraph.CellInnerFormatNotSupportedWarning``
379
+ instead of forwarding the broken command. The format op is dropped,
380
+ the surrounding batch survives, and the workaround pointer (
381
+ ``cell.text = 'value'`` to materialize, then ``cell.paragraphs[0]``
382
+ for the addressable proxy) is in the warning text. Tracked at
383
+ ``docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md`` § 13. Behavior pinned
384
+ by ``tests/test_athena_extensions_registry.py`` and the buffer
385
+ tests; ``add_hyperlink`` in a cell drops BOTH the URL and the link
386
+ text (empirically, ``Insert`` on a cell-inner paragraph also fails
387
+ with ``BlockNotFoundError`` — so the obvious ``add_run(text)``
388
+ fallback can't even land the visible text without nuking the
389
+ surrounding batch). The call returns a stub ``Hyperlink`` with an
390
+ empty range so callers don't crash; insert any visible text via
391
+ ``cell.text = '...'`` before calling ``add_hyperlink`` if you need
392
+ the link text to survive.
393
+
394
+ ### 0.11.4 behavior fixes elsewhere
395
+
396
+ - **`Sections.__getitem__` short-circuits on empty docs.**
397
+ ``Document.create()`` doesn't materialize a section client-side,
398
+ so ``sections.list`` returns ``[]`` or times out at the full
399
+ command-budget. Pre-0.11.4 ``doc.sections[0]`` either raised an
400
+ un-contextualized ``IndexError`` from ``items[index]`` or hung
401
+ for ~60s. The cache-then-raise path now fails fast with a clear
402
+ workaround pointer and avoids re-issuing the slow query.
403
+ - **`Table._fresh_node_info` rotation tracking.** Each ``Table``
404
+ created via ``Document.add_table`` records its document-order
405
+ position as ``_doc_index``. When SuperDoc rotates the table's
406
+ nodeId between saves, the fallback resolves the rotated address
407
+ by position instead of always picking the last ``doc.find`` item.
408
+ Pre-0.11.4 the "take the last" heuristic silently mapped N
409
+ rotated proxies onto a single target, surfacing as
410
+ ``ValidationError: Cell (r,c) not found in table table-auto-…``
411
+ on the next cell access.
412
+ - **`set_borders` accepts string and dict.** Both
413
+ ``cell.set_borders(top="single 0.5pt #DDDDDD")`` and
414
+ ``cell.set_borders(top={"style": "single", "size_pt": 0.5,
415
+ "color": "#DDDDDD"})`` work; the string form is normalized via
416
+ ``_normalize_border_spec`` to the wire shape the server's Zod
417
+ validator expects. Pre-0.11.4 only the dict was accepted —
418
+ the string variant documented in the skill produced an immediate
419
+ HTTP 400 from the server's validator.
420
+ - **`Run.add_field` segments shape.** ``run.add_field("PAGE")`` (and
421
+ every other field kind) now emits ``at.segments`` with
422
+ ``[{blockId, start, end}]`` instead of the ``{kind: "selection",
423
+ start, end}`` cursor envelope used by other create-ops.
424
+ SuperDoc's ``doc.fields.insert`` rejects the cursor form with
425
+ ``SuperDocCliError: fields insert:at.segments is required``;
426
+ pre-0.11.4 the call took down the whole batch on every flush.
427
+
352
428
  These additions are surfaced under their natural python-docx-shaped
353
429
  names. When python-docx upstream eventually ships any of them
354
430
  natively, the SDK should swap to the upstream signature in place;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.11.2
3
+ Version: 0.12.0
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -6,7 +6,7 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.11.2"
9
+ __version__ = "0.12.0"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -262,14 +262,24 @@ _CELL_PARAGRAPH_HINT: str = (
262
262
  "\n\nHint: SuperDoc 1.8.1 cannot format paragraphs nested inside "
263
263
  "table cells via SetParagraphAlignment / SetParagraphStyle / "
264
264
  "SetParagraphIndentation / SetParagraphSpacing / doc.insert with "
265
- "a paragraph-block target, or any FormatApply on a cell-inner run. "
266
- "The cell's inner paragraph id is returned by cell.getNodeById but "
267
- "isn't a top-level addressable block. Materialize the cell's "
268
- 'paragraph first via ``cell.text = "value"``, then re-read '
269
- "``cell.paragraphs[0]`` and apply format ops to that post-"
270
- "materialization Paragraph proxy. Tracked upstream at "
271
- "docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md § 'cell-inner paragraph "
272
- "addressing'."
265
+ "a paragraph-block target. The cell's original inner paragraph id "
266
+ "(returned by ``cell.paragraphs[0]`` on a freshly-loaded cell) "
267
+ "isn't a top-level addressable block.\n\nWorkaround: create a "
268
+ "fresh paragraph in the cell via ``para = cell.add_paragraph(text)`` "
269
+ "— 0.11.1+ routes that through ``doc.insert`` against the cell "
270
+ "with ``placement: 'insideEnd'``, so the returned ``Paragraph`` "
271
+ "proxy points at an addressable block. Apply format ops to that "
272
+ "proxy:\n\n"
273
+ ' para = cell.add_paragraph("MAY 2026")\n'
274
+ " para.alignment = WD_ALIGN_PARAGRAPH.RIGHT # works\n"
275
+ " para.add_run(...).bold = True # also works\n\n"
276
+ "Do NOT use ``cell.paragraphs[0].alignment = X`` on a freshly-"
277
+ "loaded cell — that targets the original cell-inner paragraph "
278
+ "which can't be addressed for format ops. If the cell already "
279
+ "has agent-authored content, use ``cell.paragraphs[-1]`` (the "
280
+ "most-recently-inserted paragraph, which is addressable). "
281
+ "Tracked upstream at docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md "
282
+ "§ 'cell-inner paragraph addressing'."
273
283
  )
274
284
 
275
285
 
@@ -1451,6 +1451,30 @@ class ExportPDF(Command):
1451
1451
  include_revisions: bool | None = None
1452
1452
 
1453
1453
 
1454
+ @dataclass
1455
+ class ExportDocx(Command):
1456
+ """Export the document as a ``.docx`` byte stream, optionally storing
1457
+ it under ``destination`` (an asset id, S3 URL, or path).
1458
+
1459
+ Returns ``{bytes_base64, asset_id?}``. The docx-studio applier
1460
+ routes through SuperDoc's ``exportDocx`` SDK call when available;
1461
+ when the server build doesn't expose it, the command returns an
1462
+ empty payload and the Python SDK raises a clear ``DocxError``
1463
+ rather than silently writing zero bytes.
1464
+
1465
+ Athena extension beyond python-docx 1.x — closes the gap left by
1466
+ ``Document.save(path)`` rejecting local targets (an asset-backed
1467
+ SDK can't fulfill the implied "write bytes to disk" contract from
1468
+ the buffer alone). Parity test harnesses against upstream
1469
+ ``python-docx`` need a byte-for-byte export to diff against, and
1470
+ pointing users at Olympus's Export DOCX action breaks every CI
1471
+ workflow that runs headless.
1472
+ """
1473
+
1474
+ destination: str | None = None
1475
+ include_revisions: bool | None = None
1476
+
1477
+
1454
1478
  # ---------------------------------------------------------------------------
1455
1479
  # Numbering / List metadata reads (Athena extension — python-docx
1456
1480
  # issue #471 demands ``Paragraph.get_listnum()``-style access. The
@@ -1580,6 +1604,8 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
1580
1604
  "CreateContentControl",
1581
1605
  # ExportPDF returns bytes the caller needs synchronously.
1582
1606
  "ExportPDF",
1607
+ # ExportDocx returns .docx bytes the caller needs synchronously.
1608
+ "ExportDocx",
1583
1609
  # FindReplace returns a replacement count for the caller.
1584
1610
  "FindReplace",
1585
1611
  }
@@ -1619,8 +1645,15 @@ from docx._athena_extension import ( # noqa: E402 — local circular import saf
1619
1645
  )
1620
1646
 
1621
1647
 
1622
- def _mark_athena_extension_command(cls: type, issue: str, description: str) -> None:
1648
+ def _mark_athena_extension_command(
1649
+ cls: type, issue: "str | None", description: str
1650
+ ) -> None:
1623
1651
  setattr(cls, ATHENA_EXTENSION_ATTR, True)
1652
+ # ``issue`` is ``None`` for additions that don't have a 1:1 upstream
1653
+ # python-docx issue — typically things that are *not* missing from
1654
+ # python-docx but that our asset-backed SDK has to surface differently
1655
+ # (e.g. ``Document.export_docx`` because ``Document.save(path)``
1656
+ # rejects local targets in this SDK).
1624
1657
  setattr(cls, ATHENA_EXTENSION_ISSUE_ATTR, issue)
1625
1658
  setattr(cls, ATHENA_EXTENSION_DESCRIPTION_ATTR, description)
1626
1659
  setattr(cls, ATHENA_EXTENSION_SINCE_ATTR, "0.11.0")
@@ -1672,6 +1705,7 @@ for _cls, _issue, _desc in [
1672
1705
  (FindReplace, "python-docx#30", "Formatting-preserving find/replace"),
1673
1706
  (IterRuns, "python-docx#980", "Run stream for run-isolation"),
1674
1707
  (ExportPDF, "python-docx#113", "PDF export"),
1708
+ (ExportDocx, None, "DOCX export — local file roundtrip"),
1675
1709
  (NumberingGet, "python-docx#471", "Numbering metadata read"),
1676
1710
  (NumberingList, "python-docx#471", "Numbering enumeration"),
1677
1711
  ]:
@@ -1803,6 +1837,7 @@ __all__ = [
1803
1837
  "FindReplace",
1804
1838
  "IterRuns",
1805
1839
  "ExportPDF",
1840
+ "ExportDocx",
1806
1841
  "NumberingGet",
1807
1842
  "NumberingList",
1808
1843
  # Helpers
@@ -168,6 +168,14 @@ class Document:
168
168
  # ``add_paragraph`` / ``add_heading`` / ``add_table`` resets this.
169
169
  self._last_list_item_id: str | None = None
170
170
  self._last_list_kind: str | None = None
171
+ # Document-order counter for tables created via ``add_table``.
172
+ # Captured at the time of creation and stamped on the Table
173
+ # proxy as ``_doc_index`` so the rotation fallback in
174
+ # ``Table._fresh_node_info`` can disambiguate when SuperDoc
175
+ # rotates the table's id between saves. Lazy-initialized to the
176
+ # current top-level table count so docs opened from an existing
177
+ # asset account for pre-existing tables.
178
+ self._table_doc_index_counter: int | None = None
171
179
 
172
180
  @classmethod
173
181
  @athena_extension(
@@ -1018,6 +1026,94 @@ class Document:
1018
1026
  return b""
1019
1027
  return b""
1020
1028
 
1029
+ @athena_extension(
1030
+ since="0.11.4",
1031
+ description="Document.export_docx — write the current state to a local .docx.",
1032
+ )
1033
+ def export_docx(
1034
+ self,
1035
+ path: "str | None" = None,
1036
+ *,
1037
+ include_revisions: bool = False,
1038
+ ) -> bytes:
1039
+ """Export the document's current state as ``.docx`` bytes.
1040
+
1041
+ Athena extension beyond python-docx 1.x. ``Document.save(path)``
1042
+ is intentionally rejected by this SDK (``LocalSaveTargetNotSupportedError``)
1043
+ because the buffer holds no local bytes — the source of truth
1044
+ is the SuperDoc Y.Doc on docx-studio. ``export_docx`` is the
1045
+ explicit "ship the current Y.Doc state to disk" escape hatch:
1046
+ the server runs SuperDoc's ``exportDocx`` against the live
1047
+ session and streams the bytes back, the SDK writes them to
1048
+ ``path`` (when given) and returns them.
1049
+
1050
+ Parameters
1051
+ ----------
1052
+ path:
1053
+ Optional local filesystem path. When provided, the bytes
1054
+ are written there (parents must exist). Always returned
1055
+ from the call regardless.
1056
+ include_revisions:
1057
+ When ``True``, accepted/pending tracked revisions are
1058
+ baked into the exported document instead of being
1059
+ collapsed to their resolved form.
1060
+
1061
+ Returns
1062
+ -------
1063
+ bytes
1064
+ The full ``.docx`` payload.
1065
+
1066
+ Raises
1067
+ ------
1068
+ DocxError
1069
+ When the server-side SuperDoc build doesn't expose
1070
+ ``exportDocx`` (older docx-studio deploys), the command
1071
+ returns an empty payload and this method raises a clear
1072
+ ``DocxError`` instead of silently writing 0 bytes.
1073
+ """
1074
+ from docx.commands import ExportDocx
1075
+ from docx.errors import DocxError
1076
+
1077
+ # Drain anything pending so the export reflects every queued
1078
+ # mutation. ``send_command`` for a response-bearing command
1079
+ # already flushes, but the explicit save makes intent visible
1080
+ # in tracebacks if anything below fails.
1081
+ self.save()
1082
+
1083
+ result = run_sync(
1084
+ self._session.send_command(
1085
+ ExportDocx(
1086
+ destination=None,
1087
+ include_revisions=include_revisions,
1088
+ ),
1089
+ ),
1090
+ )
1091
+ payload: bytes = b""
1092
+ if isinstance(result, dict):
1093
+ import base64
1094
+
1095
+ b64 = result.get("bytesBase64") or result.get("bytes_base64")
1096
+ if isinstance(b64, str):
1097
+ try:
1098
+ payload = base64.b64decode(b64)
1099
+ except (ValueError, TypeError):
1100
+ payload = b""
1101
+
1102
+ if not payload:
1103
+ raise DocxError(
1104
+ "docx-studio returned no bytes for ExportDocx. Either "
1105
+ "the server build doesn't expose SuperDoc's exportDocx "
1106
+ "yet (upgrade docx-studio) or the asset is in a state "
1107
+ "that can't be serialized to .docx. Use to_pdf() as a "
1108
+ "stopgap, or fall back to Olympus's Export DOCX action "
1109
+ "for an interactive export."
1110
+ )
1111
+
1112
+ if path is not None:
1113
+ with open(path, "wb") as f:
1114
+ f.write(payload)
1115
+ return payload
1116
+
1021
1117
  @athena_extension(issue=425, description="Document.add_bookmark.")
1022
1118
  def add_bookmark(
1023
1119
  self,
@@ -1377,6 +1473,13 @@ class Document:
1377
1473
  # SuperDoc id via per-batch ``clientIdMap``, so any subsequent
1378
1474
  # ``set_style({nodeId: cli_id})`` resolves correctly server-side.
1379
1475
  client_node_id = self._mint_client_node_id("t")
1476
+ # Resolve the next document-order position for this new table.
1477
+ # First call queries ``doc.find`` to account for any tables that
1478
+ # already existed when the Document was opened — Document.create()
1479
+ # docs start at 0, ``Document(existing_asset_id)`` starts at
1480
+ # whatever count the asset has.
1481
+ doc_index = self._next_table_doc_index()
1482
+
1380
1483
  table_params: dict = {
1381
1484
  "rows": rows,
1382
1485
  "columns": cols,
@@ -1402,10 +1505,50 @@ class Document:
1402
1505
  node_id=node_id,
1403
1506
  rows=rows,
1404
1507
  columns=cols,
1508
+ doc_index=doc_index,
1405
1509
  )
1406
1510
  self._register_proxy_id(client_node_id, tbl)
1407
1511
  return tbl
1408
1512
 
1513
+ def _next_table_doc_index(self) -> int:
1514
+ """Return the session-relative position the next ``add_table``
1515
+ will occupy, then increment the counter.
1516
+
1517
+ ``_doc_index`` is intentionally session-relative (0-based among
1518
+ tables added in *this* Document handle's lifetime) rather than
1519
+ document-absolute. Pre-0.11.4 (the lazy-init attempt) queried
1520
+ ``doc.find({"type": "table"})`` on the first ``add_table`` to
1521
+ seed the counter with the count of pre-existing tables — but
1522
+ that added a stray ``find`` op to every Document's wire log,
1523
+ which broke the op-snapshot drift tests in
1524
+ ``tests/fidelity/op_snapshots/``. ``Table._fresh_node_info`` does
1525
+ the offset math at rotation-lookup time instead (``items[len(
1526
+ items) - session_count + _doc_index]``), reading the live
1527
+ session count off the buffer attribute set below. For
1528
+ ``Document.create()`` docs (the common case) the offset is 0
1529
+ and the lookup degenerates to the obvious ``items[_doc_index]``.
1530
+
1531
+ Defensive against unit tests that construct ``Document`` via
1532
+ ``Document.__new__`` (skipping ``__init__``) — those instances
1533
+ don't have ``_table_doc_index_counter`` set, so we read it via
1534
+ ``getattr`` and fall back to a fresh local counter that we
1535
+ write back through ``setattr``.
1536
+ """
1537
+ counter = getattr(self, "_table_doc_index_counter", None) or 0
1538
+ idx = counter
1539
+ new_count = idx + 1
1540
+ self._table_doc_index_counter = new_count
1541
+ # Expose the live session count via the session so Table proxies
1542
+ # can compute the offset in ``_fresh_node_info`` rotation
1543
+ # fallbacks without holding a direct reference back to Document.
1544
+ try:
1545
+ session = getattr(self, "_session", None)
1546
+ if session is not None:
1547
+ session._table_session_count = new_count # type: ignore[attr-defined]
1548
+ except Exception:
1549
+ pass
1550
+ return idx
1551
+
1409
1552
  def add_picture(
1410
1553
  self,
1411
1554
  image_path_or_stream: str | BinaryIO,
@@ -0,0 +1,228 @@
1
+ """Stub package — ``docx.oxml`` is intentionally unavailable in
2
+ athena-python-docx.
3
+
4
+ python-docx upstream exposes raw OOXML element classes (``OxmlElement``,
5
+ ``CT_*``, ``qn``, ``nsmap``, …) for advanced users who manipulate
6
+ Word's XML directly. athena-python-docx is backed by a SuperDoc Y.Doc
7
+ + Keryx pipeline, not a local OOXML tree, so there is no XML element
8
+ to manipulate.
9
+
10
+ Before 0.8.0 attempts to ``from docx.oxml.ns import qn`` failed with
11
+ the stdlib's generic ``ModuleNotFoundError: No module named
12
+ 'docx.oxml'`` — agent code had to discover the gap by retry. 0.8.0
13
+ turned this package into a typed stub, but the stub raised
14
+ ``OxmlNotAvailableError`` on attribute access — which is what
15
+ ``from docx.oxml.ns import qn`` triggers, so the import statement
16
+ itself crashed before any line of the agent's actual logic ran. Common
17
+ python-docx convention is to declare ``qn`` / ``OxmlElement`` at the
18
+ top of every script "just in case," and that preamble killed scripts
19
+ that only used the high-level API.
20
+
21
+ As of 0.11.3 the stub is **lazy**: ``from docx.oxml.ns import qn``
22
+ succeeds and returns a sentinel proxy that defers the typed error
23
+ until the symbol is actually called or further-attribute-accessed.
24
+ Scripts that import for parity but never use the symbol now run
25
+ cleanly; scripts that reach for ``qn("w:tcW")`` still get the
26
+ actionable error pointing at the high-level alternative.
27
+
28
+ The gap is documented in ``docx-studio/python-sdk/CLAUDE.md`` § "Intentionally
29
+ omitted" and locked into the parity matrix via
30
+ ``tests/parity/intentional_deviations.json`` (``docx.oxml**``).
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import sys
36
+ import types
37
+ from typing import Any
38
+
39
+
40
+ class OxmlNotAvailableError(ImportError):
41
+ """Raised when a ``docx.oxml.*`` symbol is actually used.
42
+
43
+ Inherits :class:`ImportError` so callers using
44
+ ``try: from docx.oxml import X / except ImportError`` still match
45
+ after 0.11.3's lazy-stub change (the import itself succeeds, but
46
+ the first call site against the imported name raises this — and
47
+ code that uses ``except ImportError`` around a use-site still
48
+ catches it).
49
+
50
+ Use ``except OxmlNotAvailableError`` to distinguish a "no oxml
51
+ surface" miss from a real import-resolution failure or other
52
+ ``ImportError`` cause.
53
+ """
54
+
55
+
56
+ _MESSAGE: str = (
57
+ "docx.oxml is not available in athena-python-docx — the SuperDoc "
58
+ "backend doesn't expose raw OOXML elements. Use the high-level "
59
+ "python-docx API (Document.add_paragraph, Run.bold, _Cell.text, …) "
60
+ "or the typed command surface in docx.commands for any mutation "
61
+ "the high-level API doesn't cover. "
62
+ "See docx-studio/python-sdk/CLAUDE.md § 'Intentionally omitted' for "
63
+ "the parity rationale."
64
+ )
65
+
66
+
67
+ class _LazyRaisingProxy:
68
+ """Sentinel returned by the ``docx.oxml`` stub for any imported name.
69
+
70
+ Behaves as a callable, an attribute-accessible namespace, and an
71
+ awaitable for the union of common python-docx patterns:
72
+
73
+ * ``from docx.oxml.ns import qn`` → ``qn`` is a _LazyRaisingProxy.
74
+ Just importing it is a no-op. ``qn("w:tcW")`` raises with the
75
+ educated message.
76
+ * ``from docx.oxml import OxmlElement`` → same. ``OxmlElement(...)``
77
+ raises; ``OxmlElement.something`` raises.
78
+ * ``isinstance(x, OxmlElement)`` → raises Python's native
79
+ ``TypeError: isinstance() arg 2 must be a type, a tuple of
80
+ types, or a union`` (CPython validates the second argument is a
81
+ type before consulting ``__instancecheck__``, so the proxy never
82
+ sees the call). Agents who reach for it land in the same
83
+ "this surface isn't real" place via a different error class —
84
+ the message names ``isinstance`` so the typed
85
+ :class:`OxmlNotAvailableError` route isn't required to surface
86
+ the gap.
87
+
88
+ The proxy carries the full dotted path so the error message points
89
+ at exactly the offending symbol — ``docx.oxml.ns.qn`` rather than a
90
+ generic "docx.oxml".
91
+ """
92
+
93
+ __slots__ = ("_path",)
94
+
95
+ def __init__(self, path: str) -> None:
96
+ # Bypass __setattr__ guard.
97
+ object.__setattr__(self, "_path", path)
98
+
99
+ def _raise(self, suffix: str = "") -> None:
100
+ path = object.__getattribute__(self, "_path")
101
+ target = f"{path}{suffix}" if suffix else path
102
+ raise OxmlNotAvailableError(f"{target} — {_MESSAGE}")
103
+
104
+ def __call__(self, *args: Any, **kwargs: Any) -> Any: # noqa: ANN401
105
+ self._raise()
106
+
107
+ def __getattr__(self, name: str) -> Any: # noqa: ANN401
108
+ if name.startswith("__") and name.endswith("__"):
109
+ raise AttributeError(name)
110
+ self._raise(f".{name}")
111
+
112
+ def __setattr__(self, name: str, value: Any) -> None: # noqa: ANN401
113
+ # Block writes to the proxy so agents that try
114
+ # ``OxmlElement.tag = "..."`` get the typed error rather than a
115
+ # silent attribute set on the sentinel.
116
+ if name == "_path":
117
+ object.__setattr__(self, name, value)
118
+ return
119
+ self._raise(f".{name}")
120
+
121
+ def __repr__(self) -> str:
122
+ path = object.__getattribute__(self, "_path")
123
+ return f"<docx.oxml lazy stub {path!r} — call to raise>"
124
+
125
+
126
+ class _OxmlStubModule(types.ModuleType):
127
+ """ModuleType subclass that returns :class:`_LazyRaisingProxy`
128
+ sentinels for any attribute access.
129
+
130
+ Installed into :data:`sys.modules` at package-init time so
131
+ qualified imports like ``from docx.oxml.ns import qn`` find a real
132
+ module object (avoiding ``ModuleNotFoundError``). The proxy
133
+ returned defers the typed error until the symbol is actually
134
+ invoked, letting top-level python-docx import blocks succeed.
135
+
136
+ No ``__slots__`` — ``types.ModuleType`` itself carries a
137
+ ``__dict__``, so a slots declaration on a subclass is a no-op.
138
+ """
139
+
140
+ def __getattr__(self, name: str) -> Any: # noqa: ANN401
141
+ if name.startswith("__") and name.endswith("__"):
142
+ # Dunder access (e.g. ``__path__`` during submodule import)
143
+ # must fall through to AttributeError so Python's import
144
+ # machinery can probe without triggering our typed error.
145
+ raise AttributeError(name)
146
+ path = self.__name__
147
+ proxy = _LazyRaisingProxy(f"{path}.{name}")
148
+ # Cache so repeated `getattr` returns the same proxy and so
149
+ # `from X import Y` followed by another `from X import Y`
150
+ # gives the same instance.
151
+ object.__setattr__(self, name, proxy)
152
+ return proxy
153
+
154
+
155
+ # Common upstream submodule paths. Listed explicitly rather than wild-
156
+ # carded so adding a new one is a visible diff. Every entry maps to a
157
+ # single sentinel module; no per-submodule state.
158
+ _SUBMODULES: tuple[str, ...] = (
159
+ "ns",
160
+ "parser",
161
+ "element",
162
+ "xmlchemy",
163
+ "exceptions",
164
+ "ooxml",
165
+ "coreprops",
166
+ "document",
167
+ "text",
168
+ "table",
169
+ "comments",
170
+ "header_footer",
171
+ "footnote",
172
+ "endnote",
173
+ "shared",
174
+ "shape",
175
+ "section",
176
+ "settings",
177
+ "styles",
178
+ "numbering",
179
+ )
180
+
181
+
182
+ def _install_stub_submodules() -> None:
183
+ """Pre-populate :data:`sys.modules` with stub entries for every
184
+ well-known ``docx.oxml.*`` submodule.
185
+
186
+ Idempotent — re-running is a no-op for entries already installed
187
+ (covers the rare case of a test that reloads the package).
188
+ """
189
+ for short in _SUBMODULES:
190
+ full = f"docx.oxml.{short}"
191
+ if full in sys.modules:
192
+ continue
193
+ sys.modules[full] = _OxmlStubModule(full)
194
+
195
+
196
+ _install_stub_submodules()
197
+
198
+
199
+ def __getattr__(name: str) -> Any: # noqa: ANN401
200
+ """PEP 562 attribute hook for ``from docx.oxml import X``.
201
+
202
+ Submodule names (handled via :data:`sys.modules` injection in
203
+ :func:`_install_stub_submodules`) and dunder names fall through
204
+ normally. Everything else returns a :class:`_LazyRaisingProxy` so
205
+ the import succeeds and the typed error fires only on actual use.
206
+
207
+ The proxy is cached into module globals so a second access skips
208
+ ``__getattr__`` and returns the same instance — matches the
209
+ per-submodule caching done by :meth:`_OxmlStubModule.__getattr__`.
210
+ """
211
+ if name.startswith("__") and name.endswith("__"):
212
+ raise AttributeError(name)
213
+ if name in _SUBMODULES:
214
+ # Should already be present in sys.modules; defensive fallback
215
+ # so we never leak ModuleNotFoundError if the caller reloaded
216
+ # the package between calls.
217
+ full = f"docx.oxml.{name}"
218
+ mod = sys.modules.get(full)
219
+ if mod is None:
220
+ mod = _OxmlStubModule(full)
221
+ sys.modules[full] = mod
222
+ return mod
223
+ proxy = _LazyRaisingProxy(f"docx.oxml.{name}")
224
+ globals()[name] = proxy
225
+ return proxy
226
+
227
+
228
+ __all__ = ["OxmlNotAvailableError"]