athena-python-docx 0.19.0__tar.gz → 0.20.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 (366) hide show
  1. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/CLAUDE.md +74 -16
  2. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/PKG-INFO +1 -2
  3. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_image_utils.py +160 -1
  5. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/commands.py +0 -48
  6. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/comments.py +6 -2
  7. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/document.py +73 -33
  8. athena_python_docx-0.20.0/docx/enum/shape.py +30 -0
  9. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/enum/style.py +17 -68
  10. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/opc/coreprops.py +45 -0
  11. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/section.py +75 -33
  12. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/shape.py +23 -6
  13. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/shared.py +10 -8
  14. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/styles/styles.py +45 -20
  15. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/table.py +43 -31
  16. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/hyperlink.py +13 -11
  17. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/paragraph.py +4 -93
  18. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/parfmt.py +25 -22
  19. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/run.py +50 -11
  20. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/pyproject.toml +1 -7
  21. athena_python_docx-0.20.0/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +3 -0
  22. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/intentional_deviations.json +0 -2
  23. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/reports/GAP_ANALYSIS.md +5 -33
  24. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/reports/gap_report.json +9 -1688
  25. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/snapshots/athena_latest.json +2812 -580
  26. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_athena_extensions_registry.py +0 -3
  27. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_commands.py +4 -3
  28. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_parity_misc.py +35 -6
  29. athena_python_docx-0.20.0/tests/test_python_docx_api_parity.py +357 -0
  30. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_hyperlink.py +6 -4
  31. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_parfmt.py +37 -17
  32. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_section_dimensions.py +36 -17
  33. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/uv.lock +1 -12
  34. athena_python_docx-0.19.0/docx/_references.py +0 -82
  35. athena_python_docx-0.19.0/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -1
  36. athena_python_docx-0.19.0/tests/test_python_docx_api_parity.py +0 -161
  37. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/.gitignore +0 -0
  38. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/DOCX_EXEC_LAB.md +0 -0
  39. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/README.md +0 -0
  40. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_athena_extension.py +0 -0
  41. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_batching.py +0 -0
  42. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_buffer.py +0 -0
  43. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_execution.py +0 -0
  44. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_http.py +0 -0
  45. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_http_doc.py +0 -0
  46. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_postproc.py +0 -0
  47. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_ptc.py +0 -0
  48. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_table_styles.py +0 -0
  49. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/_timeouts.py +0 -0
  50. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/api.py +0 -0
  51. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/bookmarks.py +0 -0
  52. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/charts.py +0 -0
  53. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/client.py +0 -0
  54. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/enum/__init__.py +0 -0
  55. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/enum/section.py +0 -0
  56. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/enum/table.py +0 -0
  57. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/enum/text.py +0 -0
  58. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/errors.py +0 -0
  59. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/exceptions.py +0 -0
  60. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/fields.py +0 -0
  61. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/footnotes.py +0 -0
  62. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/math.py +0 -0
  63. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/opc/__init__.py +0 -0
  64. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/oxml/__init__.py +0 -0
  65. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/revisions.py +0 -0
  66. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/sdt.py +0 -0
  67. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/session.py +0 -0
  68. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/settings.py +0 -0
  69. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/styles/__init__.py +0 -0
  70. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/styles/style.py +0 -0
  71. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/__init__.py +0 -0
  72. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/font.py +0 -0
  73. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/pagebreak.py +0 -0
  74. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/text/tabstops.py +0 -0
  75. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/toc.py +0 -0
  76. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/docx/typing.py +0 -0
  77. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/docx_exec_lab.py +0 -0
  78. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
  79. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
  80. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/docx_exec_lab_server.py +0 -0
  81. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/dump_wire_fixtures.py +0 -0
  82. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/publish.sh +0 -0
  83. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/release.sh +0 -0
  84. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/round_trip_smoke.py +0 -0
  85. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/smoke_test_block_not_found.py +0 -0
  86. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/scripts/validate_find_replace_asset.py +0 -0
  87. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/__init__.py +0 -0
  88. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/conftest.py +0 -0
  89. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/METHODOLOGY.md +0 -0
  90. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/README.md +0 -0
  91. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/__init__.py +0 -0
  92. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/ab_probe_cases.py +0 -0
  93. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/ab_probe_runner.py +0 -0
  94. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/auto_gen_cases.py +0 -0
  95. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/binary_round_trip.py +0 -0
  96. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/cases.py +0 -0
  97. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/complex_cases.py +0 -0
  98. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/coverage_report.py +0 -0
  99. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/extract.py +0 -0
  100. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/extreme_cases.py +0 -0
  101. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/fake_session.py +0 -0
  102. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/firm_templates/README.md +0 -0
  103. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/firm_templates/__init__.py +0 -0
  104. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/firm_templates/_runner.py +0 -0
  105. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/firm_templates/extractor.py +0 -0
  106. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  107. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  108. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/local_runner.py +0 -0
  109. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/mega_cases.py +0 -0
  110. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshot.py +0 -0
  111. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  112. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  113. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  114. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  115. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  116. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  117. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  118. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  119. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  120. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  121. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  122. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  123. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  124. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  125. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  126. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  127. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  128. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  129. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  130. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  131. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  132. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  133. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  134. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  135. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  136. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  137. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  138. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  139. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  140. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  141. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  142. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  143. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  144. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  145. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  146. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  147. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  148. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  149. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  150. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  151. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  152. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  153. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  154. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  155. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  156. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  157. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  158. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  159. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  160. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  161. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  162. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  163. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  164. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  165. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  166. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  167. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  168. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  169. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  170. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  171. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  172. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  173. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  174. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  175. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  176. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  177. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  178. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  179. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  180. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  181. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  182. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  183. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  184. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  185. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  186. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  187. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  188. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  189. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  190. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  191. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  192. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  193. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  194. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  195. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  196. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  197. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  198. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  199. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  200. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  201. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  202. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  203. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  204. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  205. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  206. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  207. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  208. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  209. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  210. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  211. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  212. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  213. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  214. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  215. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  216. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  217. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  218. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  219. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  220. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  221. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  222. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  223. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  224. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  225. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  226. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  227. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  228. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  229. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  230. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  231. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  232. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  233. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  234. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  235. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  236. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  237. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  238. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  239. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  240. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  241. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  242. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  243. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  244. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  245. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  246. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  247. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  248. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  249. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  250. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  251. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  252. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  253. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  254. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  255. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  256. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  257. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/ours_spec.json +0 -0
  258. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/parity_crawl.py +0 -0
  259. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/parity_diff.json +0 -0
  260. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/real_world_cases.py +0 -0
  261. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/round_trip_tests.py +0 -0
  262. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/runner.py +0 -0
  263. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/stock_spec.json +0 -0
  264. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  265. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/README.md +0 -0
  266. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/__init__.py +0 -0
  267. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/baseline_gaps.json +0 -0
  268. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/compare.py +0 -0
  269. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/introspect.py +0 -0
  270. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/run_parity.py +0 -0
  271. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  272. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/parity/test_parity_gap.py +0 -0
  273. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_add_section_extract_items.py +0 -0
  274. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_athena_extensions_contract.py +0 -0
  275. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_batching_perf.py +0 -0
  276. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_block_not_found_error.py +0 -0
  277. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_buffer.py +0 -0
  278. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  279. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_add_table_not_supported.py +0 -0
  280. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
  281. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
  282. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_inner_format_stash.py +0 -0
  283. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_inner_run_format_stash.py +0 -0
  284. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_inner_run_guard.py +0 -0
  285. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_text_plain_fastpath.py +0 -0
  286. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_cell_text_replace_semantics.py +0 -0
  287. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_collapsed_range_format.py +0 -0
  288. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_command_dataclasses.py +0 -0
  289. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_comments.py +0 -0
  290. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_document_asset_id_property.py +0 -0
  291. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_document_clear.py +0 -0
  292. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_document_create.py +0 -0
  293. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_document_create_from_template.py +0 -0
  294. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_document_factory_validation.py +0 -0
  295. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_docx_exec_lab.py +0 -0
  296. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_docx_exec_lab_server.py +0 -0
  297. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
  298. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_execution_scope.py +0 -0
  299. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_find_replace_session_open.py +0 -0
  300. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_http_transport.py +0 -0
  301. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_hyperlink_coalescing.py +0 -0
  302. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_image_url_data_uri.py +0 -0
  303. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_insert_deferred.py +0 -0
  304. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_iter_inner_content.py +0 -0
  305. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_list_styles.py +0 -0
  306. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_merged_cell_secondary_slot.py +0 -0
  307. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_merged_cells.py +0 -0
  308. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_oxml_shim.py +0 -0
  309. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_paragraph_text_len_cache.py +0 -0
  310. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_parity_round2.py +0 -0
  311. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_partial_failure_cascade.py +0 -0
  312. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_phase_a_behavior.py +0 -0
  313. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_phase_b_headers_footers.py +0 -0
  314. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_phase_c_tables.py +0 -0
  315. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_positional_cell_id.py +0 -0
  316. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
  317. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
  318. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_postproc_ref_restore.py +0 -0
  319. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_pr19766_review_fixes.py +0 -0
  320. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_ptc.py +0 -0
  321. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_revisions.py +0 -0
  322. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  323. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_add_picture.py +0 -0
  324. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_add_run.py +0 -0
  325. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  326. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  327. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_comments_get.py +0 -0
  328. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_document_audit.py +0 -0
  329. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_document_element.py +0 -0
  330. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_enum_section.py +0 -0
  331. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_font_audit.py +0 -0
  332. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_header_footer.py +0 -0
  333. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_inline_shape.py +0 -0
  334. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  335. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_misc.py +0 -0
  336. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  337. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  338. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  339. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  340. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_run_add_break.py +0 -0
  341. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  342. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_run_style.py +0 -0
  343. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  344. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_run_text.py +0 -0
  345. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_run_underline.py +0 -0
  346. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_section_audit.py +0 -0
  347. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_section_onoff.py +0 -0
  348. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_settings.py +0 -0
  349. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_shared_audit.py +0 -0
  350. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_style.py +0 -0
  351. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_styles.py +0 -0
  352. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_table_audit.py +0 -0
  353. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_table_cell.py +0 -0
  354. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  355. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_silent_stub_table_layout.py +0 -0
  356. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_smoke_integration.py +0 -0
  357. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_style_acceptance.py +0 -0
  358. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_style_font.py +0 -0
  359. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_style_setters_contract.py +0 -0
  360. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_table_set_cell_perf.py +0 -0
  361. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_table_style_id_resolution.py +0 -0
  362. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_temporarily_unavailable.py +0 -0
  363. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_validate_find_replace_asset_script.py +0 -0
  364. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_wire_contract.py +0 -0
  365. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_xml_attr_guard.py +0 -0
  366. {athena_python_docx-0.19.0 → athena_python_docx-0.20.0}/tests/test_zod_wire_contract.py +0 -0
