athena-python-docx 0.16.0__tar.gz → 0.17.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 (361) hide show
  1. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/CLAUDE.md +15 -0
  2. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/PKG-INFO +1 -1
  3. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_buffer.py +16 -1
  5. athena_python_docx-0.17.0/docx/_image_utils.py +214 -0
  6. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/commands.py +5 -0
  7. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/document.py +29 -16
  8. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/paragraph.py +49 -7
  9. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/run.py +8 -16
  10. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/pyproject.toml +1 -1
  11. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/fake_session.py +7 -0
  12. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +1 -5
  13. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +1 -101
  14. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -5
  15. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -10
  16. athena_python_docx-0.17.0/tests/test_image_url_data_uri.py +283 -0
  17. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_iter_inner_content.py +65 -3
  18. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_ptc.py +40 -0
  19. athena_python_docx-0.16.0/docx/_image_utils.py +0 -90
  20. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/.gitignore +0 -0
  21. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/DOCX_EXEC_LAB.md +0 -0
  22. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/README.md +0 -0
  23. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_athena_extension.py +0 -0
  24. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_batching.py +0 -0
  25. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_execution.py +0 -0
  26. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_http.py +0 -0
  27. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_http_doc.py +0 -0
  28. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_postproc.py +0 -0
  29. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_ptc.py +0 -0
  30. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_table_styles.py +0 -0
  31. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/_timeouts.py +0 -0
  32. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/api.py +0 -0
  33. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/bookmarks.py +0 -0
  34. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/charts.py +0 -0
  35. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/client.py +0 -0
  36. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/comments.py +0 -0
  37. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/enum/__init__.py +0 -0
  38. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/enum/section.py +0 -0
  39. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/enum/style.py +0 -0
  40. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/enum/table.py +0 -0
  41. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/enum/text.py +0 -0
  42. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/errors.py +0 -0
  43. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/exceptions.py +0 -0
  44. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/fields.py +0 -0
  45. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/footnotes.py +0 -0
  46. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/math.py +0 -0
  47. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/opc/__init__.py +0 -0
  48. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/opc/coreprops.py +0 -0
  49. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/oxml/__init__.py +0 -0
  50. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/revisions.py +0 -0
  51. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/sdt.py +0 -0
  52. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/section.py +0 -0
  53. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/session.py +0 -0
  54. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/settings.py +0 -0
  55. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/shape.py +0 -0
  56. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/shared.py +0 -0
  57. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/styles/__init__.py +0 -0
  58. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/styles/style.py +0 -0
  59. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/styles/styles.py +0 -0
  60. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/table.py +0 -0
  61. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/__init__.py +0 -0
  62. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/font.py +0 -0
  63. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/hyperlink.py +0 -0
  64. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/pagebreak.py +0 -0
  65. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/parfmt.py +0 -0
  66. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/text/tabstops.py +0 -0
  67. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/toc.py +0 -0
  68. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/docx/typing.py +0 -0
  69. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/docx_exec_lab.py +0 -0
  70. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
  71. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
  72. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/docx_exec_lab_server.py +0 -0
  73. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/publish.sh +0 -0
  74. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/release.sh +0 -0
  75. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/round_trip_smoke.py +0 -0
  76. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/smoke_test_block_not_found.py +0 -0
  77. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/scripts/validate_find_replace_asset.py +0 -0
  78. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/__init__.py +0 -0
  79. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/conftest.py +0 -0
  80. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/METHODOLOGY.md +0 -0
  81. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/README.md +0 -0
  82. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/__init__.py +0 -0
  83. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/ab_probe_cases.py +0 -0
  84. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/ab_probe_runner.py +0 -0
  85. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/auto_gen_cases.py +0 -0
  86. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/binary_round_trip.py +0 -0
  87. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/cases.py +0 -0
  88. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/complex_cases.py +0 -0
  89. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/coverage_report.py +0 -0
  90. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/extract.py +0 -0
  91. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/extreme_cases.py +0 -0
  92. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/firm_templates/README.md +0 -0
  93. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/firm_templates/__init__.py +0 -0
  94. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/firm_templates/_runner.py +0 -0
  95. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/firm_templates/extractor.py +0 -0
  96. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  97. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  98. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/local_runner.py +0 -0
  99. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/mega_cases.py +0 -0
  100. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshot.py +0 -0
  101. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  102. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  103. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  104. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  105. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  106. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  107. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  108. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  109. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  110. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  111. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  112. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  113. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  114. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  115. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  116. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  117. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  118. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  119. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  120. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  121. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  122. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  123. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  124. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  125. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  126. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  127. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  128. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  129. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  130. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  131. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  132. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  133. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  134. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  135. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  136. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  137. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  138. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  139. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  140. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  141. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  142. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  143. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  144. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  145. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  146. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  147. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  148. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  149. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  150. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  151. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  152. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  153. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  154. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  155. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  156. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  157. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  158. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  159. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  160. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  161. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  162. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  163. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  164. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  165. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  166. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  167. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  168. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  169. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  170. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  171. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  172. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  173. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  174. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  175. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  176. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  177. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  178. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  179. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  180. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  181. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  182. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  183. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  184. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  185. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  186. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  187. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  188. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  189. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  190. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  191. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  192. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  193. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  194. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  195. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  196. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  197. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  198. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  199. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  200. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  201. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  202. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  203. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  204. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  205. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  206. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  207. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  208. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  209. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  210. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  211. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  212. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  213. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  214. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  215. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  216. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  217. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  218. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  219. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  220. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  221. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  222. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  223. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  224. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  225. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  226. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  227. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  228. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  229. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  230. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  231. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  232. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  233. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  234. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  235. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  236. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  237. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  238. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  239. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  240. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  241. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  242. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  243. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  244. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/ours_spec.json +0 -0
  245. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/parity_crawl.py +0 -0
  246. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/parity_diff.json +0 -0
  247. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/real_world_cases.py +0 -0
  248. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/round_trip_tests.py +0 -0
  249. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/runner.py +0 -0
  250. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/stock_spec.json +0 -0
  251. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  252. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/README.md +0 -0
  253. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/__init__.py +0 -0
  254. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/baseline_gaps.json +0 -0
  255. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/compare.py +0 -0
  256. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/intentional_deviations.json +0 -0
  257. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/introspect.py +0 -0
  258. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  259. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/reports/gap_report.json +0 -0
  260. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/run_parity.py +0 -0
  261. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/snapshots/athena_latest.json +0 -0
  262. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  263. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/parity/test_parity_gap.py +0 -0
  264. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_add_section_extract_items.py +0 -0
  265. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_athena_extensions_contract.py +0 -0
  266. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_athena_extensions_registry.py +0 -0
  267. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_batching_perf.py +0 -0
  268. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_block_not_found_error.py +0 -0
  269. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_buffer.py +0 -0
  270. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  271. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_add_table_not_supported.py +0 -0
  272. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
  273. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
  274. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_inner_format_stash.py +0 -0
  275. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_inner_run_format_stash.py +0 -0
  276. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_inner_run_guard.py +0 -0
  277. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_text_plain_fastpath.py +0 -0
  278. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_cell_text_replace_semantics.py +0 -0
  279. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_collapsed_range_format.py +0 -0
  280. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_command_dataclasses.py +0 -0
  281. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_commands.py +0 -0
  282. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_comments.py +0 -0
  283. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_document_asset_id_property.py +0 -0
  284. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_document_clear.py +0 -0
  285. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_document_create.py +0 -0
  286. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_document_create_from_template.py +0 -0
  287. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_document_factory_validation.py +0 -0
  288. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_docx_exec_lab.py +0 -0
  289. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_docx_exec_lab_server.py +0 -0
  290. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
  291. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_execution_scope.py +0 -0
  292. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_find_replace_session_open.py +0 -0
  293. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_http_transport.py +0 -0
  294. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_hyperlink_coalescing.py +0 -0
  295. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_insert_deferred.py +0 -0
  296. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_list_styles.py +0 -0
  297. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_merged_cell_secondary_slot.py +0 -0
  298. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_merged_cells.py +0 -0
  299. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_oxml_shim.py +0 -0
  300. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_paragraph_text_len_cache.py +0 -0
  301. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_parity_misc.py +0 -0
  302. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_parity_round2.py +0 -0
  303. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_partial_failure_cascade.py +0 -0
  304. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_phase_a_behavior.py +0 -0
  305. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_phase_b_headers_footers.py +0 -0
  306. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_phase_c_tables.py +0 -0
  307. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
  308. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
  309. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_postproc_ref_restore.py +0 -0
  310. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_pr19766_review_fixes.py +0 -0
  311. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_python_docx_api_parity.py +0 -0
  312. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_revisions.py +0 -0
  313. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  314. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_add_picture.py +0 -0
  315. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_add_run.py +0 -0
  316. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  317. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  318. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_comments_get.py +0 -0
  319. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_document_audit.py +0 -0
  320. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_document_element.py +0 -0
  321. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_enum_section.py +0 -0
  322. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_font_audit.py +0 -0
  323. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_header_footer.py +0 -0
  324. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_hyperlink.py +0 -0
  325. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_inline_shape.py +0 -0
  326. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  327. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_misc.py +0 -0
  328. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  329. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  330. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  331. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_parfmt.py +0 -0
  332. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  333. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_run_add_break.py +0 -0
  334. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  335. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_run_style.py +0 -0
  336. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  337. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_run_text.py +0 -0
  338. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_run_underline.py +0 -0
  339. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_section_audit.py +0 -0
  340. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_section_dimensions.py +0 -0
  341. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_section_onoff.py +0 -0
  342. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_settings.py +0 -0
  343. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_shared_audit.py +0 -0
  344. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_style.py +0 -0
  345. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_styles.py +0 -0
  346. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_table_audit.py +0 -0
  347. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_table_cell.py +0 -0
  348. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  349. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_silent_stub_table_layout.py +0 -0
  350. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_smoke_integration.py +0 -0
  351. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_style_acceptance.py +0 -0
  352. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_style_font.py +0 -0
  353. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_style_setters_contract.py +0 -0
  354. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_table_set_cell_perf.py +0 -0
  355. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_table_style_id_resolution.py +0 -0
  356. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_temporarily_unavailable.py +0 -0
  357. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_validate_find_replace_asset_script.py +0 -0
  358. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_wire_contract.py +0 -0
  359. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_xml_attr_guard.py +0 -0
  360. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/tests/test_zod_wire_contract.py +0 -0
  361. {athena_python_docx-0.16.0 → athena_python_docx-0.17.0}/uv.lock +0 -0
