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