@@ -222,22 +222,6 @@ Issue numbers reference `python-openxml/python-docx`.
222
222
  the same `Hyperlink` class the read path already exposes. Also
223
223
  available as `Run.add_hyperlink` for convenience.
224
224
 
225
- - **`Paragraph.add_citation(*, source, text=None, anchor=None,
226
- display_value=None, label=None) -> str`** + **`Document.remove_citation
227
- (*, citation_id, soft=True)`** — source-citation markers (Athena
228
- studio-linking; no python-docx analog, `issue=None`). Attaches a
229
- Word-style superscript citation marker pointing at an Athena asset
230
- (`source`, an `AssetReference`), optionally narrowed by `anchor` (a
231
- sheet range, PDF page, …). Renders in Olympus as a clickable superscript
232
- with a hover preview + Citations-panel entry — the same unified
233
- citation system as sheets / PPTX / native docs; clicking opens the
234
- source in a new tab. Routes through `CreateCitation` / `RemoveCitation`;
235
- the studio server derives the openable `citation_string` from
236
- `source_ref` + `source_anchor` via `@athenaintel/references` (single
237
- canonical serializer). `docx/_references.py` mirrors the pptx
238
- `AssetReference` + anchor re-exports off the shared `athena-references`
239
- package.
240
-
241
225
  - **`Run.add_field(kind, *, args=None, cached_result=None) -> Field`**