@@ -93,6 +93,21 @@ remaining work* for the exact wire-op shape needed to unblock each.
93
93
  These differ from stock python-docx because the SDK is asset-backed,
94
94
  not file-backed. Each is documented in the relevant docstring.
95
95
 
96
+ - **`add_picture` accepts an image URL or `data:` URI, not just a path
97
+ or stream.** Stock python-docx routes ``image_path_or_stream`` through
98
+ ``docx.image.image.Image.from_file``, which opens a ``str`` as a local
99
+ filesystem path and has no networking — so ``add_picture("https://…")``
100
+ raises ``FileNotFoundError``. Agents (and humans) routinely pass a
101
+ remote image URL, so ``Document.add_picture`` / ``Run.add_picture``
102
+ additionally fetch ``http(s)://`` URLs (via the already-required
103
+ ``requests`` dependency, 30s timeout) and decode ``data:[…;base64],…``
104
+ URIs. Local paths and file-like streams behave exactly as upstream.
105
+ The dispatch lives in ``docx._image_utils.load_image_bytes``; a failed
106
+ fetch/decode raises a ``ValueError`` at the call site rather than a
107
+ bare transport error. This is the only image-input deviation — the
108
+ resulting bytes still flow through the same magic-byte
109
+ ``sniff_content_type`` → base64 ``data:`` URI → ``create.image`` path.
110
+
96
111
  - **Built-in table style friendly names resolve to OOXML styleIds.**
