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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/PKG-INFO +1 -1
  2. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/__init__.py +1 -1
  3. athena_python_docx-0.11.3/docx/oxml/__init__.py +216 -0
  4. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/pyproject.toml +1 -1
  5. athena_python_docx-0.11.3/tests/test_oxml_shim.py +174 -0
  6. athena_python_docx-0.11.2/docx/oxml/__init__.py +0 -148
  7. athena_python_docx-0.11.2/tests/test_oxml_shim.py +0 -123
  8. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/.gitignore +0 -0
  9. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/CLAUDE.md +0 -0
  10. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/README.md +0 -0
  11. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_athena_extension.py +0 -0
  12. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_batching.py +0 -0
  13. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_buffer.py +0 -0
  14. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_http.py +0 -0
  15. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_http_doc.py +0 -0
  16. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_image_utils.py +0 -0
  17. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_ptc.py +0 -0
  18. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/_table_styles.py +0 -0
  19. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/api.py +0 -0
  20. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/bookmarks.py +0 -0
  21. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/charts.py +0 -0
  22. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/client.py +0 -0
  23. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/commands.py +0 -0
  24. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/comments.py +0 -0
  25. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/document.py +0 -0
  26. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/enum/__init__.py +0 -0
  27. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/enum/section.py +0 -0
  28. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/enum/style.py +0 -0
  29. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/enum/table.py +0 -0
  30. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/enum/text.py +0 -0
  31. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/errors.py +0 -0
  32. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/exceptions.py +0 -0
  33. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/fields.py +0 -0
  34. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/footnotes.py +0 -0
  35. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/math.py +0 -0
  36. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/opc/__init__.py +0 -0
  37. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/opc/coreprops.py +0 -0
  38. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/revisions.py +0 -0
  39. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/sdt.py +0 -0
  40. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/section.py +0 -0
  41. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/settings.py +0 -0
  42. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/shape.py +0 -0
  43. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/shared.py +0 -0
  44. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/styles/__init__.py +0 -0
  45. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/styles/style.py +0 -0
  46. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/styles/styles.py +0 -0
  47. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/table.py +0 -0
  48. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/__init__.py +0 -0
  49. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/font.py +0 -0
  50. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/hyperlink.py +0 -0
  51. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/pagebreak.py +0 -0
  52. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/paragraph.py +0 -0
  53. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/parfmt.py +0 -0
  54. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/run.py +0 -0
  55. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/text/tabstops.py +0 -0
  56. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/toc.py +0 -0
  57. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/docx/typing.py +0 -0
  58. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/scripts/publish.sh +0 -0
  59. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/scripts/release.sh +0 -0
  60. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/scripts/round_trip_smoke.py +0 -0
  61. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/scripts/smoke_test_block_not_found.py +0 -0
  62. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/__init__.py +0 -0
  63. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/conftest.py +0 -0
  64. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/METHODOLOGY.md +0 -0
  65. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/README.md +0 -0
  66. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/__init__.py +0 -0
  67. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/ab_probe_cases.py +0 -0
  68. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/ab_probe_runner.py +0 -0
  69. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/auto_gen_cases.py +0 -0
  70. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/binary_round_trip.py +0 -0
  71. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/cases.py +0 -0
  72. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/complex_cases.py +0 -0
  73. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/coverage_report.py +0 -0
  74. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/extract.py +0 -0
  75. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/extreme_cases.py +0 -0
  76. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/fake_session.py +0 -0
  77. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/firm_templates/README.md +0 -0
  78. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/firm_templates/__init__.py +0 -0
  79. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/firm_templates/_runner.py +0 -0
  80. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/firm_templates/extractor.py +0 -0
  81. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  82. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  83. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/local_runner.py +0 -0
  84. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/mega_cases.py +0 -0
  85. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshot.py +0 -0
  86. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  87. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  88. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  89. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  90. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  91. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  92. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  93. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  94. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  95. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  96. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  97. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  98. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  99. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  100. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  101. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  102. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  103. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  104. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  105. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  106. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  107. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  108. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  109. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  110. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  111. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  112. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  113. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  114. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  115. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  116. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  117. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  118. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  119. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  120. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  121. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  122. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  123. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  124. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  125. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  126. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  127. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  128. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  129. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  130. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  131. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  132. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  133. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  134. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  135. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  136. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  137. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  138. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  139. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  140. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  141. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  142. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  143. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  144. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  145. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  146. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  147. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  148. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  149. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  150. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  151. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  152. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  153. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  154. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  155. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  156. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  157. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  158. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  159. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  160. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  161. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  162. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  163. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  164. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  165. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  166. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  167. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  168. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  169. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  170. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  171. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  172. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  173. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  174. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  175. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  176. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  177. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  178. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  179. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  180. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  181. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  182. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  183. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  184. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  185. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  186. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  187. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  188. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  189. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  190. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  191. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  192. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  193. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  194. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  195. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  196. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  197. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  198. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  199. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  200. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  201. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  202. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  203. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  204. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  205. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  206. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  207. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  208. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  209. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  210. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  211. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  212. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  213. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  214. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  215. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  216. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  217. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  218. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  219. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  220. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  221. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  222. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  223. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  224. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  225. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  226. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  227. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  228. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  229. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  230. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  231. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  232. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  233. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/ours_spec.json +0 -0
  234. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/parity_crawl.py +0 -0
  235. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/parity_diff.json +0 -0
  236. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/real_world_cases.py +0 -0
  237. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/round_trip_tests.py +0 -0
  238. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/runner.py +0 -0
  239. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/stock_spec.json +0 -0
  240. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/fidelity/test_e2e_against_staging.py +0 -0
  241. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/README.md +0 -0
  242. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/__init__.py +0 -0
  243. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/baseline_gaps.json +0 -0
  244. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/compare.py +0 -0
  245. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/intentional_deviations.json +0 -0
  246. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/introspect.py +0 -0
  247. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  248. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/reports/gap_report.json +0 -0
  249. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/run_parity.py +0 -0
  250. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/snapshots/athena_latest.json +0 -0
  251. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  252. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/parity/test_parity_gap.py +0 -0
  253. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_athena_extensions_contract.py +0 -0
  254. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_athena_extensions_registry.py +0 -0
  255. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_batching_perf.py +0 -0
  256. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_block_not_found_error.py +0 -0
  257. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_buffer.py +0 -0
  258. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  259. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_cell_add_table_not_supported.py +0 -0
  260. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_cell_text_plain_fastpath.py +0 -0
  261. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_collapsed_range_format.py +0 -0
  262. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_command_dataclasses.py +0 -0
  263. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_commands.py +0 -0
  264. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_comments.py +0 -0
  265. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_document_asset_id_property.py +0 -0
  266. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_document_create.py +0 -0
  267. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_document_create_from_template.py +0 -0
  268. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_document_factory_validation.py +0 -0
  269. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_e2e_partial_failure_cascade.py +0 -0
  270. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_http_transport.py +0 -0
  271. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_hyperlink_coalescing.py +0 -0
  272. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_insert_deferred.py +0 -0
  273. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_iter_inner_content.py +0 -0
  274. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_list_styles.py +0 -0
  275. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_merged_cell_secondary_slot.py +0 -0
  276. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_merged_cells.py +0 -0
  277. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_paragraph_text_len_cache.py +0 -0
  278. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_parity_misc.py +0 -0
  279. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_parity_round2.py +0 -0
  280. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_partial_failure_cascade.py +0 -0
  281. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_phase_a_behavior.py +0 -0
  282. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_phase_b_headers_footers.py +0 -0
  283. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_phase_c_tables.py +0 -0
  284. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_pr19766_review_fixes.py +0 -0
  285. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_ptc.py +0 -0
  286. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_python_docx_api_parity.py +0 -0
  287. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_revisions.py +0 -0
  288. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  289. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_add_picture.py +0 -0
  290. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_add_run.py +0 -0
  291. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  292. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_comments_add_comment.py +0 -0
  293. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_comments_get.py +0 -0
  294. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_document_audit.py +0 -0
  295. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_document_element.py +0 -0
  296. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_enum_section.py +0 -0
  297. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_font_audit.py +0 -0
  298. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_header_footer.py +0 -0
  299. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_hyperlink.py +0 -0
  300. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_inline_shape.py +0 -0
  301. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  302. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_misc.py +0 -0
  303. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_paragraph_strict.py +0 -0
  304. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_paragraph_style.py +0 -0
  305. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  306. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_parfmt.py +0 -0
  307. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_row_col_cell.py +0 -0
  308. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_run_add_break.py +0 -0
  309. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_run_bool_setters.py +0 -0
  310. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_run_style.py +0 -0
  311. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_run_style_strict.py +0 -0
  312. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_run_text.py +0 -0
  313. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_run_underline.py +0 -0
  314. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_section_audit.py +0 -0
  315. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_section_dimensions.py +0 -0
  316. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_section_onoff.py +0 -0
  317. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_settings.py +0 -0
  318. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_shared_audit.py +0 -0
  319. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_style.py +0 -0
  320. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_styles.py +0 -0
  321. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_table_audit.py +0 -0
  322. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_table_cell.py +0 -0
  323. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_table_dimensions.py +0 -0
  324. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_silent_stub_table_layout.py +0 -0
  325. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_smoke_integration.py +0 -0
  326. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_style_acceptance.py +0 -0
  327. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_style_font.py +0 -0
  328. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_style_setters_contract.py +0 -0
  329. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_table_set_cell_perf.py +0 -0
  330. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_table_style_id_resolution.py +0 -0
  331. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_temporarily_unavailable.py +0 -0
  332. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_wire_contract.py +0 -0
  333. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/tests/test_zod_wire_contract.py +0 -0
  334. {athena_python_docx-0.11.2 → athena_python_docx-0.11.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.11.2
3
+ Version: 0.11.3
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -6,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.11.3"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -0,0 +1,216 @@
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 (we'd rather a noisy
79
+ failure than silently say no, since the typed error tells the
80
+ caller what to do).
81
+
82
+ The proxy carries the full dotted path so the error message points
83
+ at exactly the offending symbol — ``docx.oxml.ns.qn`` rather than a
84
+ generic "docx.oxml".
85
+ """
86
+
87
+ __slots__ = ("_path",)
88
+
89
+ def __init__(self, path: str) -> None:
90
+ # Bypass __setattr__ guard.
91
+ object.__setattr__(self, "_path", path)
92
+
93
+ def _raise(self, suffix: str = "") -> None:
94
+ path = object.__getattribute__(self, "_path")
95
+ target = f"{path}{suffix}" if suffix else path
96
+ raise OxmlNotAvailableError(f"{target} — {_MESSAGE}")
97
+
98
+ def __call__(self, *args: Any, **kwargs: Any) -> Any: # noqa: ANN401
99
+ self._raise()
100
+
101
+ def __getattr__(self, name: str) -> Any: # noqa: ANN401
102
+ if name.startswith("__") and name.endswith("__"):
103
+ raise AttributeError(name)
104
+ self._raise(f".{name}")
105
+
106
+ def __setattr__(self, name: str, value: Any) -> None: # noqa: ANN401
107
+ # Block writes to the proxy so agents that try
108
+ # ``OxmlElement.tag = "..."`` get the typed error rather than a
109
+ # silent attribute set on the sentinel.
110
+ if name == "_path":
111
+ object.__setattr__(self, name, value)
112
+ return
113
+ self._raise(f".{name}")
114
+
115
+ def __repr__(self) -> str:
116
+ path = object.__getattribute__(self, "_path")
117
+ return f"<docx.oxml lazy stub {path!r} — call to raise>"
118
+
119
+
120
+ class _OxmlStubModule(types.ModuleType):
121
+ """ModuleType subclass that returns :class:`_LazyRaisingProxy`
122
+ sentinels for any attribute access.
123
+
124
+ Installed into :data:`sys.modules` at package-init time so
125
+ qualified imports like ``from docx.oxml.ns import qn`` find a real
126
+ module object (avoiding ``ModuleNotFoundError``). The proxy
127
+ returned defers the typed error until the symbol is actually
128
+ invoked, letting top-level python-docx import blocks succeed.
129
+
130
+ No ``__slots__`` — ``types.ModuleType`` itself carries a
131
+ ``__dict__``, so a slots declaration on a subclass is a no-op.
132
+ """
133
+
134
+ def __getattr__(self, name: str) -> Any: # noqa: ANN401
135
+ if name.startswith("__") and name.endswith("__"):
136
+ # Dunder access (e.g. ``__path__`` during submodule import)
137
+ # must fall through to AttributeError so Python's import
138
+ # machinery can probe without triggering our typed error.
139
+ raise AttributeError(name)
140
+ path = self.__name__
141
+ proxy = _LazyRaisingProxy(f"{path}.{name}")
142
+ # Cache so repeated `getattr` returns the same proxy and so
143
+ # `from X import Y` followed by another `from X import Y`
144
+ # gives the same instance.
145
+ object.__setattr__(self, name, proxy)
146
+ return proxy
147
+
148
+
149
+ # Common upstream submodule paths. Listed explicitly rather than wild-
150
+ # carded so adding a new one is a visible diff. Every entry maps to a
151
+ # single sentinel module; no per-submodule state.
152
+ _SUBMODULES: tuple[str, ...] = (
153
+ "ns",
154
+ "parser",
155
+ "element",
156
+ "xmlchemy",
157
+ "exceptions",
158
+ "ooxml",
159
+ "coreprops",
160
+ "document",
161
+ "text",
162
+ "table",
163
+ "comments",
164
+ "header_footer",
165
+ "footnote",
166
+ "endnote",
167
+ "shared",
168
+ "shape",
169
+ "section",
170
+ "settings",
171
+ "styles",
172
+ "numbering",
173
+ )
174
+
175
+
176
+ def _install_stub_submodules() -> None:
177
+ """Pre-populate :data:`sys.modules` with stub entries for every
178
+ well-known ``docx.oxml.*`` submodule.
179
+
180
+ Idempotent — re-running is a no-op for entries already installed
181
+ (covers the rare case of a test that reloads the package).
182
+ """
183
+ for short in _SUBMODULES:
184
+ full = f"docx.oxml.{short}"
185
+ if full in sys.modules:
186
+ continue
187
+ sys.modules[full] = _OxmlStubModule(full)
188
+
189
+
190
+ _install_stub_submodules()
191
+
192
+
193
+ def __getattr__(name: str) -> Any: # noqa: ANN401
194
+ """PEP 562 attribute hook for ``from docx.oxml import X``.
195
+
196
+ Submodule names (handled via :data:`sys.modules` injection in
197
+ :func:`_install_stub_submodules`) and dunder names fall through
198
+ normally. Everything else returns a :class:`_LazyRaisingProxy` so
199
+ the import succeeds and the typed error fires only on actual use.
200
+ """
201
+ if name.startswith("__") and name.endswith("__"):
202
+ raise AttributeError(name)
203
+ if name in _SUBMODULES:
204
+ # Should already be present in sys.modules; defensive fallback
205
+ # so we never leak ModuleNotFoundError if the caller reloaded
206
+ # the package between calls.
207
+ full = f"docx.oxml.{name}"
208
+ mod = sys.modules.get(full)
209
+ if mod is None:
210
+ mod = _OxmlStubModule(full)
211
+ sys.modules[full] = mod
212
+ return mod
213
+ return _LazyRaisingProxy(f"docx.oxml.{name}")
214
+
215
+
216
+ __all__ = ["OxmlNotAvailableError"]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-docx"
7
- version = "0.11.2"
7
+ version = "0.11.3"
8
8
  description = "Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,174 @@
1
+ """Lock-in tests for the ``docx.oxml`` stub package.
2
+
3
+ ``docx.oxml`` is intentionally unavailable in athena-python-docx —
4
+ agent code that imports ``docx.oxml.ns``, ``docx.oxml.parser``, etc.
5
+ used to fail with the stdlib's generic ``ModuleNotFoundError: No
6
+ module named 'docx.oxml'`` and had to retry to discover the gap.
7
+
8
+ The 0.8.0 stub package replaced that with a single typed
9
+ :class:`OxmlNotAvailableError` that named the high-level python-docx
10
+ surface the caller should use instead — but the 0.8.0 stub raised
11
+ on attribute access, which is what ``from docx.oxml.ns import qn``
12
+ triggers. That meant the import statement itself crashed before any
13
+ line of the agent's actual logic ran. The common python-docx
14
+ convention of declaring ``qn`` / ``OxmlElement`` at the top of every
15
+ script "just in case" killed scripts that only used the high-level
16
+ API.
17
+
18
+ As of 0.11.3 the stub is **lazy**:
19
+
20
+ 1. Every common upstream submodule (``ns``, ``parser``, ``element``,
21
+ ``xmlchemy``, …) is importable — same as 0.8.0.
22
+ 2. Attribute access on those modules now returns a sentinel proxy
23
+ rather than raising — ``from docx.oxml.ns import qn`` succeeds,
24
+ ``qn`` is bound to a :class:`_LazyRaisingProxy`.
25
+ 3. The typed :class:`OxmlNotAvailableError` fires only when the
26
+ imported symbol is actually used — called (``qn(...)``),
27
+ attribute-accessed (``OxmlElement.tag``), or written-to
28
+ (``OxmlElement.tag = "..."``).
29
+
30
+ These tests pin all three contracts so a future refactor can't
31
+ regress to the 0.8.0 import-time failure or to a totally-permissive
32
+ no-op stub.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import importlib
38
+ import pytest
39
+
40
+
41
+ def test_oxml_not_available_error_is_importerror_subclass() -> None:
42
+ """Code that uses ``try / except ImportError`` to detect missing
43
+ oxml support must still catch :class:`OxmlNotAvailableError`."""
44
+ from docx.oxml import OxmlNotAvailableError
45
+
46
+ assert issubclass(OxmlNotAvailableError, ImportError)
47
+
48
+
49
+ @pytest.mark.parametrize(
50
+ "submodule",
51
+ [
52
+ "ns",
53
+ "parser",
54
+ "element",
55
+ "xmlchemy",
56
+ "exceptions",
57
+ "coreprops",
58
+ "document",
59
+ "text",
60
+ "table",
61
+ "comments",
62
+ "header_footer",
63
+ "section",
64
+ "settings",
65
+ "styles",
66
+ "numbering",
67
+ ],
68
+ )
69
+ def test_submodule_import_succeeds(submodule: str) -> None:
70
+ """``import docx.oxml.<X>`` must resolve to a stub module without
71
+ raising. Same contract as 0.8.0."""
72
+ mod = importlib.import_module(f"docx.oxml.{submodule}")
73
+ assert mod is not None
74
+ assert mod.__name__ == f"docx.oxml.{submodule}"
75
+
76
+
77
+ def test_top_level_import_succeeds_lazy() -> None:
78
+ """``from docx.oxml import OxmlElement`` (and friends) must
79
+ succeed and bind a sentinel proxy. The 0.8.0 stub raised at
80
+ import time; 0.11.3 defers the error until the symbol is used.
81
+ """
82
+ from docx.oxml import OxmlElement # must NOT raise
83
+
84
+ assert OxmlElement is not None
85
+ # repr should reveal the lazy nature so debugging is easy.
86
+ assert "docx.oxml.OxmlElement" in repr(OxmlElement)
87
+
88
+
89
+ def test_submodule_import_succeeds_lazy() -> None:
90
+ """``from docx.oxml.ns import qn`` must succeed and bind a sentinel
91
+ proxy. This is the most common python-docx import pattern."""
92
+ from docx.oxml.ns import qn, nsmap # must NOT raise
93
+
94
+ assert qn is not None
95
+ assert nsmap is not None
96
+ assert "docx.oxml.ns.qn" in repr(qn)
97
+
98
+
99
+ def test_call_on_imported_symbol_raises() -> None:
100
+ """The typed error fires when the imported symbol is actually
101
+ invoked. ``qn("w:tcW")`` raises :class:`OxmlNotAvailableError`
102
+ with the educated message pointing at the high-level surface."""
103
+ from docx.oxml import OxmlNotAvailableError
104
+ from docx.oxml.ns import qn
105
+
106
+ with pytest.raises(OxmlNotAvailableError) as exc_info:
107
+ qn("w:tcW")
108
+ msg = str(exc_info.value)
109
+ assert "docx.oxml.ns.qn" in msg
110
+ assert "Document.add_paragraph" in msg
111
+
112
+
113
+ def test_attribute_access_on_imported_symbol_raises() -> None:
114
+ """``OxmlElement.tag`` and similar attribute lookups on the
115
+ sentinel proxy raise the typed error with the full dotted path."""
116
+ from docx.oxml import OxmlElement, OxmlNotAvailableError
117
+
118
+ with pytest.raises(OxmlNotAvailableError) as exc_info:
119
+ _ = OxmlElement.tag
120
+ msg = str(exc_info.value)
121
+ assert "docx.oxml.OxmlElement.tag" in msg
122
+ assert "Document.add_paragraph" in msg
123
+
124
+
125
+ def test_attribute_write_on_imported_symbol_raises() -> None:
126
+ """Writes against the sentinel (``OxmlElement.tag = "..."``) also
127
+ raise — otherwise the assignment silently succeeds and the next
128
+ read of ``OxmlElement.tag`` returns the cached value, masking the
129
+ fact that no XML element actually exists."""
130
+ from docx.oxml import OxmlElement, OxmlNotAvailableError
131
+
132
+ with pytest.raises(OxmlNotAvailableError):
133
+ OxmlElement.tag = "w:foo"
134
+
135
+
136
+ def test_call_on_module_level_symbol_raises() -> None:
137
+ """``oxml.OxmlElement(...)`` (without a from-import) follows the
138
+ same path — the package-level ``__getattr__`` returns the lazy
139
+ proxy, the proxy raises on call."""
140
+ import docx.oxml as oxml
141
+ from docx.oxml import OxmlNotAvailableError
142
+
143
+ with pytest.raises(OxmlNotAvailableError):
144
+ oxml.OxmlElement()
145
+
146
+
147
+ def test_repeated_attribute_access_returns_same_proxy() -> None:
148
+ """``ns.qn`` accessed twice returns the same proxy instance so
149
+ callers that bind it to a local then re-fetch (e.g. assignments
150
+ in different functions) don't get diverging sentinels."""
151
+ import docx.oxml.ns as ns
152
+
153
+ first = ns.qn
154
+ second = ns.qn
155
+ assert first is second
156
+
157
+
158
+ def test_dunder_attribute_access_falls_through() -> None:
159
+ """Dunder access (``__path__``, ``__loader__``, etc.) must NOT
160
+ return a proxy — the import machinery probes those during
161
+ submodule resolution. Treating them as user attribute access
162
+ would break the import path."""
163
+ import docx.oxml.ns as ns
164
+
165
+ with pytest.raises(AttributeError):
166
+ _ = ns.__nonexistent_dunder__
167
+
168
+
169
+ def test_from_submodule_import_succeeds_lazy() -> None:
170
+ """``from docx.oxml.parser import OxmlElement`` must succeed
171
+ under the lazy contract — 0.8.0 raised here, but the agent's
172
+ actual mistake is the call site, not the import."""
173
+ # Should not raise.
174
+ exec("from docx.oxml.parser import OxmlElement", {})
@@ -1,148 +0,0 @@
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. As of
13
- 0.8.0 this package is a typed stub: every import path resolves, but
14
- every attribute access raises :class:`OxmlNotAvailableError` with a
15
- single actionable message pointing at the high-level python-docx
16
- surface (``Document.add_paragraph``, ``Run.bold``, ``_Cell.text``, …)
17
- or the typed command bus (``docx.commands``).
18
-
19
- The gap is documented in ``docx-studio/python-sdk/CLAUDE.md`` § "Intentionally
20
- omitted" and locked into the parity matrix via
21
- ``tests/parity/intentional_deviations.json`` (``docx.oxml**``).
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- import sys
27
- import types
28
- from typing import Any
29
-
30
-
31
- class OxmlNotAvailableError(ImportError):
32
- """Raised on any access to ``docx.oxml.*`` symbols.
33
-
34
- Inherits :class:`ImportError` so callers using
35
- ``try: from docx.oxml import X / except ImportError`` still match,
36
- while ``except OxmlNotAvailableError`` lets newer code distinguish
37
- a "no oxml surface" miss from a real import-resolution failure.
38
- """
39
-
40
-
41
- _MESSAGE: str = (
42
- "docx.oxml is not available in athena-python-docx — the SuperDoc "
43
- "backend doesn't expose raw OOXML elements. Use the high-level "
44
- "python-docx API (Document.add_paragraph, Run.bold, _Cell.text, …) "
45
- "or the typed command surface in docx.commands for any mutation "
46
- "the high-level API doesn't cover. "
47
- "See docx-studio/python-sdk/CLAUDE.md § 'Intentionally omitted' for "
48
- "the parity rationale."
49
- )
50
-
51
-
52
- class _OxmlStubModule(types.ModuleType):
53
- """ModuleType subclass that raises :class:`OxmlNotAvailableError`
54
- on every attribute access.
55
-
56
- Installed into :data:`sys.modules` at package-init time so
57
- qualified imports like ``from docx.oxml.ns import qn`` find a real
58
- module object (avoiding ``ModuleNotFoundError``) and raise the
59
- typed exception when the symbol is dereferenced.
60
-
61
- No ``__slots__`` — ``types.ModuleType`` itself carries a
62
- ``__dict__``, so a slots declaration on a subclass is a no-op and
63
- doesn't prevent ``docx.oxml.ns.qn = something`` from silently
64
- bypassing :meth:`__getattr__` on the next read. If a future caller
65
- needs to block that bypass we'd need a ``__setattr__`` guard that
66
- rejects user-attribute writes while still letting the import
67
- machinery install dunders (``__loader__``, ``__spec__``, …) on the
68
- stub at package-init time.
69
- """
70
-
71
- def __getattr__(self, name: str) -> Any: # noqa: ANN401
72
- if name.startswith("__") and name.endswith("__"):
73
- # Dunder access (e.g. ``__path__`` during submodule import)
74
- # must fall through to AttributeError so Python's import
75
- # machinery can probe without triggering our typed error.
76
- raise AttributeError(name)
77
- path = self.__name__
78
- raise OxmlNotAvailableError(f"{path}.{name} — {_MESSAGE}")
79
-
80
-
81
- # Common upstream submodule paths. Listed explicitly rather than wild-
82
- # carded so adding a new one is a visible diff. Every entry maps to a
83
- # single sentinel module; no per-submodule state.
84
- _SUBMODULES: tuple[str, ...] = (
85
- "ns",
86
- "parser",
87
- "element",
88
- "xmlchemy",
89
- "exceptions",
90
- "ooxml",
91
- "coreprops",
92
- "document",
93
- "text",
94
- "table",
95
- "comments",
96
- "header_footer",
97
- "footnote",
98
- "endnote",
99
- "shared",
100
- "shape",
101
- "section",
102
- "settings",
103
- "styles",
104
- "numbering",
105
- )
106
-
107
-
108
- def _install_stub_submodules() -> None:
109
- """Pre-populate :data:`sys.modules` with stub entries for every
110
- well-known ``docx.oxml.*`` submodule.
111
-
112
- Idempotent — re-running is a no-op for entries already installed
113
- (covers the rare case of a test that reloads the package).
114
- """
115
- for short in _SUBMODULES:
116
- full = f"docx.oxml.{short}"
117
- if full in sys.modules:
118
- continue
119
- sys.modules[full] = _OxmlStubModule(full)
120
-
121
-
122
- _install_stub_submodules()
123
-
124
-
125
- def __getattr__(name: str) -> Any: # noqa: ANN401
126
- """PEP 562 attribute hook for ``from docx.oxml import X``.
127
-
128
- Submodule names (handled via :data:`sys.modules` injection in
129
- :func:`_install_stub_submodules`) and dunder names fall through to
130
- the default ``AttributeError`` so Python's import machinery
131
- behaves normally. Everything else raises the typed error.
132
- """
133
- if name.startswith("__") and name.endswith("__"):
134
- raise AttributeError(name)
135
- if name in _SUBMODULES:
136
- # Should already be present in sys.modules; defensive fallback
137
- # so we never leak ModuleNotFoundError if the caller reloaded
138
- # the package between calls.
139
- full = f"docx.oxml.{name}"
140
- mod = sys.modules.get(full)
141
- if mod is None:
142
- mod = _OxmlStubModule(full)
143
- sys.modules[full] = mod
144
- return mod
145
- raise OxmlNotAvailableError(f"docx.oxml.{name} — {_MESSAGE}")
146
-
147
-
148
- __all__ = ["OxmlNotAvailableError"]
@@ -1,123 +0,0 @@
1
- """Lock-in tests for the ``docx.oxml`` stub package.
2
-
3
- ``docx.oxml`` is intentionally unavailable in athena-python-docx —
4
- agent code that imports ``docx.oxml.ns``, ``docx.oxml.parser``, etc.
5
- used to fail with the stdlib's generic ``ModuleNotFoundError: No
6
- module named 'docx.oxml'`` and had to retry to discover the gap. The
7
- 0.8.0 stub package replaces that with a single typed
8
- :class:`OxmlNotAvailableError` that names the high-level python-docx
9
- surface the caller should use instead, so the agent learns
10
- immediately.
11
-
12
- These tests pin both halves of the contract:
13
-
14
- 1. Every common upstream submodule (``ns``, ``parser``, ``element``,
15
- ``xmlchemy``, etc.) is importable — i.e. the import doesn't raise
16
- ``ModuleNotFoundError``.
17
- 2. Any attribute access on those modules raises
18
- :class:`OxmlNotAvailableError` with the actionable message pointing
19
- at the high-level API.
20
- """
21
-
22
- from __future__ import annotations
23
-
24
- import importlib
25
- import pytest
26
-
27
-
28
- def test_package_attribute_access_raises_typed_error() -> None:
29
- """``from docx.oxml import OxmlElement`` (and friends) must raise
30
- :class:`OxmlNotAvailableError`, not the stdlib's bare
31
- ``ImportError`` or ``AttributeError``."""
32
- import docx.oxml as oxml
33
- from docx.oxml import OxmlNotAvailableError
34
-
35
- with pytest.raises(OxmlNotAvailableError) as exc_info:
36
- _ = oxml.OxmlElement
37
- assert "OxmlElement" in str(exc_info.value)
38
- assert "docx.oxml" in str(exc_info.value)
39
- # The actionable hint must name the high-level surface.
40
- assert "Document.add_paragraph" in str(exc_info.value)
41
-
42
-
43
- def test_oxml_not_available_error_is_importerror_subclass() -> None:
44
- """Code that uses ``try / except ImportError`` to detect missing
45
- oxml support must still catch :class:`OxmlNotAvailableError` so
46
- pre-0.8.0 callers don't regress."""
47
- from docx.oxml import OxmlNotAvailableError
48
-
49
- assert issubclass(OxmlNotAvailableError, ImportError)
50
-
51
-
52
- @pytest.mark.parametrize(
53
- "submodule",
54
- [
55
- "ns",
56
- "parser",
57
- "element",
58
- "xmlchemy",
59
- "exceptions",
60
- "coreprops",
61
- "document",
62
- "text",
63
- "table",
64
- "comments",
65
- "header_footer",
66
- "section",
67
- "settings",
68
- "styles",
69
- "numbering",
70
- ],
71
- )
72
- def test_submodule_import_succeeds(submodule: str) -> None:
73
- """``import docx.oxml.<X>`` must resolve to a stub module without
74
- raising. The stub raises on attribute access, but the import
75
- itself succeeds — that's the contract that distinguishes a stub
76
- from a totally-missing module (which would still raise
77
- ``ModuleNotFoundError`` and force agents to retry)."""
78
- mod = importlib.import_module(f"docx.oxml.{submodule}")
79
- assert mod is not None
80
- assert mod.__name__ == f"docx.oxml.{submodule}"
81
-
82
-
83
- def test_submodule_attribute_access_raises() -> None:
84
- """The whole point of the shim — ``from docx.oxml.ns import qn``
85
- raises :class:`OxmlNotAvailableError` with a message that names
86
- the high-level surface, instead of crashing with the stdlib's
87
- bare ``ModuleNotFoundError``."""
88
- import docx.oxml.ns as ns
89
- from docx.oxml import OxmlNotAvailableError
90
-
91
- with pytest.raises(OxmlNotAvailableError) as exc_info:
92
- _ = ns.qn
93
- assert "docx.oxml.ns.qn" in str(exc_info.value)
94
- # Same hint as the top-level package error.
95
- assert "Document.add_paragraph" in str(exc_info.value)
96
-
97
-
98
- def test_from_submodule_import_raises_typed_error() -> None:
99
- """``from docx.oxml.parser import OxmlElement`` must raise
100
- :class:`OxmlNotAvailableError` even though the submodule import
101
- itself succeeded — the attribute access happens during the
102
- ``from`` step and is the user-observable failure mode."""
103
- from docx.oxml import OxmlNotAvailableError
104
-
105
- import_stmt = "from docx.oxml.parser import OxmlElement"
106
- with pytest.raises(OxmlNotAvailableError):
107
- exec(import_stmt, {})
108
-
109
-
110
- def test_dunder_attribute_access_falls_through() -> None:
111
- """Dunder access (``__path__``, ``__loader__``, etc.) must NOT
112
- raise :class:`OxmlNotAvailableError` — the import machinery probes
113
- those during submodule resolution, and treating them as user
114
- attribute access would break the whole import path. The stub
115
- raises ``AttributeError`` for dunders so the import system can
116
- fall back to its normal behavior."""
117
- import docx.oxml.ns as ns
118
-
119
- # Mimic what the import system does when it can't find an
120
- # explicit attribute — should raise plain AttributeError, not
121
- # OxmlNotAvailableError.
122
- with pytest.raises(AttributeError):
123
- _ = ns.__nonexistent_dunder__