242
226
  + **`Document.fields: Fields`** — generic Word field insertion.
243
227
  Supports `"PAGE"`, `"NUMPAGES"`, `"DATE"`, `"TIME"`, `"AUTHOR"`,
@@ -617,6 +601,80 @@ natively, the SDK should swap to the upstream signature in place;
617
601
  this section is the canonical reference for what each Athena
618
602
  extension wires up.
619
603
 
604
+ ### Behavioral-parity deviations (2026 deep-dive, v0.19.0)
605
+
606
+ A deep behavioral audit against python-docx 1.2.0 (the structural
607
+ parity ratchet only sees shape, not runtime behavior) fixed ~40
608
+ divergences so they now match upstream exactly — `Pt`/`RGBColor`
609
+ coercion, `Font.size` bare-int-is-EMU, `Font.underline`
610
+ SINGLE→`True`/NONE→`False`, `line_spacing` integer multipliers,
611
+ `line_spacing_rule` rule-only members, `Hyperlink.url`/`.address`,
612
+ `Table.autofit`/`_Row.cells`/`_Column.cells`, `Section`
613
+ orientation/start-type defaults + `Sections` slicing,
614
+ `Styles.get_by_id`/`get_style_id`/`default(LIST)`/`add_style`,
615
+ `Document.add_heading` (`ValueError`), `add_paragraph` strict heading
616
+ routing, `add_picture` native size + aspect ratio, `WD_BUILTIN_STYLE`
617
+ (now the 132-member int enum aliased to `WD_STYLE`),
618
+ `docx.enum.shape.WD_INLINE_SHAPE`, `InlineShape.type`/zero-dimensions,
619
+ and `CoreProperties` validation. These are not deviations — they close
620
+ gaps. The items below are the **residual intentional deviations** that
621
+ remain because matching upstream would break the asset-backed/HTTP
622
+ architecture or a SuperDoc constraint:
623
+
624
+ - **`WD_*` enum `.value` is the SuperDoc wire string, not the MS Word
625
+ integer constant.** `WD_ALIGN_PARAGRAPH.CENTER.value == "center"`
626
+ (upstream: `1`); `int(member)` / `member == 1` / `WD_*(1)` therefore
627
+ differ. The string value is load-bearing — every command serializes
628
+ `enum.value` directly onto the wire (`underline=value.value`,
629
+ `WD_COLOR_INDEX(v).value`, …), so int-valuing the enums would require
630
+ re-plumbing the entire command layer. Members are accessed by name
631
+ (`WD_ALIGN_PARAGRAPH.CENTER`), so the drift is academic for normal
632
+ use. (`WD_STYLE`/`WD_BUILTIN_STYLE` and `WD_INLINE_SHAPE_TYPE` are NOT
633
+ wire-load-bearing and DO carry the upstream int values.)
634
+
635
+ - **`RGBColor.from_string` additionally accepts a leading `#`.** Upstream
636
+ rejects `"#FF0000"`; agents routinely pass it, so a single `#` is
637
+ stripped before the (otherwise byte-identical) upstream slice parse.
638
+ A strict superset — every value upstream accepts parses identically.
639
+
640
+ - **`_Cell.vertical_alignment` setter accepts the four SuperDoc wire
641
+ tokens** (`"top"`/`"center"`/`"bottom"`/`"both"`) in addition to
642
+ `WD_ALIGN_VERTICAL` members. Upstream rejects all plain strings; this
643
+ is an ergonomic superset (typos like `"middle"` still raise
644
+ `ValueError`).
645
+
646
+ - **`Table.cell(row, col)` bounds-checks each axis independently** and
647
+ raises `IndexError` for an out-of-range row or column. Upstream
648
+ indexes a single flat row-major `_cells` list, so a column overflow
649
+ spills into the next row and `cell(-1, -1)` resolves against the flat
650
+ list (NOT the bottom-right cell). Athena's per-axis contract is the
651
+ more intuitive, stricter behavior for an HTTP-backed proxy that can't
652
+ cheaply enumerate every cell. `(0,0)` and in-range negative indices
653
+ behave identically to upstream.
654
+
655
+ - **`BaseStyle.style_id` returns the display name for built-ins** (so
656
+ `style_id == name`). The SDK is HTTP-only and can't read `styles.xml`
657
+ to recover the OOXML styleId (`"Heading 1"` → `"Heading1"`); the wire
658
+ format also uses the display name as the style id. Consequently
659
+ `get_by_id("Heading1", …)` / `"Heading1" in styles` won't resolve the
660
+ OOXML-id form (use the display name).
661
+
662
+ - **`Document.add_table(rows, cols)` requires `rows >= 1` and
663
+ `cols >= 1`.** Upstream allows a 0-row/0-col grid you later populate,
664
+ but SuperDoc's ProseMirror table node can't represent one; the SDK
665
+ raises eagerly rather than failing opaquely on the wire.
666
+
667
+ - **`Comment.comment_id` returns `str`, not `int`.** SuperDoc uses
668
+ non-numeric entity ids (`"c-42"`), so the upstream `int` contract
669
+ can't be honored. Tracked in `SUPERDOC_UPSTREAM_REQUESTS.md`.
670
+
671
+ - **`Section` dimension setters and `ParagraphFormat.line_spacing_rule
672
+ = None` warn-and-preserve on a clear** rather than raising (upstream
673
+ accepts `None` to clear, but SuperDoc's merging mutation ops have no
674
+ single-attribute clear). They emit a `Pending…ClearWarning` and leave
675
+ the value intact instead of destructively clearing the whole block.
676
+ Tracked in `SUPERDOC_UPSTREAM_REQUESTS.md`.
677
+
620
678
  ### If you need a deviation