97
112
  Word's ``styles.xml`` ships built-in table styles whose styleIds
98
113
  (``LightGrid-Accent1``, ``MediumShading1-Accent2``, …) differ from
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -6,7 +6,7 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.16.0"
9
+ __version__ = "0.17.0"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -130,6 +130,21 @@ if TYPE_CHECKING:
130
130
  _PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph", "CreateHeading", "CreateTable"})
131
131
 
132
132
 
133
+ def _should_emit_ptc(cmd: Command) -> bool:
134
+ """Whether ``cmd`` should produce a PTC sub-tool-card.
135
+
136
+ Only allow-listed block creates emit, and only when they target the
137
+ **body** story. Creates carrying an ``in_`` story locator (header,
138
+ footer, footnote, …) are skipped: the frontend locate affordance
139
+ scans the body editor only, so a header/footer card would highlight
140
+ an unrelated body block or nothing at all. Skip until the frontend
141
+ can route by ``args.in``.
142
+ """
143
+ if type(cmd).__name__ not in _PTC_EMIT_TOOLS:
144
+ return False
145
+ return getattr(cmd, "in_", None) is None
146
+
147
+
133
148
  def _ptc_emit_end_batch(cmds: list[Command], *, is_error: bool) -> None:
134
149
  """Emit a PTC ``end`` event for every cmd that received a ``begin``.
135
150
 
@@ -414,7 +429,7 @@ class CommandBuffer:
414
429
  # commands still flow through the buffer (and through the batched
415
430
  # HTTP POST), they just don't produce a sub-tool-card. ``emit_end``
416
431
  # is automatically skipped because no ``_ptc_call_id`` was set.
417
- if type(cmd).__name__ in _PTC_EMIT_TOOLS:
432
+ if _should_emit_ptc(cmd):
418
433
  try:
419
434
  cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
420
435
  type(cmd).__name__,
@@ -0,0 +1,214 @@
1
+ """Image format / content-type helpers shared by ``Run.add_picture``
2
+ and ``Document.add_picture``.
3
+
4
+ The previous implementations sniffed only by file extension, which
5
+ silently mistyped streams (default ``image/png``) and was wrong on
6
+ files with mismatched extensions (e.g. ``screenshot.png`` containing
7
+ JPEG bytes — common from OS screen-capture flows).
8
+
9
+ We sniff by **magic bytes** for the four formats SuperDoc accepts as
10
+ inline image data URIs (PNG, JPEG, GIF, WebP), with extension as a
11
+ secondary fallback for unknown / off-format streams.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import base64
17
+ from typing import BinaryIO
18
+
19
+ # Magic-byte signatures for the formats SuperDoc accepts inline.
20
+ # (mime, signature_bytes, signature_offset). Most are at offset 0;
21
+ # WebP needs a two-part check (RIFF header + WEBP marker at offset 8).
22
+ _PNG_MAGIC: bytes = b"\x89PNG\r\n\x1a\n"
23
+ _JPEG_MAGIC: bytes = b"\xff\xd8\xff"
24
+ _GIF87_MAGIC: bytes = b"GIF87a"
25
+ _GIF89_MAGIC: bytes = b"GIF89a"
26
+ _WEBP_RIFF: bytes = b"RIFF"
27
+ _WEBP_MARKER: bytes = b"WEBP"
28
+ _BMP_MAGIC: bytes = b"BM"
29
+ _TIFF_LE_MAGIC: bytes = b"II*\x00"
30
+ _TIFF_BE_MAGIC: bytes = b"MM\x00*"
31
+ _SVG_HINT_DECL: bytes = b"<?xml"
32
+ _SVG_HINT_TAG: bytes = b"<svg"
33
+
34
+
35
+ def sniff_content_type(image_bytes: bytes, *, fallback_path: str = "") -> str:
36
+ """Return a SuperDoc-acceptable image MIME for ``image_bytes``.
37
+
38
+ Magic-byte detection in priority order; if no signature matches,
39
+ fall back to the file extension on ``fallback_path``; if that
40
+ also fails, default to ``"image/png"`` (matches the prior
41
+ silent-default behavior so callers that previously got away with
42
+ it keep working).
43
+
44
+ Athena extension beyond python-docx 1.x: also detects SVG, BMP,
45
+ and TIFF. python-docx upstream supports PNG/JPEG/GIF/BMP/TIFF; SVG
46
+ and WEBP are missing from upstream (issues #651, #717, #1399).
47
+ """
48
+ if image_bytes.startswith(_PNG_MAGIC):
49
+ return "image/png"
50
+ if image_bytes.startswith(_JPEG_MAGIC):
51
+ return "image/jpeg"
52
+ if image_bytes.startswith(_GIF87_MAGIC) or image_bytes.startswith(_GIF89_MAGIC):
53
+ return "image/gif"
54
+ if (
55
+ len(image_bytes) >= 12
56
+ and image_bytes.startswith(_WEBP_RIFF)
57
+ and image_bytes[8:12] == _WEBP_MARKER
58
+ ):
59
+ return "image/webp"
60
+ if image_bytes.startswith(_BMP_MAGIC):
61
+ return "image/bmp"
62
+ if image_bytes.startswith(_TIFF_LE_MAGIC) or image_bytes.startswith(_TIFF_BE_MAGIC):
63
+ return "image/tiff"
64
+ # SVG: probe first ~1KB for either an XML declaration or a literal
65
+ # ``<svg`` tag. Case-insensitive on the tag because legitimate SVG
66
+ # files sometimes carry ``<SVG`` after author tools.
67
+ head = image_bytes[:1024]
68
+ if _SVG_HINT_TAG in head or _SVG_HINT_TAG in head.lower():
69
+ return "image/svg+xml"
70
+ if head.startswith(_SVG_HINT_DECL) and b"<svg" in head.lower():
71
+ return "image/svg+xml"
72
+
73
+ if fallback_path:
74
+ lower = fallback_path.lower()
75
+ if lower.endswith((".jpg", ".jpeg")):
76
+ return "image/jpeg"
77
+ if lower.endswith(".gif"):
78
+ return "image/gif"
79
+ if lower.endswith(".webp"):
80
+ return "image/webp"
81
+ if lower.endswith(".png"):
82
+ return "image/png"
83
+ if lower.endswith(".svg"):
84
+ return "image/svg+xml"
85
+ if lower.endswith(".bmp"):
86
+ return "image/bmp"
87
+ if lower.endswith((".tif", ".tiff")):
88
+ return "image/tiff"
89
+
90
+ return "image/png"
91
+
92
+
93
+ # Generous default so large remote images over a slow link still land, while a
94
+ # dead host fails the call instead of hanging the agent's code-exec forever.
95
+ _URL_FETCH_TIMEOUT_SECONDS: float = 30.0
96
+ # Hard cap so a giant (or accidentally non-image) URL can't balloon memory in
97
+ # the document-exec sandbox — the bytes get base64-encoded into the doc next.
98
+ _MAX_REMOTE_IMAGE_BYTES: int = 10 * 1024 * 1024 # 10 MiB
99
+
100
+
101
+ def load_image_bytes(image_path_or_stream: "str | BinaryIO") -> tuple[bytes, str]:
102
+ """Read raw image bytes from a local path, ``http(s)://`` URL, ``data:``
103
+ URI, or binary stream.
104
+
105
+ Returns ``(image_bytes, fallback_path)`` where ``fallback_path`` feeds the
106
+ extension-based MIME fallback in :func:`sniff_content_type` (empty when the
107
+ source carries no usable filename).
108
+
109
+ Athena extension beyond python-docx: upstream ``add_picture`` only accepts a
110
+ local path or file-like object — a ``str`` is always opened as a filesystem
111
+ path. Agents routinely pass an image URL or a ``data:`` URI, so the Athena
112
+ SDK additionally fetches ``http(s)://`` URLs (via ``requests``) and decodes
113
+ ``data:`` URIs here. Local paths and streams behave exactly as in
114
+ python-docx.
115
+
116
+ URL fetching runs inside the document-exec sandbox, which already executes
117
+ arbitrary agent-authored code (hence arbitrary outbound requests), so this
118
+ grants no network capability the caller lacks — it is not an SSRF allowlist
119
+ boundary. It does cap the download at ``_MAX_REMOTE_IMAGE_BYTES`` so an
120
+ oversized response can't exhaust sandbox memory.
121
+ """
122
+ if isinstance(image_path_or_stream, str):
123
+ descriptor = image_path_or_stream
124
+ # URI schemes are case-insensitive (RFC 3986): dispatch on a lowercased
125
+ # scheme, but fetch/open the original value unchanged.
126
+ scheme = descriptor.split(":", 1)[0].lower() if ":" in descriptor else ""
127
+ if scheme in ("http", "https"):
128
+ from urllib.parse import urlsplit
129
+
130
+ # Sniff fallback off the URL *path* only — a signed URL's query
131
+ # string (``?token=…``) would otherwise defeat the extension check.
132
+ return _fetch_image_url(descriptor), urlsplit(descriptor).path
133
+ if scheme == "data":
134
+ return _decode_image_data_uri(descriptor), ""
135
+ with open(descriptor, "rb") as f:
136
+ return f.read(), descriptor
137
+ # Duck-type the stream: python-docx accepts anything with ``.read()`` (it
138
+ # never isinstance-checks), so a BinaryIO that doesn't subclass io.IOBase
139
+ # must work too.
140
+ reader = getattr(image_path_or_stream, "read", None)
141
+ if callable(reader):
142
+ data = reader()
143
+ if not isinstance(data, (bytes, bytearray)):
144
+ raise TypeError(
145
+ f"Image stream .read() returned {type(data).__name__}, expected bytes",
146
+ )
147
+ return bytes(data), ""
148
+ raise TypeError(
149
+ "Expected a path str, http(s) URL, data: URI, or binary stream, got "
150
+ f"{type(image_path_or_stream).__name__}",
151
+ )
152
+
153
+
154
+ def _fetch_image_url(url: str) -> bytes:
155
+ """Download image bytes from an ``http(s)://`` URL, capped at
156
+ ``_MAX_REMOTE_IMAGE_BYTES`` (Athena extension — see
157
+ :func:`load_image_bytes`)."""
158
+ # Lazy import: ``requests`` is already a hard SDK dependency (HTTP transport),
159
+ # but keep this format helper importable without paying for it up front.
160
+ import requests
161
+ from urllib.parse import urlsplit, urlunsplit
162
+
163
+ def _redact(raw: str) -> str:
164
+ # Drop query + fragment so signed-URL tokens don't leak into errors/logs.
165
+ parts = urlsplit(raw)
166
+ return urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
167
+
168
+ try:
169
+ with requests.get(url, timeout=_URL_FETCH_TIMEOUT_SECONDS, stream=True) as resp:
170
+ resp.raise_for_status()
171
+ declared = resp.headers.get("content-length")
172
+ if declared is not None and declared.isdigit() and int(declared) > _MAX_REMOTE_IMAGE_BYTES:
173
+ raise ValueError(
174
+ f"add_picture image at {_redact(url)!r} declares {declared} bytes, "
175
+ f"over the {_MAX_REMOTE_IMAGE_BYTES}-byte cap"
176
+ )
177
+ chunks: list[bytes] = []
178
+ total = 0
179
+ for chunk in resp.iter_content(chunk_size=65536):
180
+ if not chunk:
181
+ continue
182
+ total += len(chunk)
183
+ if total > _MAX_REMOTE_IMAGE_BYTES:
184
+ raise ValueError(
185
+ f"add_picture image at {_redact(url)!r} exceeds the "
186
+ f"{_MAX_REMOTE_IMAGE_BYTES}-byte cap"
187
+ )
188
+ chunks.append(chunk)
189
+ return b"".join(chunks)
190
+ except requests.RequestException as exc:
191
+ raise ValueError(f"add_picture could not fetch image URL {_redact(url)!r}: {exc}") from exc
192
+
193
+
194
+ def _decode_image_data_uri(uri: str) -> bytes:
195
+ """Decode a ``data:[<mediatype>][;base64],<payload>`` URI to raw bytes
196
+ (Athena extension — see :func:`load_image_bytes`)."""
197
+ header, sep, payload = uri.partition(",")
198
+ if not sep or not payload:
199
+ raise ValueError("add_picture received a data: URI with no payload")
200
+ # ``;base64`` is a discrete, case-insensitive token (RFC 2397) — match it as
201
+ # a token, not a substring (so ``;notbase64x`` doesn't false-match), and use
202
+ # strict decoding so malformed base64 fails loudly instead of silently.
203
+ is_base64 = any(token.strip().lower() == "base64" for token in header.split(";")[1:])
204
+ try:
205
+ if is_base64:
206
+ return base64.b64decode(payload, validate=True)
207
+ from urllib.parse import unquote_to_bytes
208
+
209
+ return unquote_to_bytes(payload)
210
+ except Exception as exc:
211
+ raise ValueError(f"add_picture could not decode data: URI: {exc}") from exc
212
+
213
+
214
+ __all__ = ["load_image_bytes", "sniff_content_type"]
@@ -519,6 +519,11 @@ class ImagesGet(Command):
519
519
  class BlocksList(Command):
