athena-python-docx 0.21.8__tar.gz → 0.23.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.21.8/CLAUDE.md → athena_python_docx-0.23.0/AGENTS.md +50 -3
- athena_python_docx-0.23.0/CLAUDE.md +5 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/DOCX_EXEC_LAB.md +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/PKG-INFO +2 -2
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/README.md +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_buffer.py +2 -2
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_citations.py +122 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_http.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_http_doc.py +2 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_postproc.py +21 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_ptc.py +74 -8
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_timeouts.py +3 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/client.py +5 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/commands.py +23 -2
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/document.py +39 -70
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/table.py +78 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/paragraph.py +20 -10
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/run.py +3 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/pyproject.toml +1 -1
- athena_python_docx-0.23.0/scripts/release.sh +14 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/README.md +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/__init__.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/ab_probe_cases.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/binary_round_trip.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/cases.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/fake_session.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/local_runner.py +1 -1
- athena_python_docx-0.23.0/tests/test_athena_extension_creates_session_open.py +64 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_athena_extensions_registry.py +2 -0
- athena_python_docx-0.23.0/tests/test_citations.py +620 -0
- athena_python_docx-0.23.0/tests/test_document_clear.py +277 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_execution_scope.py +1 -1
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_ptc.py +105 -1
- athena_python_docx-0.23.0/tests/test_run_mark_inheritance.py +88 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_wire_contract.py +1 -0
- athena_python_docx-0.21.8/scripts/publish.sh +0 -37
- athena_python_docx-0.21.8/scripts/release.sh +0 -84
- athena_python_docx-0.21.8/tests/test_citations.py +0 -317
- athena_python_docx-0.21.8/tests/test_document_clear.py +0 -132
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/.gitignore +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_references.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/api.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/charts.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/comments.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/enum/shape.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/errors.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/fields.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/math.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/section.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/session.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/settings.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/shape.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/shared.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/toc.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/docx/typing.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/dump_wire_fixtures.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_fastpath_runs.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_image_url_data_uri.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_positional_cell_id.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_run_add_picture_anchor.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.21.8 → athena_python_docx-0.23.0}/uv.lock +0 -0
|
@@ -367,8 +367,8 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
367
367
|
list metadata. Routes through `NumberingGet`.
|
|
368
368
|
|
|
369
369
|
- **`Document.to_pdf(*, quality=None, destination=None,
|
|
370
|
-
include_revisions=False) -> bytes`** — render to PDF
|
|
371
|
-
|
|
370
|
+
include_revisions=False) -> bytes`** — render to PDF. python-docx
|
|
371
|
+
issue #113 was closed as
|
|
372
372
|
wontfix upstream but the request keeps recurring. Routes through
|
|
373
373
|
`ExportPDF` (DOCX Studio handles the actual conversion).
|
|
374
374
|
|
|
@@ -447,6 +447,53 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
447
447
|
only when you already have a DOCX editor anchor id. For normal writing,
|
|
448
448
|
cite the ``Paragraph`` proxy you just created or found.
|
|
449
449
|
|
|
450
|
+
- **Table-cell citations** (0.22.0+): **`_Cell.add_citation(source, *,
|
|
451
|
+
anchor=None, citation_string=None, excerpt=None, display_value=None)
|
|
452
|
+
-> str`** + **`_Cell.clear_citations()`**, and ``Paragraph.add_citation``
|
|
453
|
+
/ ``Paragraph.clear_citations`` on cell paragraphs (before 0.22.0 they
|
|
454
|
+
raised ``ValueError`` "top-level DOCX paragraphs only"). The SDK models a
|
|
455
|
+
cell as one paragraph addressed by the synthetic cell id
|
|
456
|
+
(``tc:table:row:col``; ``cell.paragraphs[0]`` stands for the whole cell),
|
|
457
|
+
so a cell citation sends that id. The docx-studio applier resolves it to
|
|
458
|
+
the cell's paragraph ids: one paragraph with text → ``pm_node``, several
|
|
459
|
+
→ ``pm_node_range`` from the first to the last; a cell paragraph with no
|
|
460
|
+
paraId (or a repeated one) gets a new one. A horizontally merged cell
|
|
461
|
+
resolves to its master cell from any grid position it covers. A
|
|
462
|
+
paragraph that ``cell.add_paragraph(text)`` returned already carries its
|
|
463
|
+
own paraId and is cited alone. To cite one paragraph of a cell (or a
|
|
464
|
+
paragraph in a nested table), pass the ``data-id`` a Word read shows for
|
|
465
|
+
it: ``doc.add_citation(..., destination_anchor=PmNodeAnchor(node_id=...))``.
|
|
466
|
+
``_Cell.clear_citations()`` removes every citation inside the cell.
|
|
467
|
+
Write the cell text first, then cite it:
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
table = doc.add_table(rows=2, cols=2)
|
|
471
|
+
table.cell(1, 0).text = "Revenue"
|
|
472
|
+
table.cell(1, 1).text = "$12.4M"
|
|
473
|
+
table.cell(1, 1).add_citation(
|
|
474
|
+
"/dashboard/spaces/?asset_ids=asset_sheet&type=spreadsheet"
|
|
475
|
+
"&anchor=sheet_range&sheet_id=0&range=C4",
|
|
476
|
+
excerpt="$12.4M",
|
|
477
|
+
)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Needs a docx-studio API that resolves cell ids (the same release train);
|
|
481
|
+
an older API stores the record under the cell id, where nothing renders
|
|
482
|
+
it and the next edit retires it.
|
|
483
|
+
|
|
484
|
+
- **Image region citations**: cite a region of an image asset with
|
|
485
|
+
``AssetReference(id=<image>)`` + ``PixelRangeAnchor(x=, y=, width=,
|
|
486
|
+
height=)`` in source image pixels, or with a
|
|
487
|
+
``/dashboard/spaces/?asset_ids=<image>&type=image&anchor=pixel_range&x=…&y=…&width=…&height=…``
|
|
488
|
+
citation string. DOCX Studio stores ``type=image`` and keeps ``excerpt``
|
|
489
|
+
out of the URL (the record keeps it as the span of the cited paragraph to
|
|
490
|
+
highlight). Since 0.23.0, ``add_citation`` raises ``ValueError`` for the
|
|
491
|
+
image citations that chat and Sheets reject (agora
|
|
492
|
+
``validate_image_citation_url``): an image URL with ``excerpt`` or
|
|
493
|
+
``#:~:text=`` and no anchor, a non-region anchor, a region anchor without
|
|
494
|
+
x, y, width and height, and an ``AssetMeta(asset_type="image")`` source
|
|
495
|
+
with a non-region anchor or with an excerpt and no anchor.
|
|
496
|
+
|
|
450
497
|
- **`Document.export_docx(path=None, *, include_revisions=False)
|
|
451
498
|
-> bytes`** (0.11.4+) — export the document's current state as
|
|
452
499
|
``.docx`` bytes, optionally writing to ``path``. Closes the gap
|
|
@@ -769,7 +816,7 @@ This is a **thin HTTP client** that mimics the sync python-docx API.
|
|
|
769
816
|
drain pending ops before running; the 100 ms idle timer fires on
|
|
770
817
|
inactivity so collab updates stream live; `Document.save()` and
|
|
771
818
|
the context-manager exit drain explicitly; `docx.flush_all()` is
|
|
772
|
-
the
|
|
819
|
+
the executor-postamble hook that flushes every live buffer in the
|
|
773
820
|
process.
|
|
774
821
|
- Execution scope: executor sandboxes may keep Python globals
|
|
775
822
|
between tool calls so imports, constants, and helper functions can be
|
|
@@ -6,7 +6,7 @@ shortens the debug loop by running local SDK code directly against a
|
|
|
6
6
|
chosen DOCX Studio API, capturing stdout/stderr/errors/timings, and
|
|
7
7
|
optionally exporting the result to `.docx` for assertions.
|
|
8
8
|
|
|
9
|
-
Use this before waiting on a full Agent ->
|
|
9
|
+
Use this before waiting on a full Agent -> executor sandbox -> preview cycle when
|
|
10
10
|
debugging SDK behavior such as large-document replacement, table fills,
|
|
11
11
|
unsupported OOXML internals, or export fidelity.
|
|
12
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: Drop-in replacement for python-docx that connects to Athena's DOCX Studio collaborative document stack
|
|
5
5
|
Project-URL: Homepage, https://athenaintelligence.ai
|
|
6
6
|
Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
|
|
@@ -76,7 +76,7 @@ an Olympus Word document embed preview pane. See `DOCX_EXEC_LAB.md`.
|
|
|
76
76
|
|
|
77
77
|
## Environment variables
|
|
78
78
|
|
|
79
|
-
Required when connecting to DOCX Studio (set by Athena backend
|
|
79
|
+
Required when connecting to DOCX Studio (set by the Athena backend in the `execute_word_document_code` executor sandbox):
|
|
80
80
|
|
|
81
81
|
- `ATHENA_DOCX_BASE_URL` — DOCX Studio API base URL
|
|
82
82
|
- `ATHENA_DOCX_API_KEY` — Athena API key or service-issued DOCX Studio token
|
|
@@ -51,7 +51,7 @@ an Olympus Word document embed preview pane. See `DOCX_EXEC_LAB.md`.
|
|
|
51
51
|
|
|
52
52
|
## Environment variables
|
|
53
53
|
|
|
54
|
-
Required when connecting to DOCX Studio (set by Athena backend
|
|
54
|
+
Required when connecting to DOCX Studio (set by the Athena backend in the `execute_word_document_code` executor sandbox):
|
|
55
55
|
|
|
56
56
|
- `ATHENA_DOCX_BASE_URL` — DOCX Studio API base URL
|
|
57
57
|
- `ATHENA_DOCX_API_KEY` — Athena API key or service-issued DOCX Studio token
|
|
@@ -20,7 +20,7 @@ guard the pending list with an RLock and treat the timer flush as a
|
|
|
20
20
|
best-effort coalesce — if the loop thread races us we just flush twice
|
|
21
21
|
(or zero times; the next loop-thread call drains it).
|
|
22
22
|
|
|
23
|
-
`flush_all` is a process-wide hook used by
|
|
23
|
+
`flush_all` is a process-wide hook used by Athena's sandbox executor to
|
|
24
24
|
make sure pending writes hit Keryx before the sandbox is suspended. It walks
|
|
25
25
|
a weak-ref registry so dead Workbook instances don't keep their buffers
|
|
26
26
|
alive.
|
|
@@ -199,7 +199,7 @@ def _unregister(buffer: "CommandBuffer") -> None:
|
|
|
199
199
|
def flush_all(*, strict: bool = False) -> None:
|
|
200
200
|
"""Flush every live CommandBuffer in this process.
|
|
201
201
|
|
|
202
|
-
Used by
|
|
202
|
+
Used by Athena's sandbox executor after user code returns, so
|
|
203
203
|
buffered mutations make it to Keryx before the sandbox is suspended.
|
|
204
204
|
Safe to call when no Buffers exist (no-op).
|
|
205
205
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import uuid
|
|
6
|
+
from collections.abc import Mapping
|
|
6
7
|
from typing import Any
|
|
7
8
|
from urllib.parse import parse_qs, urlparse
|
|
8
9
|
|
|
@@ -34,6 +35,87 @@ def source_ref_to_dict(source: AssetReference) -> dict[str, Any]:
|
|
|
34
35
|
return source.to_dict()
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
_IMAGE_REGION_ANCHOR_TYPES: tuple[str, ...] = ("pixel_range", "rect")
|
|
39
|
+
_IMAGE_REGION_FIELDS: tuple[str, ...] = ("x", "y", "width", "height")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_number(value: object) -> bool:
|
|
43
|
+
"""True for what agora's anchor parser reads as a number (``float(...)``)."""
|
|
44
|
+
if isinstance(value, bool):
|
|
45
|
+
return False
|
|
46
|
+
if isinstance(value, (int, float)):
|
|
47
|
+
return True
|
|
48
|
+
if isinstance(value, str):
|
|
49
|
+
try:
|
|
50
|
+
float(value)
|
|
51
|
+
except ValueError:
|
|
52
|
+
return False
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_image_citation(
|
|
58
|
+
*,
|
|
59
|
+
anchor_type: str | None,
|
|
60
|
+
has_excerpt: bool,
|
|
61
|
+
region: Mapping[str, object] | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Reject an image citation that chat and Sheets reject.
|
|
64
|
+
|
|
65
|
+
The same rules as agora's ``validate_citation_url`` for an image: an
|
|
66
|
+
image is cited by a region (``anchor=pixel_range`` with numeric x, y,
|
|
67
|
+
width and height in source image pixels), never by a page, a text anchor
|
|
68
|
+
or an excerpt. ``region`` holds the x, y, width and height values of a
|
|
69
|
+
region anchor (``None`` for a missing one).
|
|
70
|
+
"""
|
|
71
|
+
if anchor_type and anchor_type not in _IMAGE_REGION_ANCHOR_TYPES:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"An image citation cannot use anchor={anchor_type}. Cite a region "
|
|
74
|
+
"of the image: anchor=pixel_range with x, y, width and height in "
|
|
75
|
+
"source image pixels (PixelRangeAnchor)."
|
|
76
|
+
)
|
|
77
|
+
if not anchor_type and has_excerpt:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"An image citation cannot use an excerpt or a #:~:text= fragment: "
|
|
80
|
+
"an image has no text to find. Cite a region of the image: "
|
|
81
|
+
"anchor=pixel_range with x, y, width and height in source image "
|
|
82
|
+
"pixels (PixelRangeAnchor), and put the label in display_value."
|
|
83
|
+
)
|
|
84
|
+
if not anchor_type or region is None:
|
|
85
|
+
return
|
|
86
|
+
missing = [field for field in _IMAGE_REGION_FIELDS if region.get(field) is None]
|
|
87
|
+
if missing:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"An image citation with anchor={anchor_type} needs x, y, width "
|
|
90
|
+
f"and height. Missing: {', '.join(missing)}."
|
|
91
|
+
)
|
|
92
|
+
not_numbers = [field for field in _IMAGE_REGION_FIELDS if not _is_number(region[field])]
|
|
93
|
+
if not_numbers:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"An image citation with anchor={anchor_type} needs numbers for "
|
|
96
|
+
f"x, y, width and height. Not a number: {', '.join(not_numbers)}."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _check_image_citation_url(query: str, fragment: str) -> None:
|
|
101
|
+
# Blank values are dropped, as agora's validator parses the query.
|
|
102
|
+
params = parse_qs(query)
|
|
103
|
+
|
|
104
|
+
def first(key: str) -> str | None:
|
|
105
|
+
values = params.get(key)
|
|
106
|
+
return values[0] if values else None
|
|
107
|
+
|
|
108
|
+
asset_type = (first("asset_type") or first("type") or "").lower()
|
|
109
|
+
if asset_type != "image":
|
|
110
|
+
return
|
|
111
|
+
anchor_type = (first("anchor") or "").lower() or None
|
|
112
|
+
check_image_citation(
|
|
113
|
+
anchor_type=anchor_type,
|
|
114
|
+
has_excerpt=first("excerpt") is not None or fragment.startswith(":~:text="),
|
|
115
|
+
region={field: first(field) for field in _IMAGE_REGION_FIELDS},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
37
119
|
def normalize_citation_string(citation_string: str) -> str:
|
|
38
120
|
value = citation_string.strip()
|
|
39
121
|
if not value:
|
|
@@ -49,6 +131,7 @@ def normalize_citation_string(citation_string: str) -> str:
|
|
|
49
131
|
asset_ids = parse_qs(parsed.query).get("asset_ids")
|
|
50
132
|
if not asset_ids or not asset_ids[0]:
|
|
51
133
|
raise ValueError("Athena Spaces citation URLs must include asset_ids")
|
|
134
|
+
_check_image_citation_url(parsed.query, parsed.fragment)
|
|
52
135
|
return value
|
|
53
136
|
|
|
54
137
|
if not parsed.scheme and value.startswith("/"):
|
|
@@ -61,6 +144,7 @@ def normalize_citation_string(citation_string: str) -> str:
|
|
|
61
144
|
if relative.path.rstrip("/") == "/dashboard/spaces":
|
|
62
145
|
asset_ids = parse_qs(relative.query).get("asset_ids")
|
|
63
146
|
if asset_ids and asset_ids[0]:
|
|
147
|
+
_check_image_citation_url(relative.query, relative.fragment)
|
|
64
148
|
return value
|
|
65
149
|
raise ValueError("Athena Spaces citation URLs must include asset_ids")
|
|
66
150
|
|
|
@@ -106,6 +190,35 @@ def destination_anchor_to_dict(destination_anchor: object) -> dict[str, Any]:
|
|
|
106
190
|
)
|
|
107
191
|
|
|
108
192
|
|
|
193
|
+
def _check_typed_image_citation(
|
|
194
|
+
*,
|
|
195
|
+
source_ref: dict[str, Any],
|
|
196
|
+
source_anchor: dict[str, Any] | None,
|
|
197
|
+
excerpt: str | None,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Apply the image citation rules to an ``AssetReference`` source.
|
|
200
|
+
|
|
201
|
+
A ``PixelRangeAnchor`` / ``RectAnchor`` source is an image region:
|
|
202
|
+
DOCX Studio names it ``type=image`` and keeps the excerpt out of its URL
|
|
203
|
+
(the record keeps it as the span of the cited paragraph to highlight).
|
|
204
|
+
A source that ``AssetMeta(asset_type="image")`` names an image must use
|
|
205
|
+
one of those anchors.
|
|
206
|
+
"""
|
|
207
|
+
anchor = source_anchor or source_ref.get("anchor")
|
|
208
|
+
anchor_type = anchor.get("type") if isinstance(anchor, dict) else None
|
|
209
|
+
meta = source_ref.get("meta")
|
|
210
|
+
asset_type = meta.get("assetType") if isinstance(meta, dict) else None
|
|
211
|
+
is_region = anchor_type in _IMAGE_REGION_ANCHOR_TYPES
|
|
212
|
+
if not is_region and str(asset_type or "").lower() != "image":
|
|
213
|
+
return
|
|
214
|
+
meta_excerpt = meta.get("excerpt") if isinstance(meta, dict) else None
|
|
215
|
+
check_image_citation(
|
|
216
|
+
anchor_type=anchor_type,
|
|
217
|
+
has_excerpt=excerpt is not None or meta_excerpt is not None,
|
|
218
|
+
region=anchor if is_region and isinstance(anchor, dict) else None,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
109
222
|
def build_add_citation_command(
|
|
110
223
|
*,
|
|
111
224
|
destination_anchor: object,
|
|
@@ -142,11 +255,19 @@ def build_add_citation_command(
|
|
|
142
255
|
if source_ref is None and anchor is not None:
|
|
143
256
|
raise ValueError("anchor requires an AssetReference source; encode the anchor in citation_string instead")
|
|
144
257
|
|
|
258
|
+
source_anchor = anchor_to_dict(anchor)
|
|
259
|
+
if source_ref is not None:
|
|
260
|
+
_check_typed_image_citation(
|
|
261
|
+
source_ref=source_ref,
|
|
262
|
+
source_anchor=source_anchor,
|
|
263
|
+
excerpt=excerpt,
|
|
264
|
+
)
|
|
265
|
+
|
|
145
266
|
citation_id = new_citation_id()
|
|
146
267
|
return citation_id, AddCitation(
|
|
147
268
|
destination_anchor=destination_anchor_to_dict(destination_anchor),
|
|
148
269
|
source_ref=source_ref,
|
|
149
|
-
source_anchor=
|
|
270
|
+
source_anchor=source_anchor,
|
|
150
271
|
citation_string=normalized_citation_string,
|
|
151
272
|
citation_id=citation_id,
|
|
152
273
|
excerpt=excerpt,
|
|
@@ -5,7 +5,7 @@ bootstrap and upload calls on urllib so ``Document.create()`` can provision
|
|
|
5
5
|
Word document assets without pulling in extra runtime dependencies.
|
|
6
6
|
|
|
7
7
|
We use ``urllib`` from stdlib to avoid pulling ``httpx`` / ``requests``
|
|
8
|
-
into the runtime — this code runs in
|
|
8
|
+
into the runtime — this code runs in sandboxes where every MB
|
|
9
9
|
matters, and the existing SDK already keeps its dep tree tight.
|
|
10
10
|
"""
|
|
11
11
|
|
|
@@ -46,6 +46,7 @@ from docx.commands import (
|
|
|
46
46
|
BookmarksList,
|
|
47
47
|
ChartsGet,
|
|
48
48
|
ChartsList,
|
|
49
|
+
ClearBody,
|
|
49
50
|
ClearParagraphAlignment,
|
|
50
51
|
ClearParagraphIndentation,
|
|
51
52
|
ClearParagraphSpacing,
|
|
@@ -1002,6 +1003,7 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
1002
1003
|
"sdt.delete": ContentControlsDelete,
|
|
1003
1004
|
"sections.set_columns": SetSectionColumns,
|
|
1004
1005
|
"find_replace": FindReplace,
|
|
1006
|
+
"clear_body": ClearBody,
|
|
1005
1007
|
"find_text": FindText,
|
|
1006
1008
|
"iter_runs": IterRuns,
|
|
1007
1009
|
"export_pdf": ExportPDF,
|
|
@@ -497,6 +497,27 @@ class PostExportOps:
|
|
|
497
497
|
or bool(self._row_cant_split_ops)
|
|
498
498
|
)
|
|
499
499
|
|
|
500
|
+
def discard_body_ops(self) -> None:
|
|
501
|
+
"""Drop every rewrite that addresses body content by position.
|
|
502
|
+
|
|
503
|
+
Cell/table formatting, row heights, captured REF paragraphs and
|
|
504
|
+
header/footer relocations are all keyed by body-relative indices
|
|
505
|
+
(``table_doc_index``, ``source_paragraph_index``, body node ids).
|
|
506
|
+
Once the body story has been replaced they point at nodes that no
|
|
507
|
+
longer exist — and would silently re-target whatever the caller
|
|
508
|
+
creates next at the same index. Document-wide rewrites
|
|
509
|
+
(``font_override``) do not address the body and are kept.
|
|
510
|
+
"""
|
|
511
|
+
self._cell_format_ops.clear()
|
|
512
|
+
self._cell_run_format_ops.clear()
|
|
513
|
+
self._cell_hyperlink_ops.clear()
|
|
514
|
+
self._cell_shading_ops.clear()
|
|
515
|
+
self._table_borders_ops.clear()
|
|
516
|
+
self._captured_refs.clear()
|
|
517
|
+
self._row_height_ops.clear()
|
|
518
|
+
self._row_cant_split_ops.clear()
|
|
519
|
+
self._header_footer_relocations.clear()
|
|
520
|
+
|
|
500
521
|
def add_row_cant_split(
|
|
501
522
|
self,
|
|
502
523
|
*,
|
|
@@ -23,6 +23,11 @@ Emits are **synchronous, best-effort** HTTP POSTs:
|
|
|
23
23
|
- Snapshot the URL at ``emit_begin`` time and carry it to ``emit_end``
|
|
24
24
|
so late end events can't be misattributed if a new sandbox run
|
|
25
25
|
swapped in a different URL between begin and end.
|
|
26
|
+
- Reuse one keep-alive connection per endpoint instead of a new TCP (and
|
|
27
|
+
TLS) connection per event. An idle connection the server has closed is
|
|
28
|
+
detected before sending and replaced; a send is never retried (the
|
|
29
|
+
registry does not deduplicate events). When a proxy applies to the URL
|
|
30
|
+
(``*_proxy`` env vars) each event goes through urllib as before.
|
|
26
31
|
|
|
27
32
|
This module is *intentionally byte-identical* across the docx / pptx /
|
|
28
33
|
xlsx SDKs — the three SDKs have no shared base. Duplicating ~100 LOC
|
|
@@ -31,10 +36,14 @@ of stdlib code is cheaper than spinning up a 4th release pipeline.
|
|
|
31
36
|
|
|
32
37
|
from __future__ import annotations
|
|
33
38
|
|
|
39
|
+
import http.client
|
|
34
40
|
import json
|
|
35
41
|
import os
|
|
42
|
+
import select
|
|
43
|
+
import threading
|
|
36
44
|
import time
|
|
37
45
|
import urllib.error
|
|
46
|
+
import urllib.parse
|
|
38
47
|
import urllib.request
|
|
39
48
|
import uuid
|
|
40
49
|
from typing import Any
|
|
@@ -46,6 +55,58 @@ _HTTP_TIMEOUT = 2.0
|
|
|
46
55
|
# original run's endpoint even if ATHENA_PTC_URL changed since.
|
|
47
56
|
_call_url: dict[str, str] = {}
|
|
48
57
|
|
|
58
|
+
# (scheme, host:port) -> an idle keep-alive connection. Checked out while a
|
|
59
|
+
# POST uses it, so no two threads ever share one.
|
|
60
|
+
_idle: dict[tuple[str, str], http.client.HTTPConnection] = {}
|
|
61
|
+
_idle_lock = threading.Lock()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _dropped(conn: http.client.HTTPConnection) -> bool:
|
|
65
|
+
"""An idle connection the server has closed (or that has unexpected data
|
|
66
|
+
waiting) reads as ready: never send on it."""
|
|
67
|
+
sock = conn.sock
|
|
68
|
+
if sock is None:
|
|
69
|
+
return True
|
|
70
|
+
try:
|
|
71
|
+
return bool(select.select([sock], [], [], 0)[0])
|
|
72
|
+
except (OSError, ValueError):
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _post_keepalive(url: str, raw: bytes) -> None:
|
|
77
|
+
parts = urllib.parse.urlsplit(url)
|
|
78
|
+
if parts.scheme not in ("http", "https") or not parts.hostname:
|
|
79
|
+
raise ValueError(f"unsupported PTC URL: {url!r}")
|
|
80
|
+
target = (parts.path or "/") + (f"?{parts.query}" if parts.query else "")
|
|
81
|
+
key = (parts.scheme, parts.netloc)
|
|
82
|
+
with _idle_lock:
|
|
83
|
+
conn = _idle.pop(key, None)
|
|
84
|
+
if conn is not None and _dropped(conn):
|
|
85
|
+
conn.close()
|
|
86
|
+
conn = None
|
|
87
|
+
if conn is None:
|
|
88
|
+
factory = (
|
|
89
|
+
http.client.HTTPSConnection if parts.scheme == "https" else http.client.HTTPConnection
|
|
90
|
+
)
|
|
91
|
+
conn = factory(parts.netloc, timeout=_HTTP_TIMEOUT)
|
|
92
|
+
# Never retried: a failure after the request left may still have been
|
|
93
|
+
# processed, and the registry does not deduplicate events.
|
|
94
|
+
try:
|
|
95
|
+
conn.request("POST", target, body=raw, headers={"Content-Type": "application/json"})
|
|
96
|
+
response = conn.getresponse()
|
|
97
|
+
response.read()
|
|
98
|
+
except BaseException:
|
|
99
|
+
conn.close()
|
|
100
|
+
raise
|
|
101
|
+
if response.will_close:
|
|
102
|
+
conn.close()
|
|
103
|
+
return
|
|
104
|
+
with _idle_lock:
|
|
105
|
+
spare = _idle.pop(key, None)
|
|
106
|
+
_idle[key] = conn
|
|
107
|
+
if spare is not None:
|
|
108
|
+
spare.close()
|
|
109
|
+
|
|
49
110
|
|
|
50
111
|
def _send(url: str, body: dict[str, Any]) -> None:
|
|
51
112
|
"""Synchronously POST one PTC event. Best-effort; never raises.
|
|
@@ -63,14 +124,19 @@ def _send(url: str, body: dict[str, Any]) -> None:
|
|
|
63
124
|
key = "args" if body.get("phase") == "begin" else "result"
|
|
64
125
|
body[key] = {"__truncated__": True}
|
|
65
126
|
raw = json.dumps(body).encode("utf-8")
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
127
|
+
parts = urllib.parse.urlsplit(url)
|
|
128
|
+
proxy = urllib.request.getproxies().get(parts.scheme)
|
|
129
|
+
if proxy and not urllib.request.proxy_bypass(parts.hostname or ""):
|
|
130
|
+
req = urllib.request.Request(
|
|
131
|
+
url,
|
|
132
|
+
data=raw,
|
|
133
|
+
method="POST",
|
|
134
|
+
headers={"Content-Type": "application/json"},
|
|
135
|
+
)
|
|
136
|
+
urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT).close()
|
|
137
|
+
else:
|
|
138
|
+
_post_keepalive(url, raw)
|
|
139
|
+
except (urllib.error.URLError, OSError, ValueError, http.client.HTTPException):
|
|
74
140
|
pass # never propagate — PTC is best-effort
|
|
75
141
|
|
|
76
142
|
|
|
@@ -19,6 +19,9 @@ _LONG_RUNNING_COMMAND_TYPES: frozenset[str] = frozenset(
|
|
|
19
19
|
# ordinary 60s budget committing a table batch even when the Python code
|
|
20
20
|
# uses the fast no-read cell path.
|
|
21
21
|
"TableSetCell",
|
|
22
|
+
# ``Document.clear()`` replaces the whole body story in one write;
|
|
23
|
+
# on a large document that single command can outlast the 60s budget.
|
|
24
|
+
"ClearBody",
|
|
22
25
|
}
|
|
23
26
|
)
|
|
24
27
|
|
|
@@ -36,7 +36,7 @@ if TYPE_CHECKING:
|
|
|
36
36
|
|
|
37
37
|
def _log_info(msg: str) -> None:
|
|
38
38
|
"""Minimal stderr logger; we avoid loguru to keep the SDK runtime
|
|
39
|
-
dependency tree small (this code runs inside
|
|
39
|
+
dependency tree small (this code runs inside sandboxes)."""
|
|
40
40
|
import sys
|
|
41
41
|
|
|
42
42
|
print(f"[docx-sdk] {msg}", file=sys.stderr)
|
|
@@ -84,6 +84,10 @@ class Session:
|
|
|
84
84
|
from docx._postproc import PostExportOps
|
|
85
85
|
|
|
86
86
|
self._post_export_ops: PostExportOps = PostExportOps()
|
|
87
|
+
# Bumped by ``Document.clear()``. ``Table`` proxies remember the
|
|
88
|
+
# generation they were created in, so a proxy from before a clear
|
|
89
|
+
# can never be re-anchored to a table created after it.
|
|
90
|
+
self._body_generation: int = 0
|
|
87
91
|
|
|
88
92
|
@property
|
|
89
93
|
def asset_id(self) -> str:
|
|
@@ -136,6 +136,10 @@ class Insert(Command):
|
|
|
136
136
|
# on the DOCX Studio call (renamed in transit so it doesn't collide with our
|
|
137
137
|
# discriminator).
|
|
138
138
|
value_type: str | None = None
|
|
139
|
+
# Text mode only. ``True`` continues the formatting of the run at the
|
|
140
|
+
# insertion point (``Run.add_text``); omitted means the inserted text is
|
|
141
|
+
# a fresh, unformatted run (``Paragraph.add_run``).
|
|
142
|
+
inherit_marks: bool | None = None
|
|
139
143
|
placement: str | None = None
|
|
140
144
|
content: dict[str, Any] | None = None
|
|
141
145
|
|
|
@@ -1543,6 +1547,18 @@ class FindReplace(Command):
|
|
|
1543
1547
|
max_replacements: int | None = None
|
|
1544
1548
|
|
|
1545
1549
|
|
|
1550
|
+
@dataclass
|
|
1551
|
+
class ClearBody(Command):
|
|
1552
|
+
"""Replace the whole body story with a single empty paragraph.
|
|
1553
|
+
|
|
1554
|
+
Athena extension backing :meth:`Document.clear`. One server-side
|
|
1555
|
+
write instead of a ``Replace`` per paragraph and per table-cell
|
|
1556
|
+
paragraph; headers, footers and notes are untouched and the final
|
|
1557
|
+
section's page setup survives. Returns ``{nodeId}`` of the new
|
|
1558
|
+
empty paragraph.
|
|
1559
|
+
"""
|
|
1560
|
+
|
|
1561
|
+
|
|
1546
1562
|
@dataclass
|
|
1547
1563
|
class FindText(Command):
|
|
1548
1564
|
"""Search document text and return bounded match context.
|
|
@@ -1576,8 +1592,8 @@ class IterRuns(Command):
|
|
|
1576
1592
|
|
|
1577
1593
|
# ---------------------------------------------------------------------------
|
|
1578
1594
|
# PDF Export (Athena extension — python-docx issue #113 keeps getting
|
|
1579
|
-
# re-asked despite being closed wontfix. DOCX Studio renders
|
|
1580
|
-
#
|
|
1595
|
+
# re-asked despite being closed wontfix. DOCX Studio renders the document
|
|
1596
|
+
# to bytes the client can download or persist.)
|
|
1581
1597
|
# ---------------------------------------------------------------------------
|
|
1582
1598
|
|
|
1583
1599
|
|
|
@@ -1759,6 +1775,9 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
|
|
|
1759
1775
|
"ExportDocx",
|
|
1760
1776
|
# FindReplace returns a replacement count for the caller.
|
|
1761
1777
|
"FindReplace",
|
|
1778
|
+
# ClearBody replaces the whole body; Document.clear awaits it so a
|
|
1779
|
+
# rejected clear raises instead of leaving stale content behind.
|
|
1780
|
+
"ClearBody",
|
|
1762
1781
|
}
|
|
1763
1782
|
)
|
|
1764
1783
|
|
|
@@ -1852,6 +1871,7 @@ for _cls, _issue, _desc in [
|
|
|
1852
1871
|
(ContentControlsDelete, "python-docx#155", "SDT delete"),
|
|
1853
1872
|
(SetSectionColumns, "python-docx#167", "Section multi-column layout"),
|
|
1854
1873
|
(FindReplace, "python-docx#30", "Formatting-preserving find/replace"),
|
|
1874
|
+
(ClearBody, None, "Document.clear — bulk body reset"),
|
|
1855
1875
|
(FindText, "python-docx#980", "Fast document text search"),
|
|
1856
1876
|
(IterRuns, "python-docx#980", "Run stream for run-isolation"),
|
|
1857
1877
|
(ExportPDF, "python-docx#113", "PDF export"),
|
|
@@ -1990,6 +2010,7 @@ __all__ = [
|
|
|
1990
2010
|
"ContentControlsDelete",
|
|
1991
2011
|
"SetSectionColumns",
|
|
1992
2012
|
"FindReplace",
|
|
2013
|
+
"ClearBody",
|
|
1993
2014
|
"FindText",
|
|
1994
2015
|
"IterRuns",
|
|
1995
2016
|
"ExportPDF",
|