621
679
 
622
680
  If there is a genuine technical reason why a deviation from python-docx is necessary:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.19.0
3
+ Version: 0.20.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>
@@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Python: >=3.11
14
- Requires-Dist: athena-references>=0.1.0
15
14
  Requires-Dist: requests>=2.28
16
15
  Requires-Dist: urllib3>=1.26.6
17
16
  Provides-Extra: dev
@@ -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.19.0"
9
+ __version__ = "0.20.0"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -211,4 +211,163 @@ def _decode_image_data_uri(uri: str) -> bytes:
211
211
  raise ValueError(f"add_picture could not decode data: URI: {exc}") from exc
212
212
 
213
213
 
214
- __all__ = ["load_image_bytes", "sniff_content_type"]
214
+ # ---------------------------------------------------------------------------
215
+ # Native dimension / DPI sniffing — parity with python-docx's add_picture
216
+ # native-size + aspect-ratio behavior (no Pillow dependency).
217
+ # ---------------------------------------------------------------------------
218
+
219
+ _EMU_PER_INCH: int = 914400
220
+
221
+
222
+ def _png_dimensions(b: bytes) -> "tuple[int, int, float, float] | None":
223
+ # IHDR (width/height) sits right after the 8-byte signature in the
224
+ # first chunk: [4-byte len][b"IHDR"][4-byte width][4-byte height]...
225
+ if len(b) < 24 or b[12:16] != b"IHDR":
226
+ return None
227
+ px_w = int.from_bytes(b[16:20], "big")
228
+ px_h = int.from_bytes(b[20:24], "big")
229
+ if px_w <= 0 or px_h <= 0:
230
+ return None
231
+ horz_dpi = vert_dpi = 72.0 # python-docx's PNG default
232
+ # Optional pHYs chunk carries pixels-per-unit; unit 1 == metre.
233
+ idx = b.find(b"pHYs")
234
+ if idx != -1 and idx + 13 <= len(b):
235
+ ppu_x = int.from_bytes(b[idx + 4 : idx + 8], "big")
236
+ ppu_y = int.from_bytes(b[idx + 8 : idx + 12], "big")
237
+ unit = b[idx + 12]
238
+ if unit == 1 and ppu_x > 0 and ppu_y > 0:
239
+ # python-docx rounds DPI to an int (``int(round(ppu*0.0254))``),
240
+ # so match that to produce identical native EMU dimensions.
241
+ horz_dpi = float(round(ppu_x * 0.0254))
242
+ vert_dpi = float(round(ppu_y * 0.0254))
243
+ return px_w, px_h, horz_dpi, vert_dpi
244
+
245
+
246
+ def _gif_dimensions(b: bytes) -> "tuple[int, int, float, float] | None":
247
+ if len(b) < 10:
248
+ return None
249
+ px_w = int.from_bytes(b[6:8], "little")
250
+ px_h = int.from_bytes(b[8:10], "little")
251
+ if px_w <= 0 or px_h <= 0:
252
+ return None
253
+ # GIF carries no resolution; python-docx defaults to 72 DPI.
254
+ return px_w, px_h, 72.0, 72.0
255
+
256
+
257
+ def _bmp_dimensions(b: bytes) -> "tuple[int, int, float, float] | None":
258
+ if len(b) < 26:
259
+ return None
260
+ px_w = int.from_bytes(b[18:22], "little", signed=True)
261
+ px_h = abs(int.from_bytes(b[22:26], "little", signed=True))
262
+ if px_w <= 0 or px_h <= 0:
263
+ return None
264
+ horz_dpi = vert_dpi = 96.0 # python-docx's BMP default
265
+ if len(b) >= 46:
266
+ ppm_x = int.from_bytes(b[38:42], "little", signed=True)
267
+ ppm_y = int.from_bytes(b[42:46], "little", signed=True)
268
+ if ppm_x > 0 and ppm_y > 0:
269
+ horz_dpi = float(round(ppm_x * 0.0254))
270
+ vert_dpi = float(round(ppm_y * 0.0254))
271
+ return px_w, px_h, horz_dpi, vert_dpi
272
+
273
+
274
+ def _jpeg_dimensions(b: bytes) -> "tuple[int, int, float, float] | None":
275
+ # Default density per python-docx's JFIF handling.
276
+ horz_dpi = vert_dpi = 72.0
277
+ px_w = px_h = 0
278
+ n = len(b)
279
+ pos = 2 # skip SOI (FF D8)
280
+ while pos + 4 <= n:
281
+ if b[pos] != 0xFF:
282
+ pos += 1
283
+ continue
284
+ marker = b[pos + 1]
285
+ if marker in (0xD8, 0xD9) or 0xD0 <= marker <= 0xD7:
286
+ pos += 2
287
+ continue
288
+ seg_len = int.from_bytes(b[pos + 2 : pos + 4], "big")
289
+ if seg_len < 2:
290
+ break
291
+ seg = b[pos + 4 : pos + 2 + seg_len]
292
+ if marker == 0xE0 and seg[:5] == b"JFIF\x00" and len(seg) >= 12:
293
+ units = seg[7]
294
+ dx = int.from_bytes(seg[8:10], "big")
295
+ dy = int.from_bytes(seg[10:12], "big")
296
+ if units == 1 and dx and dy: # dots per inch
297
+ horz_dpi, vert_dpi = float(dx), float(dy)
298
+ elif units == 2 and dx and dy: # dots per cm
299
+ horz_dpi, vert_dpi = dx * 2.54, dy * 2.54
300
+ # SOFn frame markers carry the actual dimensions.
301
+ elif marker in (
302
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7,
303
+ 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF,
304
+ ) and len(seg) >= 5:
305
+ px_h = int.from_bytes(seg[1:3], "big")
306
+ px_w = int.from_bytes(seg[3:5], "big")
307
+ break
308
+ pos += 2 + seg_len
309
+ if px_w <= 0 or px_h <= 0:
310
+ return None
311
+ return px_w, px_h, horz_dpi, vert_dpi
312
+
313
+
314
+ def sniff_image_dimensions(image_bytes: bytes) -> "tuple[int, int, float, float] | None":
315
+ """Return ``(px_width, px_height, horz_dpi, vert_dpi)`` for raster
316
+ image bytes, or ``None`` if the format/dimensions can't be parsed.
317
+
318
+ Mirrors the header parsers in python-docx's ``docx.image`` (PNG,
319
+ JPEG, GIF, BMP) without requiring Pillow. DPI defaults match
320
+ python-docx (72 for PNG/JPEG/GIF, 96 for BMP) when the image carries
321
+ no embedded resolution.
322
+ """
323
+ if image_bytes.startswith(_PNG_MAGIC):
324
+ return _png_dimensions(image_bytes)
325
+ if image_bytes.startswith(_JPEG_MAGIC):
326
+ return _jpeg_dimensions(image_bytes)
327
+ if image_bytes.startswith(_GIF87_MAGIC) or image_bytes.startswith(_GIF89_MAGIC):
328
+ return _gif_dimensions(image_bytes)
329
+ if image_bytes.startswith(_BMP_MAGIC):
330
+ return _bmp_dimensions(image_bytes)
331
+ return None
332
+
333
+
334
+ def scaled_dimensions_emu(
335
+ image_bytes: bytes,
336
+ width_emu: "int | None",
337
+ height_emu: "int | None",
338
+ ) -> "tuple[int, int] | None":
339
+ """Resolve the render size (in EMU) for ``add_picture``, mirroring
340
+ python-docx's ``Image.scaled_dimensions``.
341
+
342
+ - both width and height given -> use them verbatim.
343
+ - neither given -> the image's native size (from px + DPI).
344
+ - exactly one given -> compute the other to preserve aspect ratio.
345
+
346
+ Returns ``None`` only when a dimension must be derived but native
347
+ dimensions can't be sniffed — the caller then falls back to its
348
+ legacy fixed default.
349
+ """
350
+ if width_emu is not None and height_emu is not None:
351
+ return width_emu, height_emu
352
+ dims = sniff_image_dimensions(image_bytes)
353
+ if dims is None:
354
+ return None
355
+ px_w, px_h, dpi_x, dpi_y = dims
356
+ nat_w = int(round(px_w / dpi_x * _EMU_PER_INCH))
357
+ nat_h = int(round(px_h / dpi_y * _EMU_PER_INCH))
358
+ if width_emu is None and height_emu is None:
359
+ return nat_w, nat_h
360
+ if height_emu is None: # width supplied, scale height
361
+ scale = (float(width_emu) / float(nat_w)) if nat_w else 1.0
362
+ return width_emu, int(round(nat_h * scale))
363
+ # height supplied, scale width
364
+ scale = (float(height_emu) / float(nat_h)) if nat_h else 1.0
365
+ return int(round(nat_w * scale)), height_emu
366
+
367
+
368
+ __all__ = [
369
+ "load_image_bytes",
370
+ "sniff_content_type",
371
+ "sniff_image_dimensions",
372
+ "scaled_dimensions_emu",
373
+ ]
@@ -926,47 +926,6 @@ class RemoveHyperlink(Command):
926
926
  target: dict[str, Any] = dataclasses.field(default_factory=dict)