520
520
  node_types: list[str] | None = None
521
521
  include_text: bool | None = None
522
+ # Ask the server to inline each paragraph/heading block's run breakdown
523
+ # (``paragraph.inlines``) so ``Document.paragraphs`` can hydrate
524
+ # ``Paragraph.text``/``.runs``/``.style`` from one list instead of a
525
+ # ``get_node_by_id`` per paragraph. Serialized to ``includeRuns`` on the wire.
526
+ include_runs: bool | None = None
522
527
 
523
528
 
524
529
  @dataclass
@@ -15,7 +15,6 @@ Phase 1 supported methods:
15
15
  from __future__ import annotations
16
16
 
17
17
  import base64
18
- import io
19
18
  import sys
20
19
  from contextlib import contextmanager
21
20
  from typing import TYPE_CHECKING, BinaryIO, Generator, Iterator
@@ -359,12 +358,23 @@ class Document:
359
358
  # Include "heading" too so iterating over paragraphs covers both —
360
359
  # python-docx does not distinguish headings from paragraphs at the
361
360
  # Document.paragraphs level either (they're all Paragraph instances).
361
+ # ``includeRuns`` asks the server to inline each block's run breakdown
362
+ # (``paragraph.inlines``) so we can hydrate every returned proxy's
363
+ # ``.text`` / ``.runs`` / ``.style`` from this single list — instead of
364
+ # one ``get_node_by_id`` round-trip per paragraph per property. A
365
+ # read-heavy restyle loop over a large doc drops from ~N×3 round-trips
366
+ # to 1. See ``docx-studio/PERFORMANCE_BATCHING_ANALYSIS.md``.
362
367
  raw: object = run_sync(
363
368
  self._session.doc.blocks.list(
364
- {"nodeTypes": ["paragraph", "heading"], "includeText": False},
369
+ {
370
+ "nodeTypes": ["paragraph", "heading"],
371
+ "includeText": True,
372
+ "includeRuns": True,
373
+ },
365
374
  ),
366
375
  )
