athena-python-docx 0.16.1__tar.gz → 0.18.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.
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/CLAUDE.md +15 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/PKG-INFO +1 -1
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/__init__.py +1 -1
- athena_python_docx-0.18.0/docx/_image_utils.py +214 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/document.py +17 -16
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/run.py +37 -21
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/pyproject.toml +1 -1
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/fake_session.py +9 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +1 -5
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +1 -101
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -5
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -10
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_inner_run_format_stash.py +79 -0
- athena_python_docx-0.18.0/tests/test_image_url_data_uri.py +283 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_iter_inner_content.py +25 -0
- athena_python_docx-0.16.1/docx/_image_utils.py +0 -90
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/.gitignore +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/DOCX_EXEC_LAB.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/README.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_http.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_http_doc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_postproc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/_timeouts.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/api.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/charts.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/client.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/commands.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/comments.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/errors.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/fields.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/math.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/section.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/session.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/settings.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/shape.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/shared.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/table.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/toc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/docx/typing.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_athena_extensions_registry.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.0}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.16.1 → athena_python_docx-0.18.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.
|
|
3
|
+
Version: 0.18.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>
|
|
@@ -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"]
|
|
@@ -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
|
|
@@ -387,10 +386,18 @@ class Document:
|
|
|
387
386
|
for b in blocks:
|
|
388
387
|
if not isinstance(b, dict):
|
|
389
388
|
continue
|
|
389
|
+
nt_raw = b.get("nodeType")
|
|
390
|
+
nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
|
|
391
|
+
# Tables belong to ``Document.tables``, never ``Document.paragraphs``
|
|
392
|
+
# (python-docx parity). The server honors the ``nodeTypes`` filter,
|
|
393
|
+
# but defend against an unfiltered response too: skip table blocks so
|
|
394
|
+
# they never surface here as Paragraph proxies (each would otherwise
|
|
395
|
+
# cost 3 live reads in a paragraph loop). Denylist, not allowlist, so
|
|
396
|
+
# we don't drop paragraph-like types we don't explicitly know.
|
|
397
|
+
if nt == "table":
|
|
398
|
+
continue
|
|
390
399
|
node_id: str = str(b.get("nodeId", ""))
|
|
391
400
|
if node_id:
|
|
392
|
-
nt_raw = b.get("nodeType")
|
|
393
|
-
nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
|
|
394
401
|
# Seed the read cache when the server inlined this block's runs.
|
|
395
402
|
# The snapshot is ``get_node_by_id``-shaped so
|
|
396
403
|
# ``Paragraph._node_info`` and the text/runs/style getters consume
|
|
@@ -1874,26 +1881,20 @@ class Document:
|
|
|
1874
1881
|
|
|
1875
1882
|
Mirrors python-docx ``Document.add_picture`` — returns the new
|
|
1876
1883
|
:class:`InlineShape` proxy.
|
|
1884
|
+
|
|
1885
|
+
``image_path_or_stream`` accepts a local file path, a binary stream
|
|
1886
|
+
(e.g. ``io.BytesIO``), an ``http(s)://`` image URL (fetched
|
|
1887
|
+
server-side), or a ``data:`` URI. The URL and ``data:`` forms are
|
|
1888
|
+
Athena extensions beyond python-docx, which is path/stream-only.
|
|
1877
1889
|
"""
|
|
1878
|
-
from docx._image_utils import sniff_content_type
|
|
1890
|
+
from docx._image_utils import load_image_bytes, sniff_content_type
|
|
1879
1891
|
from docx.shape import InlineShape
|
|
1880
1892
|
from docx.text.run import _build_inline_shape_info
|
|
1881
1893
|
|
|
1882
1894
|
self._ensure_open()
|
|
1883
1895
|
self._reset_list_chain()
|
|
1884
1896
|
|
|
1885
|
-
image_bytes
|
|
1886
|
-
fallback_path: str = ""
|
|
1887
|
-
if isinstance(image_path_or_stream, str):
|
|
1888
|
-
with open(image_path_or_stream, "rb") as f:
|
|
1889
|
-
image_bytes = f.read()
|
|
1890
|
-
fallback_path = image_path_or_stream
|
|
1891
|
-
elif isinstance(image_path_or_stream, io.IOBase):
|
|
1892
|
-
image_bytes = image_path_or_stream.read()
|
|
1893
|
-
else:
|
|
1894
|
-
raise TypeError(
|
|
1895
|
-
f"Expected path str or binary stream, got {type(image_path_or_stream).__name__}",
|
|
1896
|
-
)
|
|
1897
|
+
image_bytes, fallback_path = load_image_bytes(image_path_or_stream)
|
|
1897
1898
|
# An empty payload (closed file, empty BytesIO, truncated stream
|
|
1898
1899
|
# that's already been read to EOF) would silently produce a
|
|
1899
1900
|
# ``data:image/png;base64,`` URI — SuperDoc accepts that and
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
6
7
|
|
|
7
8
|
from docx._athena_extension import athena_extension
|
|
@@ -9,6 +10,14 @@ from docx._batching import run_sync
|
|
|
9
10
|
from docx.enum.text import WD_BREAK
|
|
10
11
|
from docx.shared import Length, Pt, RGBColor # noqa: F401
|
|
11
12
|
|
|
13
|
+
# docx-editor addresses cell-inner paragraphs by the cell's synthetic id
|
|
14
|
+
# (``tc:tableIndex:row:col``) because cell paragraphs carry no paraId. Its
|
|
15
|
+
# applier routes a ``FormatApply`` against such an id to a positional
|
|
16
|
+
# whole-cell mark, so cell-inner run formatting can apply live. SuperDoc cell
|
|
17
|
+
# ids are not ``tc:``-shaped, so this discriminator leaves the SuperDoc
|
|
18
|
+
# warn-and-skip path (post-export OOXML rewrite only) untouched.
|
|
19
|
+
_DOCX_EDITOR_CELL_ID_RE = re.compile(r"^tc:\d+:\d+:\d+$")
|
|
20
|
+
|
|
12
21
|
|
|
13
22
|
class PendingBreakDegradationWarning(UserWarning):
|
|
14
23
|
"""Emitted when ``Run.add_break`` is called with a break type that
|
|
@@ -594,26 +603,18 @@ class Run:
|
|
|
594
603
|
|
|
595
604
|
Returns an :class:`InlineShape` proxy for the newly-inserted
|
|
596
605
|
image, matching python-docx ``Run.add_picture``.
|
|
606
|
+
|
|
607
|
+
``image_path_or_stream`` accepts a local file path, a binary stream
|
|
608
|
+
(e.g. ``io.BytesIO``), an ``http(s)://`` image URL (fetched
|
|
609
|
+
server-side), or a ``data:`` URI. The URL and ``data:`` forms are
|
|
610
|
+
Athena extensions beyond python-docx, which is path/stream-only.
|
|
597
611
|
"""
|
|
598
|
-
from docx._image_utils import sniff_content_type
|
|
612
|
+
from docx._image_utils import load_image_bytes, sniff_content_type
|
|
599
613
|
from docx.document import _emu_to_px
|
|
600
614
|
from docx.shape import InlineShape
|
|
601
615
|
import base64
|
|
602
|
-
|
|
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
|
-
)
|
|
616
|
+
|
|
617
|
+
image_bytes, fallback_path = load_image_bytes(image_path_or_stream)
|
|
617
618
|
# Empty payload would silently round-trip as
|
|
618
619
|
# ``data:image/png;base64,`` (broken placeholder image). Fail
|
|
619
620
|
# at the call site instead of at render time. Matches the
|
|
@@ -978,12 +979,27 @@ class Run:
|
|
|
978
979
|
# has text — see ``_pending_inline`` in ``__init__`` for the
|
|
979
980
|
# python-docx parity rationale.
|
|
980
981
|
if getattr(self, "_in_cell", False):
|
|
981
|
-
#
|
|
982
|
-
#
|
|
983
|
-
#
|
|
984
|
-
#
|
|
985
|
-
# the formatting still lands in the exported ``.docx``.
|
|
982
|
+
# Stash for the post-export OOXML rewrite FIRST so the formatting
|
|
983
|
+
# lands in the exported ``.docx`` regardless of engine (and is the
|
|
984
|
+
# only path on the legacy SuperDoc engine, which rejects
|
|
985
|
+
# ``doc.format.apply`` for paragraphs nested inside ``tableCell``).
|
|
986
986
|
self._stash_cell_inner_run_format(kwargs)
|
|
987
|
+
# docx-editor CAN apply cell-inner formatting live: it addresses
|
|
988
|
+
# the cell paragraph by the cell's synthetic id and routes a
|
|
989
|
+
# ``FormatApply`` against that id to a positional whole-cell mark.
|
|
990
|
+
# Ship it so collaborators see the change immediately instead of
|
|
991
|
+
# only at export. The ``tc:``-shape discriminator keeps the
|
|
992
|
+
# SuperDoc warn-and-skip path intact (its cell ids differ).
|
|
993
|
+
block_id = getattr(self, "_block_id", "")
|
|
994
|
+
if isinstance(block_id, str) and _DOCX_EDITOR_CELL_ID_RE.match(
|
|
995
|
+
block_id,
|
|
996
|
+
):
|
|
997
|
+
run_sync(
|
|
998
|
+
self._session.doc.format.apply(
|
|
999
|
+
{"target": self._text_range, "inline": kwargs},
|
|
1000
|
+
),
|
|
1001
|
+
)
|
|
1002
|
+
return
|
|
987
1003
|
self._check_cell_inner(
|
|
988
1004
|
"font/format (bold/italic/font.size/etc.)",
|
|
989
1005
|
)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-docx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.18.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"
|
|
@@ -121,6 +121,11 @@ class FakeDocState:
|
|
|
121
121
|
self.sections: list[_Section] = [_Section("sec_1")]
|
|
122
122
|
self.images: list[dict] = []
|
|
123
123
|
self.calls: list[tuple[str, dict]] = []
|
|
124
|
+
# When True, ``blocks.list`` ignores the requested ``nodeTypes`` and
|
|
125
|
+
# returns every block (tables included), simulating the docx-editor
|
|
126
|
+
# server before it honored the filter. Lets tests verify the SDK's own
|
|
127
|
+
# table denylist in ``Document.paragraphs``.
|
|
128
|
+
self.blocks_list_ignore_node_types: bool = False
|
|
124
129
|
|
|
125
130
|
def _new_id(self, prefix: str) -> str:
|
|
126
131
|
return f"{prefix}_{next(self._ids)}"
|
|
@@ -296,6 +301,10 @@ class FakeDocState:
|
|
|
296
301
|
|
|
297
302
|
if op == "blocks.list":
|
|
298
303
|
types = params.get("nodeTypes") or None
|
|
304
|
+
# Simulate the pre-fix docx-editor server that ignored the filter and
|
|
305
|
+
# returned every block (tables included), to exercise the SDK denylist.
|
|
306
|
+
if self.blocks_list_ignore_node_types:
|
|
307
|
+
types = None
|
|
299
308
|
include_runs = bool(params.get("includeRuns"))
|
|
300
309
|
out_blocks: list[dict] = []
|
|
301
310
|
for b in self.blocks:
|
|
@@ -99,105 +99,5 @@
|
|
|
99
99
|
"create.paragraph",
|
|
100
100
|
"create.heading",
|
|
101
101
|
"create.paragraph",
|
|
102
|
-
"blocks.list"
|
|
103
|
-
"getNodeById",
|
|
104
|
-
"getNodeById",
|
|
105
|
-
"getNodeById",
|
|
106
|
-
"getNodeById",
|
|
107
|
-
"getNodeById",
|
|
108
|
-
"getNodeById",
|
|
109
|
-
"getNodeById",
|
|
110
|
-
"getNodeById",
|
|
111
|
-
"getNodeById",
|
|
112
|
-
"getNodeById",
|
|
113
|
-
"getNodeById",
|
|
114
|
-
"getNodeById",
|
|
115
|
-
"getNodeById",
|
|
116
|
-
"getNodeById",
|
|
117
|
-
"getNodeById",
|
|
118
|
-
"getNodeById",
|
|
119
|
-
"getNodeById",
|
|
120
|
-
"getNodeById",
|
|
121
|
-
"getNodeById",
|
|
122
|
-
"getNodeById",
|
|
123
|
-
"getNodeById",
|
|
124
|
-
"getNodeById",
|
|
125
|
-
"getNodeById",
|
|
126
|
-
"getNodeById",
|
|
127
|
-
"getNodeById",
|
|
128
|
-
"getNodeById",
|
|
129
|
-
"getNodeById",
|
|
130
|
-
"getNodeById",
|
|
131
|
-
"getNodeById",
|
|
132
|
-
"getNodeById",
|
|
133
|
-
"getNodeById",
|
|
134
|
-
"getNodeById",
|
|
135
|
-
"getNodeById",
|
|
136
|
-
"getNodeById",
|
|
137
|
-
"getNodeById",
|
|
138
|
-
"getNodeById",
|
|
139
|
-
"getNodeById",
|
|
140
|
-
"getNodeById",
|
|
141
|
-
"getNodeById",
|
|
142
|
-
"getNodeById",
|
|
143
|
-
"getNodeById",
|
|
144
|
-
"getNodeById",
|
|
145
|
-
"getNodeById",
|
|
146
|
-
"getNodeById",
|
|
147
|
-
"getNodeById",
|
|
148
|
-
"getNodeById",
|
|
149
|
-
"getNodeById",
|
|
150
|
-
"getNodeById",
|
|
151
|
-
"getNodeById",
|
|
152
|
-
"getNodeById",
|
|
153
|
-
"getNodeById",
|
|
154
|
-
"getNodeById",
|
|
155
|
-
"getNodeById",
|
|
156
|
-
"getNodeById",
|
|
157
|
-
"getNodeById",
|
|
158
|
-
"getNodeById",
|
|
159
|
-
"getNodeById",
|
|
160
|
-
"getNodeById",
|
|
161
|
-
"getNodeById",
|
|
162
|
-
"getNodeById",
|
|
163
|
-
"getNodeById",
|
|
164
|
-
"getNodeById",
|
|
165
|
-
"getNodeById",
|
|
166
|
-
"getNodeById",
|
|
167
|
-
"getNodeById",
|
|
168
|
-
"getNodeById",
|
|
169
|
-
"getNodeById",
|
|
170
|
-
"getNodeById",
|
|
171
|
-
"getNodeById",
|
|
172
|
-
"getNodeById",
|
|
173
|
-
"getNodeById",
|
|
174
|
-
"getNodeById",
|
|
175
|
-
"getNodeById",
|
|
176
|
-
"getNodeById",
|
|
177
|
-
"getNodeById",
|
|
178
|
-
"getNodeById",
|
|
179
|
-
"getNodeById",
|
|
180
|
-
"getNodeById",
|
|
181
|
-
"getNodeById",
|
|
182
|
-
"getNodeById",
|
|
183
|
-
"getNodeById",
|
|
184
|
-
"getNodeById",
|
|
185
|
-
"getNodeById",
|
|
186
|
-
"getNodeById",
|
|
187
|
-
"getNodeById",
|
|
188
|
-
"getNodeById",
|
|
189
|
-
"getNodeById",
|
|
190
|
-
"getNodeById",
|
|
191
|
-
"getNodeById",
|
|
192
|
-
"getNodeById",
|
|
193
|
-
"getNodeById",
|
|
194
|
-
"getNodeById",
|
|
195
|
-
"getNodeById",
|
|
196
|
-
"getNodeById",
|
|
197
|
-
"getNodeById",
|
|
198
|
-
"getNodeById",
|
|
199
|
-
"getNodeById",
|
|
200
|
-
"getNodeById",
|
|
201
|
-
"getNodeById",
|
|
202
|
-
"getNodeById"
|
|
102
|
+
"blocks.list"
|
|
203
103
|
]
|
|
@@ -20,23 +20,18 @@
|
|
|
20
20
|
"insert",
|
|
21
21
|
"insert",
|
|
22
22
|
"blocks.list",
|
|
23
|
-
"getNodeById",
|
|
24
23
|
"format.apply",
|
|
25
24
|
"format.apply",
|
|
26
25
|
"format.apply",
|
|
27
|
-
"getNodeById",
|
|
28
26
|
"format.apply",
|
|
29
27
|
"format.apply",
|
|
30
28
|
"format.apply",
|
|
31
|
-
"getNodeById",
|
|
32
29
|
"format.apply",
|
|
33
30
|
"format.apply",
|
|
34
31
|
"format.apply",
|
|
35
|
-
"getNodeById",
|
|
36
32
|
"format.apply",
|
|
37
33
|
"format.apply",
|
|
38
34
|
"format.apply",
|
|
39
|
-
"getNodeById",
|
|
40
35
|
"format.apply",
|
|
41
36
|
"format.apply",
|
|
42
37
|
"format.apply"
|
|
@@ -69,15 +69,5 @@
|
|
|
69
69
|
"getNodeById",
|
|
70
70
|
"getNodeById",
|
|
71
71
|
"getNodeById",
|
|
72
|
-
"getNodeById",
|
|
73
|
-
"getNodeById",
|
|
74
|
-
"getNodeById",
|
|
75
|
-
"getNodeById",
|
|
76
|
-
"getNodeById",
|
|
77
|
-
"getNodeById",
|
|
78
|
-
"getNodeById",
|
|
79
|
-
"getNodeById",
|
|
80
|
-
"getNodeById",
|
|
81
|
-
"getNodeById",
|
|
82
72
|
"getNodeById"
|
|
83
73
|
]
|