927
927
 
928
928
 
929
- # ---------------------------------------------------------------------------
930
- # Citations (Athena extension — source-citation markers. No python-docx
931
- # analog; mirrors the PPTX studio's AddSlideCitation / RemoveCitation. The
932
- # studio server derives the openable ``citation_string`` (Spaces URL) from
933
- # ``source_ref`` + ``source_anchor`` via @athenaintel/references.)
934
- # ---------------------------------------------------------------------------
935
-
936
-
937
- @dataclass
938
- class CreateCitation(Command):
939
- """Insert a source-citation marker.
940
-
941
- Either ``text`` inserts a new cited span at ``at`` (a Word-style
942
- superscript marker is appended after it), or ``target`` cites an existing
943
- range. ``source_ref`` / ``source_anchor`` describe the cited source
944
- (AssetReference / Anchor JSON, transited as dicts like ``SetImageAnchor``).
945
- ``label`` is the marker glyph (defaults to a running index server-side).
946
- ``client_entity_id`` is the client-minted citation id the applier honors
947
- as the citation's stable id (the ``docx-editor-citations`` Y.Map key).
948
- """
949
-
950
- source_ref: dict[str, Any] = dataclasses.field(default_factory=dict)
951
- source_anchor: dict[str, Any] | None = None
952
- display_value: str | None = None
953
- label: str | None = None
954
- text: str | None = None
955
- target: dict[str, Any] | None = None
956
- at: dict[str, Any] | None = None
957
- client_entity_id: str | None = None
958
-
959
-
960
- @dataclass
961
- class RemoveCitation(Command):
962
- """Remove a citation by id — strips the marker mark and, by default,
963
- deactivates the metadata record (``active: false``) so the removal is
964
- undoable. ``soft=False`` splices the record out entirely."""
965
-
966
- citation_id: str = ""
967
- soft: bool = True
968
-
969
-
970
929
  # ---------------------------------------------------------------------------