367
- # Real shape: {"blocks": [{"kind":"block","nodeType":..,"nodeId":..}], ...}
376
+ # Real shape: {"blocks": [{"kind":"block","nodeType":..,"nodeId":..,
377
+ # "styleId":..,"text":..,"paragraph":{"inlines":[..]}}], ...}
368
378
  if isinstance(raw, dict):
369
379
  blocks_obj: object = raw.get("blocks", [])
370
380
  blocks: list = blocks_obj if isinstance(blocks_obj, list) else []
@@ -380,11 +390,20 @@ class Document:
380
390
  if node_id:
381
391
  nt_raw = b.get("nodeType")
382
392
  nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
393
+ # Seed the read cache when the server inlined this block's runs.
394
+ # The snapshot is ``get_node_by_id``-shaped so
395
+ # ``Paragraph._node_info`` and the text/runs/style getters consume
396
+ # it unchanged. Blocks without an ``inlines`` payload (e.g.
397
+ # tables) fall back to live queries.
398
+ snapshot: dict | None = (
399
+ {"node": b} if isinstance(b.get("paragraph"), dict) else None
400
+ )
383
401
  out.append(
384
402
  Paragraph(
385
403
  session=self._session,
386
404
  node_id=node_id,
387
405
  node_type=nt,
406
+ node_info_snapshot=snapshot,
388
407
  ),
389
408
  )
390
409
  return out
@@ -1854,26 +1873,20 @@ class Document:
1854
1873
 
1855
1874
  Mirrors python-docx ``Document.add_picture`` — returns the new
1856
1875
  :class:`InlineShape` proxy.
1876
+
1877
+ ``image_path_or_stream`` accepts a local file path, a binary stream
1878
+ (e.g. ``io.BytesIO``), an ``http(s)://`` image URL (fetched
1879
+ server-side), or a ``data:`` URI. The URL and ``data:`` forms are
1880
+ Athena extensions beyond python-docx, which is path/stream-only.
1857
1881
  """
1858
- from docx._image_utils import sniff_content_type
1882
+ from docx._image_utils import load_image_bytes, sniff_content_type
1859
1883
  from docx.shape import InlineShape
1860
1884
  from docx.text.run import _build_inline_shape_info
1861
1885
 
1862
1886
  self._ensure_open()
1863
1887
  self._reset_list_chain()
1864
1888
 
1865
- image_bytes: bytes
1866
- fallback_path: str = ""
1867
- if isinstance(image_path_or_stream, str):
1868
- with open(image_path_or_stream, "rb") as f:
1869
- image_bytes = f.read()
1870
- fallback_path = image_path_or_stream
1871
- elif isinstance(image_path_or_stream, io.IOBase):
1872
- image_bytes = image_path_or_stream.read()
1873
- else:
1874
- raise TypeError(
1875
- f"Expected path str or binary stream, got {type(image_path_or_stream).__name__}",
1876
- )
1889
+ image_bytes, fallback_path = load_image_bytes(image_path_or_stream)
1877
1890
  # An empty payload (closed file, empty BytesIO, truncated stream
1878
1891
  # that's already been read to EOF) would silently produce a
1879
1892
  # ``data:image/png;base64,`` URI — SuperDoc accepts that and
@@ -226,6 +226,7 @@ class Paragraph:
226
226
  in_cell: bool = False,
227
227
  cell_ooxml_address: "tuple[str | None, int, int, int, int] | None" = None,
228
228
  cell: "object | None" = None,
229
+ node_info_snapshot: dict | None = None,
229
230
  ) -> None:
230
231
  self._session: "Session" = session
231
232
  self._node_id: str = node_id
@@ -272,6 +273,36 @@ class Paragraph:
272
273
  # — defensive for paragraph proxies built outside the _Cell
273
274
  # construction paths.
274
275
  self._cell: object | None = cell
276
+ # Prefetched ``get_node_by_id``-shaped snapshot
277
+ # (``{"node": {..., "paragraph": {"inlines": [...]}}}``) seeded by
278
+ # ``Document.paragraphs`` via the bulk ``blocks.list(includeRuns=True)``
279
+ # read. When present, ``text`` / ``runs`` / ``style`` read it instead of
280
+ # issuing a per-property ``get_node_by_id`` round-trip. It is a
281
+ # point-in-time snapshot (consistent with ``Document.paragraphs`` already
282
+ # returning a snapshot list) and is dropped by ``_invalidate_node_info``
283
+ # the moment this proxy mutates its own content.
284
+ self._node_info_snapshot: dict | None = node_info_snapshot
285
+
286
+ def _node_info(self) -> object:
287
+ """Return this paragraph's ``get_node_by_id``-shaped node info.
288
+
289
+ Served from the prefetch snapshot when one was seeded (one bulk
290
+ ``blocks.list`` for the whole document instead of one
291
+ ``get_node_by_id`` per ``.text`` / ``.runs`` / ``.style`` access);
292
+ otherwise a live query. ``_invalidate_node_info`` clears the snapshot
293
+ after a content mutation so the next read re-queries.
294
+ """
295
+ snapshot = self._node_info_snapshot
296
+ if snapshot is not None:
297
+ return snapshot
298
+ return run_sync(
299
+ self._session.doc.get_node_by_id({"id": self._node_id}),
300
+ )
301
+
302
+ def _invalidate_node_info(self) -> None:
303
+ """Drop the prefetched node snapshot after a content mutation, so the
304
+ next ``text`` / ``runs`` / ``style`` read reflects the new state."""
305
+ self._node_info_snapshot = None
275
306
 
276
307
  def _check_cell_inner(self, op: str, **format_props: Any) -> bool:
277
308
  """Return ``True`` if this paragraph is inside a table cell and