971
930
  # Fields (Athena extension — python-docx has no field-add API. PAGE,
972
931
  # NUMPAGES, DATE, TIME, FILENAME, AUTHOR, NUMWORDS, REF, SEQ, TOC. The
@@ -1755,9 +1714,6 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
1755
1714
  # Athena-extension response-bearing creates — each returns an
1756
1715
  # entity id the caller wraps in a proxy.
1757
1716
  "CreateHyperlink",
1758
- # CreateCitation eager-flushes so the marker's ``at`` offset stays
1759
- # correct relative to surrounding text inserts (mirrors CreateHyperlink).
1760
- "CreateCitation",
1761
1717
  "CreateField",
1762
1718
  "CreateBookmark",
1763
1719
  "CreateFootnote",
@@ -1827,8 +1783,6 @@ def _mark_athena_extension_command(cls: type, issue: "str | None", description:
1827
1783
  for _cls, _issue, _desc in [
1828
1784
  (CreateHyperlink, "python-docx#74", "Hyperlink add"),
1829
1785
  (RemoveHyperlink, "python-docx#74", "Hyperlink remove"),
1830
- (CreateCitation, None, "Source citation marker create"),
1831
- (RemoveCitation, None, "Source citation remove"),
1832
1786
  (CreateField, "python-docx#498", "Field insert"),
1833
1787
  (FieldsRefresh, "python-docx#498", "Field refresh"),
1834
1788
  (FieldsList, "python-docx#498", "Field enumeration"),
@@ -1960,8 +1914,6 @@ __all__ = [
1960
1914
  # semantics))
1961
1915
  "CreateHyperlink",
1962
1916
  "RemoveHyperlink",
1963
- "CreateCitation",
1964
- "RemoveCitation",
1965
1917
  "CreateField",
1966
1918
  "FieldsRefresh",
1967
1919
  "FieldsList",
@@ -217,8 +217,12 @@ class Comment:
217
217
  self._info["creatorName"] = value
218
218
 
219
219
  @property
220
- def initials(self) -> str:
221
- return str(self._info.get("initials") or "")
220
+ def initials(self) -> str | None:
221
+ # python-docx's CT_Comment.initials is OptionalAttribute(str), so
222
+ # an absent/None initials reads back as None (not ""), letting
223
+ # callers distinguish "no initials set" from "empty initials".
224
+ val = self._info.get("initials")
225
+ return None if val is None else str(val)
222
226
 
223
227
  @initials.setter
224
228
  def initials(self, value: str) -> None:
@@ -936,23 +936,6 @@ class Document:
936
936
  include_hyperlinks=include_hyperlinks,
937
937
  )
938
938
 
939
- @athena_extension(
940
- description="Document.remove_citation — remove a source citation by id.",
941
- )
942
- def remove_citation(self, *, citation_id: str, soft: bool = True) -> None:
943
- """Remove a source citation by id (Athena extension).
944
-
945
- Strips the citation marker and, by default (``soft=True``),
946
- deactivates the metadata record so the removal is undoable;
947
- ``soft=False`` splices the record out entirely. No-op if the id is
948
- unknown. No upstream python-docx analog.
949
- """
950
- from docx.commands import RemoveCitation
951
-
952
- run_sync(
953
- self._session.send_command(RemoveCitation(citation_id=citation_id, soft=soft))
954
- )
955
-
956
939
  @athena_extension(issue=179, description="Document.add_chart.")
957
940
  def add_chart(
958
941
  self,
@@ -1542,14 +1525,18 @@ class Document:
1542
1525
  #
1543
1526
  # For heading styles we still dispatch to add_heading, which uses
1544
1527
  # `doc.create.heading` (also canonical, takes `level`).
1545
- if style_str and style_str.lower().startswith("heading"):
1546
- try:
1547
- level: int = int(style_str.rsplit(" ", 1)[-1])
1548
- except ValueError:
1549
- level = 1
1550
- return self.add_heading(text=text, level=level)
1551
- if style_str and style_str.lower() == "title":
1552
- return self.add_heading(text=text, level=0)
1528
+ if style_str is not None:
1529
+ low: str = style_str.lower()
1530
+ # Only the canonical built-in heading ids (``Heading 1`` ..
1531
+ # ``Heading 9``) route to add_heading. A custom style whose
1532
+ # name merely starts with "heading" (e.g. "Heading Box",
1533
+ # "Heading Note") must be applied verbatim — the previous
1534
+ # loose ``startswith("heading")`` + parse-or-default-to-1
1535
+ # silently relabeled every such style as Heading 1.
1536
+ if low in {f"heading {n}" for n in range(1, 10)}:
1537
+ return self.add_heading(text=text, level=int(low.rsplit(" ", 1)[-1]))
1538
+ if low == "title":
1539
+ return self.add_heading(text=text, level=0)
1553
1540
 
1554
1541
  # python-docx parity: tabs and line breaks in `text` must
1555
1542
  # round-trip as Word ``<w:tab/>`` / ``<w:br/>`` elements, not
@@ -1742,9 +1729,9 @@ class Document:
1742
1729
  self._ensure_open()
1743
1730
  self._reset_list_chain()
1744
1731
  if not 0 <= level <= 9:
1745
- raise ValidationError(
1746
- f"level must be in 0..9; got {level}",
1747
- )
1732
+ # python-docx raises a builtin ``ValueError`` here (not an
1733
+ # SDK ValidationError) with this exact message.
1734
+ raise ValueError("level must be in range 0-9, got %d" % level)
1748
1735
 
1749
1736
  # python-docx parity: tabs/newlines in `text` must round-trip
1750
1737
  # as Word inlines, not literal characters. SuperDoc's
@@ -1814,9 +1801,18 @@ class Document:
1814
1801
 
1815
1802
  self._ensure_open()
1816
1803
  self._reset_list_chain()
1804
+ # Intentional deviation from python-docx (documented in CLAUDE.md):
1805
+ # upstream allows ``add_table(0, cols)`` to create an empty grid
1806
+ # you then populate with ``add_row()``, but SuperDoc's ProseMirror
1807
+ # table node requires at least one row and one column — a 0-row
1808
+ # table can't be represented — so we reject eagerly with a clear
1809
+ # error rather than failing opaquely on the wire.
1817
1810
  if rows < 1 or cols < 1:
1818
1811
  raise ValidationError(
1819
- f"rows and cols must be >= 1; got rows={rows} cols={cols}",
1812
+ f"rows and cols must be >= 1; got rows={rows} cols={cols}. "
1813
+ f"SuperDoc cannot represent a 0-row/0-col table — create "
1814
+ f"with the final dimensions (or 1x1, then add_row/"
1815
+ f"add_column).",
1820
1816
  )
1821
1817
 
1822
1818
  # The real param is `columns` (not `cols`). `at` is required to
@@ -1918,7 +1914,11 @@ class Document:
1918
1914
  server-side), or a ``data:`` URI. The URL and ``data:`` forms are
1919
1915
  Athena extensions beyond python-docx, which is path/stream-only.
1920
1916
  """
1921
- from docx._image_utils import load_image_bytes, sniff_content_type
1917
+ from docx._image_utils import (
1918
+ load_image_bytes,
1919
+ scaled_dimensions_emu,
1920
+ sniff_content_type,
1921
+ )
1922
1922
  from docx.shape import InlineShape
1923
1923
  from docx.text.run import _build_inline_shape_info
1924
1924
 
@@ -1948,8 +1948,32 @@ class Document:
1948
1948
  b64: str = base64.b64encode(image_bytes).decode("ascii")
1949
1949
  data_uri: str = f"data:{content_type};base64,{b64}"
1950
1950
 
1951
- w_px: float = _emu_to_px(width) if width is not None else 576.0
1952
- h_px: float = _emu_to_px(height) if height is not None else 432.0
1951
+ # python-docx parity: with no width/height the picture appears at
1952
+ # its native size (px / DPI); with exactly one dimension the other
1953
+ # is computed to preserve the aspect ratio. Only when the native
1954
+ # dimensions can't be sniffed (unknown/SVG/corrupt) do we keep the
1955
+ # legacy fixed 6" x 4.5" fallback.
1956
+ dims_emu = scaled_dimensions_emu(
1957
+ image_bytes,
1958
+ int(width) if width is not None else None,
1959
+ int(height) if height is not None else None,
1960
+ )
1961
+ if dims_emu is not None:
1962
+ w_emu, h_emu = dims_emu
1963
+ else:
1964
+ # Undecodable native size — legacy 6" x 4.5" fixed fallback
1965
+ # (576px / 432px @ 96 DPI) or the explicit dimension.
1966
+ w_emu = int(width) if width is not None else 5486400
1967
+ h_emu = int(height) if height is not None else 4114800
1968
+ # The ``create.image`` wire wants pixels (96 DPI); the returned
1969
+ # InlineShape's ``size`` is read back as POINTS (the getter wraps
1970
+ # it in ``Pt(...)``, matching the resize setter's ``unit: "pt"``),
1971
+ # so store the two units separately — otherwise ``shape.width``
1972
+ # reads back ~1.33x inflated (px interpreted as pt).
1973
+ w_px: float = _emu_to_px(w_emu)
1974
+ h_px: float = _emu_to_px(h_emu)
1975
+ w_pt: float = w_emu / 12700.0
1976
+ h_pt: float = h_emu / 12700.0
1953
1977
 
1954
1978
  # Pre-mint the client-side UUID so the create can buffer and
1955
1979
  # the InlineShape proxy hands the caller a stable id without
@@ -1965,7 +1989,7 @@ class Document:
1965
1989
  # InlineShape stores its id under ``_info["nodeId"]`` rather than
1966
1990
  # a top-level ``_node_id`` attribute, so route the flush-time
1967
1991
  # rewrite through a small setattr-adapter wrapper.
1968
- info = _build_inline_shape_info(result, width=w_px, height=h_px)
1992
+ info = _build_inline_shape_info(result, width=w_pt, height=h_pt)
1969
1993
  info["nodeId"] = client_node_id
1970
1994
  shape = InlineShape(session=self._session, info=info)
1971
1995
  buffer = self._buffer()
@@ -1998,6 +2022,22 @@ class Document:
1998
2022
  if start_type is not None:
1999
2023
  if isinstance(start_type, WD_SECTION_START):
2000
2024
  break_type = start_type.to_superdoc()
2025
+ elif isinstance(start_type, int):
2026
+ # python-docx's WD_SECTION_START members ARE ints
2027
+ # (NEW_PAGE == 2), so ``add_section(2)`` is valid upstream.
2028
+ # Athena's members carry SuperDoc string values, so map the
2029
+ # upstream integer constants to the matching member.
2030
+ _int_to_member = {
2031
+ 0: WD_SECTION_START.CONTINUOUS,
2032
+ 1: WD_SECTION_START.NEW_COLUMN,
2033
+ 2: WD_SECTION_START.NEW_PAGE,
2034
+ 3: WD_SECTION_START.EVEN_PAGE,
2035
+ 4: WD_SECTION_START.ODD_PAGE,
2036
+ }
2037
+ member = _int_to_member.get(int(start_type))
2038
+ if member is None:
2039
+ raise ValueError(f"{start_type!r} is not a valid WD_SECTION_START")
2040
+ break_type = member.to_superdoc()
2001
2041
  elif isinstance(start_type, str):
2002
2042
  # python-docx coerces strings through the enum, raising
2003
2043
  # ``ValueError`` for non-member values. Mirror that here
@@ -0,0 +1,30 @@
1
+ """Inline-shape enums — python-docx parity (``docx.enum.shape``).
2
+
3
+ Mirrors python-docx 1.2.0: ``WD_INLINE_SHAPE_TYPE`` is the canonical
4
+ enum and ``WD_INLINE_SHAPE`` is an alias of it (``WD_INLINE_SHAPE is
5
+ WD_INLINE_SHAPE_TYPE`` is ``True``). It is a plain :class:`enum.Enum`
6
+ (NOT an ``IntEnum``), so ``shape.type == WD_INLINE_SHAPE.PICTURE`` works
7
+ but ``shape.type == 3`` does not — exactly as upstream.
8
+
9
+ This is purely the return-value surface for :attr:`docx.shape.InlineShape.type`;
10
+ the SDK only models pictures, so ``type`` returns ``PICTURE``.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from enum import Enum
16
+
17
+
18
+ class WD_INLINE_SHAPE_TYPE(Enum):
19
+ """The type of an inline shape, e.g. ``PICTURE`` or ``CHART``."""
20
+
21
+ CHART = 12
22
+ LINKED_PICTURE = 4
23
+ NOT_IMPLEMENTED = -6
24
+ PICTURE = 3
25
+ SMART_ART = 15
26
+
27
+
28
+ # python-docx exposes ``WD_INLINE_SHAPE`` as an alias of the canonical
29
+ # ``WD_INLINE_SHAPE_TYPE`` (same object).
30
+ WD_INLINE_SHAPE = WD_INLINE_SHAPE_TYPE