@@ -356,7 +387,7 @@ class Paragraph:
356
387
  @property
357
388
  def text(self) -> str:
358
389
  """Return the plain-text content of this paragraph."""
359
- return _node_text(self._session, self._node_id)
390
+ return _extract_text(self._node_info())
360
391
 
361
392
  @text.setter
362
393
  def text(self, value: str) -> None:
@@ -399,6 +430,9 @@ class Paragraph:
399
430
  # We just replaced the entire paragraph contents with ``value``;
400
431
  # the new text-address length is simply ``len(value)``.
401
432
  self._text_len = len(value)
433
+ # Content changed — drop the prefetched snapshot so a follow-up
434
+ # ``.text`` / ``.runs`` read reflects the replacement.
435
+ self._invalidate_node_info()
402
436
 
403
437
  @property
404
438
  def runs(self) -> list["Run"]:
@@ -413,9 +447,7 @@ class Paragraph:
413
447
  """
414
448
  from docx.text.run import Run
415
449
 
416
- info: object = run_sync(
417
- self._session.doc.get_node_by_id({"id": self._node_id}),
418
- )
450
+ info: object = self._node_info()
419
451
  inlines: list[dict] = _walk_inlines(info)
420
452
  runs: list["Run"] = []
421
453
  offset: int = 0
@@ -479,9 +511,7 @@ class Paragraph:
479
511
  from docx.styles.style import ParagraphStyle
480
512
  from docx.enum.style import WD_STYLE_TYPE
481
513
 
482
- info: object = run_sync(
483
- self._session.doc.get_node_by_id({"id": self._node_id}),
484
- )
514
+ info: object = self._node_info()
485
515
  if not isinstance(info, dict):
486
516
  return None
487
517
  node: object = info.get("node")
@@ -537,6 +567,10 @@ class Paragraph:
537
567
  },
538
568
  ),
539
569
  )
570
+ # Style changed — drop the prefetched snapshot so a follow-up ``.style``
571
+ # read reflects the new style instead of the stale prefetched value
572
+ # (the restyle-loop read-after-write this PR's prefetch targets).
573
+ self._invalidate_node_info()
540
574
 
541
575
  @property
542
576
  def alignment(self) -> "WD_ALIGN_PARAGRAPH | None":
@@ -1227,6 +1261,9 @@ class Paragraph:
1227
1261
  )
1228
1262
  offset += 1
1229
1263
  i = j + 1
1264
+ # Inserted content — drop the prefetched node snapshot so a follow-up
1265
+ # ``.text`` / ``.runs`` read on this proxy reflects the new runs.
1266
+ self._invalidate_node_info()
1230
1267
 
1231
1268
  @athena_extension(
1232
1269
  issue=74,
@@ -1397,6 +1434,11 @@ class Paragraph:
1397
1434
  )
1398
1435
  run_sync(self._session.send_command(cmd))
1399
1436
  self._text_len = end_offset
1437
+ # Appended a hyperlink run — drop the prefetched snapshot so a follow-up
1438
+ # ``.text`` / ``.runs`` read reflects the new content. This path fires
1439
+ # ``CreateHyperlink`` directly via ``send_command`` (it bypasses
1440
+ # ``_insert_run_text_segments``), so it needs its own invalidation.
1441
+ self._invalidate_node_info()
1400
1442
 
1401
1443
  run_dict = {"text": text, "hyperlink": {"url": url, "target": url}}
1402
1444
  return Hyperlink(
@@ -594,26 +594,18 @@ class Run:
594
594
 
595
595
  Returns an :class:`InlineShape` proxy for the newly-inserted
596
596
  image, matching python-docx ``Run.add_picture``.
597
+
598
+ ``image_path_or_stream`` accepts a local file path, a binary stream
599
+ (e.g. ``io.BytesIO``), an ``http(s)://`` image URL (fetched
600
+ server-side), or a ``data:`` URI. The URL and ``data:`` forms are
601
+ Athena extensions beyond python-docx, which is path/stream-only.
597
602
  """
598
- from docx._image_utils import sniff_content_type
603
+ from docx._image_utils import load_image_bytes, sniff_content_type
599
604
  from docx.document import _emu_to_px
600
605
  from docx.shape import InlineShape
601
606
  import base64
602
- import io
603
-
604
- image_bytes: bytes
605
- fallback_path: str = ""
606
- if isinstance(image_path_or_stream, str):
607
- with open(image_path_or_stream, "rb") as f:
608
- image_bytes = f.read()
609
- fallback_path = image_path_or_stream
610
- elif isinstance(image_path_or_stream, io.IOBase):
611
- image_bytes = image_path_or_stream.read()
612
- else:
613
- raise TypeError(
614
- "Expected path str or binary stream, "
615
- f"got {type(image_path_or_stream).__name__}",
616
- )
607
+
608
+ image_bytes, fallback_path = load_image_bytes(image_path_or_stream)
617
609
  # Empty payload would silently round-trip as
618
610
  # ``data:image/png;base64,`` (broken placeholder image). Fail
619
611
  # at the call site instead of at render time. Matches the
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-docx"
7
- version = "0.16.0"
7
+ version = "0.17.0"
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"
@@ -296,6 +296,7 @@ class FakeDocState:
296
296
 
297
297
  if op == "blocks.list":
298
298
  types = params.get("nodeTypes") or None
299
+ include_runs = bool(params.get("includeRuns"))
299
300
  out_blocks: list[dict] = []
300
301
  for b in self.blocks:
301
302
  if types and b.node_type not in types:
@@ -307,6 +308,12 @@ class FakeDocState:
307
308
  "styleId": b.style_id,
308
309
  "alignment": b.alignment,
309
310
  }
311
+ # Mirror the real server: when runs are requested, inline each
312
+ # paragraph/heading block's run breakdown so the SDK hydrates
313
+ # Paragraph.text/.runs/.style from this one list (no per-node read).
314
+ if include_runs and b.node_type in ("paragraph", "heading"):
315
+ entry["text"] = b.text
316
+ entry["paragraph"] = {"inlines": list(b.inlines)}
310
317
  out_blocks.append(entry)
311
318
  # Mirror real SuperDoc: when "table" is in the requested
312
319
  # nodeTypes, top-level tables come back too. The fake doesn't
@@ -3,9 +3,5 @@
3
3
  "create.paragraph",
4
4
  "create.paragraph",
5
5
  "create.paragraph",
6
- "blocks.list",
7
- "getNodeById",
8
- "getNodeById",
9
- "getNodeById",
10
- "getNodeById"
6
+ "blocks.list"
11
7
  ]