athena-python-docx 0.15.5__tar.gz → 0.16.1__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.15.5 → athena_python_docx-0.16.1}/PKG-INFO +1 -1
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/__init__.py +1 -1
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_buffer.py +29 -29
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_http_doc.py +0 -18
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_timeouts.py +0 -7
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/client.py +2 -2
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/commands.py +5 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/document.py +22 -2
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/paragraph.py +49 -7
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/pyproject.toml +1 -1
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/fake_session.py +7 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_buffer.py +1 -40
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_http_transport.py +1 -68
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_iter_inner_content.py +65 -3
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_ptc.py +91 -15
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/uv.lock +1 -1
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/.gitignore +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/CLAUDE.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/DOCX_EXEC_LAB.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/README.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_batching.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_execution.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_http.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_postproc.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_ptc.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/api.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/charts.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/comments.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/section.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/style.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/table.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/enum/text.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/errors.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/exceptions.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/fields.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/footnotes.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/math.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/revisions.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/sdt.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/section.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/session.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/settings.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/shape.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/shared.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/styles/style.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/table.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/font.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/run.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/toc.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/docx/typing.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/publish.sh +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/release.sh +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/conftest.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/README.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_athena_extensions_registry.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_commands.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_comments.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.15.5 → athena_python_docx-0.16.1}/tests/test_zod_wire_contract.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.1
|
|
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>
|
|
@@ -21,9 +21,9 @@ 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
23
|
`flush_all` is a process-wide hook used by the Daytona sandbox prelude to
|
|
24
|
-
make sure pending writes hit Keryx
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
make sure pending writes hit Keryx before the sandbox is suspended. It walks
|
|
25
|
+
a weak-ref registry so dead Workbook instances don't keep their buffers
|
|
26
|
+
alive.
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
from __future__ import annotations
|
|
@@ -124,10 +124,25 @@ if TYPE_CHECKING:
|
|
|
124
124
|
# HTTP POST to agora (see ``_ptc._send``), so emitting one card per low-level
|
|
125
125
|
# mutation produces hundreds of sub-cards per script and pays full network
|
|
126
126
|
# RTT on every one. The allow-list keeps the per-action signal — one card
|
|
127
|
-
# per logical
|
|
128
|
-
# latency. Asset-level creation events (``CreateDocument``)
|
|
129
|
-
# from ``Document.create`` directly, not through this buffer path.
|
|
130
|
-
_PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph"})
|
|
127
|
+
# per logical block creation (paragraph, heading, table) — without the spam
|
|
128
|
+
# or the cumulative latency. Asset-level creation events (``CreateDocument``)
|
|
129
|
+
# are emitted from ``Document.create`` directly, not through this buffer path.
|
|
130
|
+
_PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph", "CreateHeading", "CreateTable"})
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _should_emit_ptc(cmd: Command) -> bool:
|
|
134
|
+
"""Whether ``cmd`` should produce a PTC sub-tool-card.
|
|
135
|
+
|
|
136
|
+
Only allow-listed block creates emit, and only when they target the
|
|
137
|
+
**body** story. Creates carrying an ``in_`` story locator (header,
|
|
138
|
+
footer, footnote, …) are skipped: the frontend locate affordance
|
|
139
|
+
scans the body editor only, so a header/footer card would highlight
|
|
140
|
+
an unrelated body block or nothing at all. Skip until the frontend
|
|
141
|
+
can route by ``args.in``.
|
|
142
|
+
"""
|
|
143
|
+
if type(cmd).__name__ not in _PTC_EMIT_TOOLS:
|
|
144
|
+
return False
|
|
145
|
+
return getattr(cmd, "in_", None) is None
|
|
131
146
|
|
|
132
147
|
|
|
133
148
|
def _ptc_emit_end_batch(cmds: list[Command], *, is_error: bool) -> None:
|
|
@@ -180,12 +195,11 @@ def _unregister(buffer: "CommandBuffer") -> None:
|
|
|
180
195
|
|
|
181
196
|
|
|
182
197
|
def flush_all(*, strict: bool = False) -> None:
|
|
183
|
-
"""Flush
|
|
198
|
+
"""Flush every live CommandBuffer in this process.
|
|
184
199
|
|
|
185
200
|
Used by the Daytona sandbox prelude after user code returns, so
|
|
186
|
-
buffered mutations make it to Keryx
|
|
187
|
-
|
|
188
|
-
Buffers exist (no-op).
|
|
201
|
+
buffered mutations make it to Keryx before the sandbox is suspended.
|
|
202
|
+
Safe to call when no Buffers exist (no-op).
|
|
189
203
|
|
|
190
204
|
By default, failures are logged and swallowed for backwards
|
|
191
205
|
compatibility with existing local scripts. The executor calls
|
|
@@ -217,9 +231,9 @@ def flush_all(*, strict: bool = False) -> None:
|
|
|
217
231
|
sys.stderr.write(f"[docx-sdk] flush_all: {msg}\n")
|
|
218
232
|
continue
|
|
219
233
|
try:
|
|
220
|
-
buf.
|
|
234
|
+
buf.flush()
|
|
221
235
|
except Exception as e: # noqa: BLE001
|
|
222
|
-
msg = f"buffer {buf.asset_id} flush
|
|
236
|
+
msg = f"buffer {buf.asset_id} flush failed: {e}"
|
|
223
237
|
if strict:
|
|
224
238
|
failures.append(msg)
|
|
225
239
|
else:
|
|
@@ -415,7 +429,7 @@ class CommandBuffer:
|
|
|
415
429
|
# commands still flow through the buffer (and through the batched
|
|
416
430
|
# HTTP POST), they just don't produce a sub-tool-card. ``emit_end``
|
|
417
431
|
# is automatically skipped because no ``_ptc_call_id`` was set.
|
|
418
|
-
if
|
|
432
|
+
if _should_emit_ptc(cmd):
|
|
419
433
|
try:
|
|
420
434
|
cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
|
|
421
435
|
type(cmd).__name__,
|
|
@@ -500,20 +514,6 @@ class CommandBuffer:
|
|
|
500
514
|
_apply_proxy_id_rewrites(results, proxy_id_refs)
|
|
501
515
|
return results
|
|
502
516
|
|
|
503
|
-
def commit(self) -> None:
|
|
504
|
-
"""Flush pending commands and release the server-side edit session.
|
|
505
|
-
|
|
506
|
-
The HTTP command response tells us docx-studio applied the batch to its
|
|
507
|
-
pooled SuperDoc handle. Releasing the handle closes the collaboration
|
|
508
|
-
session without discard on the server, which is the durability
|
|
509
|
-
checkpoint for asset-backed document edits.
|
|
510
|
-
"""
|
|
511
|
-
self._assert_current_execution()
|
|
512
|
-
self.flush()
|
|
513
|
-
release_asset = getattr(self._client, "release_asset", None)
|
|
514
|
-
if callable(release_asset):
|
|
515
|
-
release_asset(self._asset_id)
|
|
516
|
-
|
|
517
517
|
def close(self) -> None:
|
|
518
518
|
"""Flush and disable. Idempotent."""
|
|
519
519
|
if self._closed:
|
|
@@ -528,7 +528,7 @@ class CommandBuffer:
|
|
|
528
528
|
self._proxy_id_refs = {}
|
|
529
529
|
_ptc_emit_end_batch(pending, is_error=True)
|
|
530
530
|
else:
|
|
531
|
-
self.
|
|
531
|
+
self.flush()
|
|
532
532
|
finally:
|
|
533
533
|
self._closed = True
|
|
534
534
|
_unregister(self)
|
|
@@ -699,24 +699,6 @@ class HttpClient:
|
|
|
699
699
|
except Exception: # noqa: BLE001
|
|
700
700
|
pass
|
|
701
701
|
|
|
702
|
-
def release_asset(self, asset_id: str) -> None:
|
|
703
|
-
"""Ask docx-studio to close its pooled SuperDoc session for ``asset_id``.
|
|
704
|
-
|
|
705
|
-
Command POSTs only prove that the server accepted and applied the
|
|
706
|
-
mutation batch to its live SuperDoc handle. The durable checkpoint for
|
|
707
|
-
collaborative sessions happens when that handle is closed without
|
|
708
|
-
discard, so ``Document.save()`` and the Daytona ``flush_all`` cleanup
|
|
709
|
-
call this endpoint after draining the command buffer.
|
|
710
|
-
"""
|
|
711
|
-
url: str = f"{self._base_url}/docs/{asset_id}/session/release"
|
|
712
|
-
_http_post_json(
|
|
713
|
-
session=self._session,
|
|
714
|
-
url=url,
|
|
715
|
-
api_key=self._api_key,
|
|
716
|
-
body={},
|
|
717
|
-
timeout=30.0,
|
|
718
|
-
)
|
|
719
|
-
|
|
720
702
|
def execute_batch(
|
|
721
703
|
self,
|
|
722
704
|
asset_id: str,
|
|
@@ -13,13 +13,6 @@ LONG_RUNNING_COMMAND_TIMEOUT_SECONDS: float = 300.0
|
|
|
13
13
|
|
|
14
14
|
_LONG_RUNNING_COMMAND_TYPES: frozenset[str] = frozenset(
|
|
15
15
|
{
|
|
16
|
-
# The first write to a large imported document can spend close to a
|
|
17
|
-
# minute opening/syncing the server-side SuperDoc session before the
|
|
18
|
-
# block creation itself is applied. These are ordinary agent primitives
|
|
19
|
-
# for "append a summary/TLDR", so give the first batch enough room.
|
|
20
|
-
"CreateHeading",
|
|
21
|
-
"CreateParagraph",
|
|
22
|
-
"CreateSectionBreak",
|
|
23
16
|
"FindReplace",
|
|
24
17
|
# Plain ``cell.text = value`` table fills are buffered as TableSetCell
|
|
25
18
|
# commands. On dense legal documents, SuperDoc can spend well over the
|
|
@@ -231,8 +231,8 @@ class Session:
|
|
|
231
231
|
handle = self._doc_handle
|
|
232
232
|
buffer = getattr(handle, "buffer", None) if handle is not None else None
|
|
233
233
|
if buffer is not None:
|
|
234
|
-
buffer.
|
|
235
|
-
_log_info(f"Saved {self._asset_id} (buffer drained
|
|
234
|
+
buffer.flush()
|
|
235
|
+
_log_info(f"Saved {self._asset_id} (buffer drained)")
|
|
236
236
|
|
|
237
237
|
async def close(self) -> None:
|
|
238
238
|
"""Close the session. Idempotent."""
|
|
@@ -519,6 +519,11 @@ class ImagesGet(Command):
|
|
|
519
519
|
class BlocksList(Command):
|
|
520
520
|
node_types: list[str] | None = None
|
|
521
521
|
include_text: bool | None = None
|
|
522
|
+
# Ask the server to inline each paragraph/heading block's run breakdown
|
|
523
|
+
# (``paragraph.inlines``) so ``Document.paragraphs`` can hydrate
|
|
524
|
+
# ``Paragraph.text``/``.runs``/``.style`` from one list instead of a
|
|
525
|
+
# ``get_node_by_id`` per paragraph. Serialized to ``includeRuns`` on the wire.
|
|
526
|
+
include_runs: bool | None = None
|
|
522
527
|
|
|
523
528
|
|
|
524
529
|
@dataclass
|
|
@@ -359,12 +359,23 @@ class Document:
|
|
|
359
359
|
# Include "heading" too so iterating over paragraphs covers both —
|
|
360
360
|
# python-docx does not distinguish headings from paragraphs at the
|
|
361
361
|
# Document.paragraphs level either (they're all Paragraph instances).
|
|
362
|
+
# ``includeRuns`` asks the server to inline each block's run breakdown
|
|
363
|
+
# (``paragraph.inlines``) so we can hydrate every returned proxy's
|
|
364
|
+
# ``.text`` / ``.runs`` / ``.style`` from this single list — instead of
|
|
365
|
+
# one ``get_node_by_id`` round-trip per paragraph per property. A
|
|
366
|
+
# read-heavy restyle loop over a large doc drops from ~N×3 round-trips
|
|
367
|
+
# to 1. See ``docx-studio/PERFORMANCE_BATCHING_ANALYSIS.md``.
|
|
362
368
|
raw: object = run_sync(
|
|
363
369
|
self._session.doc.blocks.list(
|
|
364
|
-
{
|
|
370
|
+
{
|
|
371
|
+
"nodeTypes": ["paragraph", "heading"],
|
|
372
|
+
"includeText": True,
|
|
373
|
+
"includeRuns": True,
|
|
374
|
+
},
|
|
365
375
|
),
|
|
366
376
|
)
|
|
367
|
-
# Real shape: {"blocks": [{"kind":"block","nodeType":..,"nodeId"
|
|
377
|
+
# Real shape: {"blocks": [{"kind":"block","nodeType":..,"nodeId":..,
|
|
378
|
+
# "styleId":..,"text":..,"paragraph":{"inlines":[..]}}], ...}
|
|
368
379
|
if isinstance(raw, dict):
|
|
369
380
|
blocks_obj: object = raw.get("blocks", [])
|
|
370
381
|
blocks: list = blocks_obj if isinstance(blocks_obj, list) else []
|
|
@@ -380,11 +391,20 @@ class Document:
|
|
|
380
391
|
if node_id:
|
|
381
392
|
nt_raw = b.get("nodeType")
|
|
382
393
|
nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
|
|
394
|
+
# Seed the read cache when the server inlined this block's runs.
|
|
395
|
+
# The snapshot is ``get_node_by_id``-shaped so
|
|
396
|
+
# ``Paragraph._node_info`` and the text/runs/style getters consume
|
|
397
|
+
# it unchanged. Blocks without an ``inlines`` payload (e.g.
|
|
398
|
+
# tables) fall back to live queries.
|
|
399
|
+
snapshot: dict | None = (
|
|
400
|
+
{"node": b} if isinstance(b.get("paragraph"), dict) else None
|
|
401
|
+
)
|
|
383
402
|
out.append(
|
|
384
403
|
Paragraph(
|
|
385
404
|
session=self._session,
|
|
386
405
|
node_id=node_id,
|
|
387
406
|
node_type=nt,
|
|
407
|
+
node_info_snapshot=snapshot,
|
|
388
408
|
),
|
|
389
409
|
)
|
|
390
410
|
return out
|
|
@@ -226,6 +226,7 @@ class Paragraph:
|
|
|
226
226
|
in_cell: bool = False,
|
|
227
227
|
cell_ooxml_address: "tuple[str | None, int, int, int, int] | None" = None,
|
|
228
228
|
cell: "object | None" = None,
|
|
229
|
+
node_info_snapshot: dict | None = None,
|
|
229
230
|
) -> None:
|
|
230
231
|
self._session: "Session" = session
|
|
231
232
|
self._node_id: str = node_id
|
|
@@ -272,6 +273,36 @@ class Paragraph:
|
|
|
272
273
|
# — defensive for paragraph proxies built outside the _Cell
|
|
273
274
|
# construction paths.
|
|
274
275
|
self._cell: object | None = cell
|
|
276
|
+
# Prefetched ``get_node_by_id``-shaped snapshot
|
|
277
|
+
# (``{"node": {..., "paragraph": {"inlines": [...]}}}``) seeded by
|
|
278
|
+
# ``Document.paragraphs`` via the bulk ``blocks.list(includeRuns=True)``
|
|
279
|
+
# read. When present, ``text`` / ``runs`` / ``style`` read it instead of
|
|
280
|
+
# issuing a per-property ``get_node_by_id`` round-trip. It is a
|
|
281
|
+
# point-in-time snapshot (consistent with ``Document.paragraphs`` already
|
|
282
|
+
# returning a snapshot list) and is dropped by ``_invalidate_node_info``
|
|
283
|
+
# the moment this proxy mutates its own content.
|
|
284
|
+
self._node_info_snapshot: dict | None = node_info_snapshot
|
|
285
|
+
|
|
286
|
+
def _node_info(self) -> object:
|
|
287
|
+
"""Return this paragraph's ``get_node_by_id``-shaped node info.
|
|
288
|
+
|
|
289
|
+
Served from the prefetch snapshot when one was seeded (one bulk
|
|
290
|
+
``blocks.list`` for the whole document instead of one
|
|
291
|
+
``get_node_by_id`` per ``.text`` / ``.runs`` / ``.style`` access);
|
|
292
|
+
otherwise a live query. ``_invalidate_node_info`` clears the snapshot
|
|
293
|
+
after a content mutation so the next read re-queries.
|
|
294
|
+
"""
|
|
295
|
+
snapshot = self._node_info_snapshot
|
|
296
|
+
if snapshot is not None:
|
|
297
|
+
return snapshot
|
|
298
|
+
return run_sync(
|
|
299
|
+
self._session.doc.get_node_by_id({"id": self._node_id}),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def _invalidate_node_info(self) -> None:
|
|
303
|
+
"""Drop the prefetched node snapshot after a content mutation, so the
|
|
304
|
+
next ``text`` / ``runs`` / ``style`` read reflects the new state."""
|
|
305
|
+
self._node_info_snapshot = None
|
|
275
306
|
|
|
276
307
|
def _check_cell_inner(self, op: str, **format_props: Any) -> bool:
|
|
277
308
|
"""Return ``True`` if this paragraph is inside a table cell and
|
|
@@ -356,7 +387,7 @@ class Paragraph:
|
|
|
356
387
|
@property
|
|
357
388
|
def text(self) -> str:
|
|
358
389
|
"""Return the plain-text content of this paragraph."""
|
|
359
|
-
return
|
|
390
|
+
return _extract_text(self._node_info())
|
|
360
391
|
|
|
361
392
|
@text.setter
|
|
362
393
|
def text(self, value: str) -> None:
|
|
@@ -399,6 +430,9 @@ class Paragraph:
|
|
|
399
430
|
# We just replaced the entire paragraph contents with ``value``;
|
|
400
431
|
# the new text-address length is simply ``len(value)``.
|
|
401
432
|
self._text_len = len(value)
|
|
433
|
+
# Content changed — drop the prefetched snapshot so a follow-up
|
|
434
|
+
# ``.text`` / ``.runs`` read reflects the replacement.
|
|
435
|
+
self._invalidate_node_info()
|
|
402
436
|
|
|
403
437
|
@property
|
|
404
438
|
def runs(self) -> list["Run"]:
|
|
@@ -413,9 +447,7 @@ class Paragraph:
|
|
|
413
447
|
"""
|
|
414
448
|
from docx.text.run import Run
|
|
415
449
|
|
|
416
|
-
info: object =
|
|
417
|
-
self._session.doc.get_node_by_id({"id": self._node_id}),
|
|
418
|
-
)
|
|
450
|
+
info: object = self._node_info()
|
|
419
451
|
inlines: list[dict] = _walk_inlines(info)
|
|
420
452
|
runs: list["Run"] = []
|
|
421
453
|
offset: int = 0
|
|
@@ -479,9 +511,7 @@ class Paragraph:
|
|
|
479
511
|
from docx.styles.style import ParagraphStyle
|
|
480
512
|
from docx.enum.style import WD_STYLE_TYPE
|
|
481
513
|
|
|
482
|
-
info: object =
|
|
483
|
-
self._session.doc.get_node_by_id({"id": self._node_id}),
|
|
484
|
-
)
|
|
514
|
+
info: object = self._node_info()
|
|
485
515
|
if not isinstance(info, dict):
|
|
486
516
|
return None
|
|
487
517
|
node: object = info.get("node")
|
|
@@ -537,6 +567,10 @@ class Paragraph:
|
|
|
537
567
|
},
|
|
538
568
|
),
|
|
539
569
|
)
|
|
570
|
+
# Style changed — drop the prefetched snapshot so a follow-up ``.style``
|
|
571
|
+
# read reflects the new style instead of the stale prefetched value
|
|
572
|
+
# (the restyle-loop read-after-write this PR's prefetch targets).
|
|
573
|
+
self._invalidate_node_info()
|
|
540
574
|
|
|
541
575
|
@property
|
|
542
576
|
def alignment(self) -> "WD_ALIGN_PARAGRAPH | None":
|
|
@@ -1227,6 +1261,9 @@ class Paragraph:
|
|
|
1227
1261
|
)
|
|
1228
1262
|
offset += 1
|
|
1229
1263
|
i = j + 1
|
|
1264
|
+
# Inserted content — drop the prefetched node snapshot so a follow-up
|
|
1265
|
+
# ``.text`` / ``.runs`` read on this proxy reflects the new runs.
|
|
1266
|
+
self._invalidate_node_info()
|
|
1230
1267
|
|
|
1231
1268
|
@athena_extension(
|
|
1232
1269
|
issue=74,
|
|
@@ -1397,6 +1434,11 @@ class Paragraph:
|
|
|
1397
1434
|
)
|
|
1398
1435
|
run_sync(self._session.send_command(cmd))
|
|
1399
1436
|
self._text_len = end_offset
|
|
1437
|
+
# Appended a hyperlink run — drop the prefetched snapshot so a follow-up
|
|
1438
|
+
# ``.text`` / ``.runs`` read reflects the new content. This path fires
|
|
1439
|
+
# ``CreateHyperlink`` directly via ``send_command`` (it bypasses
|
|
1440
|
+
# ``_insert_run_text_segments``), so it needs its own invalidation.
|
|
1441
|
+
self._invalidate_node_info()
|
|
1400
1442
|
|
|
1401
1443
|
run_dict = {"text": text, "hyperlink": {"url": url, "target": url}}
|
|
1402
1444
|
return Hyperlink(
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-docx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.16.1"
|
|
8
8
|
description = "Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -296,6 +296,7 @@ class FakeDocState:
|
|
|
296
296
|
|
|
297
297
|
if op == "blocks.list":
|
|
298
298
|
types = params.get("nodeTypes") or None
|
|
299
|
+
include_runs = bool(params.get("includeRuns"))
|
|
299
300
|
out_blocks: list[dict] = []
|
|
300
301
|
for b in self.blocks:
|
|
301
302
|
if types and b.node_type not in types:
|
|
@@ -307,6 +308,12 @@ class FakeDocState:
|
|
|
307
308
|
"styleId": b.style_id,
|
|
308
309
|
"alignment": b.alignment,
|
|
309
310
|
}
|
|
311
|
+
# Mirror the real server: when runs are requested, inline each
|
|
312
|
+
# paragraph/heading block's run breakdown so the SDK hydrates
|
|
313
|
+
# Paragraph.text/.runs/.style from this one list (no per-node read).
|
|
314
|
+
if include_runs and b.node_type in ("paragraph", "heading"):
|
|
315
|
+
entry["text"] = b.text
|
|
316
|
+
entry["paragraph"] = {"inlines": list(b.inlines)}
|
|
310
317
|
out_blocks.append(entry)
|
|
311
318
|
# Mirror real SuperDoc: when "table" is in the requested
|
|
312
319
|
# nodeTypes, top-level tables come back too. The fake doesn't
|
|
@@ -7,9 +7,8 @@ Verifies that:
|
|
|
7
7
|
- Creates WITH a client id (the transparent-batching path) defer,
|
|
8
8
|
- pending mutations drain in the same batch as an eager call,
|
|
9
9
|
- ``flush()`` drains explicitly,
|
|
10
|
-
- ``commit()`` drains and releases the server-side session,
|
|
11
10
|
- the idle timer auto-flushes after a configurable window,
|
|
12
|
-
- the process-wide ``flush_all`` walks every live buffer
|
|
11
|
+
- the process-wide ``flush_all`` walks every live buffer.
|
|
13
12
|
"""
|
|
14
13
|
|
|
15
14
|
from __future__ import annotations
|
|
@@ -27,7 +26,6 @@ from docx._timeouts import DEFAULT_OP_TIMEOUT_SECONDS, LONG_RUNNING_COMMAND_TIME
|
|
|
27
26
|
from docx.commands import (
|
|
28
27
|
BlocksList,
|
|
29
28
|
Command,
|
|
30
|
-
CreateHeading,
|
|
31
29
|
CreateParagraph,
|
|
32
30
|
FormatApply,
|
|
33
31
|
Replace,
|
|
@@ -46,7 +44,6 @@ class _FakeClient:
|
|
|
46
44
|
# Envelope-level fields recorded per batch — used by
|
|
47
45
|
# track-changes tests to verify changeMode / user propagation.
|
|
48
46
|
self.envelopes: list[dict[str, Any]] = []
|
|
49
|
-
self.releases: list[str] = []
|
|
50
47
|
self._lock = threading.Lock()
|
|
51
48
|
|
|
52
49
|
def execute_batch(
|
|
@@ -65,10 +62,6 @@ class _FakeClient:
|
|
|
65
62
|
# Mimic per-command result envelope.
|
|
66
63
|
return [{"index": i, "ok": True} for i, _ in enumerate(commands)]
|
|
67
64
|
|
|
68
|
-
def release_asset(self, asset_id: str) -> None:
|
|
69
|
-
with self._lock:
|
|
70
|
-
self.releases.append(asset_id)
|
|
71
|
-
|
|
72
65
|
|
|
73
66
|
class _FakeDocHandle:
|
|
74
67
|
def __init__(self, buffer: CommandBuffer) -> None:
|
|
@@ -116,23 +109,6 @@ def test_pending_timeout_extends_for_table_set_cell_batches() -> None:
|
|
|
116
109
|
buf.close()
|
|
117
110
|
|
|
118
111
|
|
|
119
|
-
def test_pending_timeout_extends_for_block_creation_batches() -> None:
|
|
120
|
-
fake = _FakeClient()
|
|
121
|
-
buf = CommandBuffer(fake, "asset_1", auto_flush_seconds=0.0)
|
|
122
|
-
|
|
123
|
-
buf.call(
|
|
124
|
-
CreateHeading(
|
|
125
|
-
text="TLDR",
|
|
126
|
-
level=1,
|
|
127
|
-
at={"kind": "documentEnd"},
|
|
128
|
-
client_node_id="h_test",
|
|
129
|
-
)
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
assert buf.pending_timeout_seconds == LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
|
|
133
|
-
buf.close()
|
|
134
|
-
|
|
135
|
-
|
|
136
112
|
def test_session_pending_timeout_reads_command_buffer_budget() -> None:
|
|
137
113
|
from docx.client import Session
|
|
138
114
|
|
|
@@ -213,19 +189,6 @@ def test_flush_with_empty_queue_is_noop() -> None:
|
|
|
213
189
|
buf.close()
|
|
214
190
|
|
|
215
191
|
|
|
216
|
-
def test_commit_drains_pending_and_releases_server_session() -> None:
|
|
217
|
-
fake = _FakeClient()
|
|
218
|
-
buf = CommandBuffer(fake, "asset_1", auto_flush_seconds=0.0)
|
|
219
|
-
|
|
220
|
-
buf.call(FormatApply(target={"kind": "block"}, inline={"bold": True}))
|
|
221
|
-
buf.commit()
|
|
222
|
-
|
|
223
|
-
assert len(fake.batches) == 1
|
|
224
|
-
assert _types(fake.batches[0]) == ["FormatApply"]
|
|
225
|
-
assert fake.releases == ["asset_1"]
|
|
226
|
-
buf.close()
|
|
227
|
-
|
|
228
|
-
|
|
229
192
|
def test_idle_timer_auto_flushes() -> None:
|
|
230
193
|
"""A non-zero idle window should flush after inactivity.
|
|
231
194
|
|
|
@@ -319,8 +282,6 @@ def test_flush_all_walks_every_live_buffer() -> None:
|
|
|
319
282
|
flush_all()
|
|
320
283
|
assert len(fake_a.batches) == 1
|
|
321
284
|
assert len(fake_b.batches) == 1
|
|
322
|
-
assert fake_a.releases == ["asset_a"]
|
|
323
|
-
assert fake_b.releases == ["asset_b"]
|
|
324
285
|
buf_a.close()
|
|
325
286
|
buf_b.close()
|
|
326
287
|
|
|
@@ -17,7 +17,7 @@ import requests
|
|
|
17
17
|
from docx import __version__ as SDK_VERSION
|
|
18
18
|
from docx._http_doc import HttpClient, HttpDocHandle
|
|
19
19
|
from docx._timeouts import LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
|
|
20
|
-
from docx.commands import
|
|
20
|
+
from docx.commands import FindReplace, TableSetCell
|
|
21
21
|
from docx.errors import AuthenticationError, DocxError, SessionError
|
|
22
22
|
|
|
23
23
|
|
|
@@ -145,73 +145,6 @@ def test_table_set_cell_batch_uses_long_running_http_timeout() -> None:
|
|
|
145
145
|
assert captured["timeout"] == LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
def test_block_creation_batch_uses_long_running_http_timeout() -> None:
|
|
149
|
-
captured: dict[str, Any] = {}
|
|
150
|
-
|
|
151
|
-
def _capture(url: str, **kwargs: Any) -> _FakeResponse: # noqa: ARG001
|
|
152
|
-
captured["timeout"] = kwargs["timeout"]
|
|
153
|
-
return _FakeResponse(
|
|
154
|
-
status_code=200,
|
|
155
|
-
body=_ok_body(
|
|
156
|
-
[
|
|
157
|
-
{
|
|
158
|
-
"index": 0,
|
|
159
|
-
"type": "CreateParagraph",
|
|
160
|
-
"result": {
|
|
161
|
-
"client_node_id": "p_test",
|
|
162
|
-
"real_node_id": "paragraph_real",
|
|
163
|
-
},
|
|
164
|
-
}
|
|
165
|
-
],
|
|
166
|
-
),
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
client = HttpClient(base_url="https://example.com", api_key="bg_key")
|
|
170
|
-
|
|
171
|
-
with patch.object(client._session, "post", side_effect=_capture):
|
|
172
|
-
result = client.execute_batch(
|
|
173
|
-
"asset_test",
|
|
174
|
-
[
|
|
175
|
-
CreateParagraph(
|
|
176
|
-
text="DLDR",
|
|
177
|
-
at={"kind": "documentEnd"},
|
|
178
|
-
client_node_id="p_test",
|
|
179
|
-
)
|
|
180
|
-
],
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
assert result == [
|
|
184
|
-
{"client_node_id": "p_test", "real_node_id": "paragraph_real"}
|
|
185
|
-
]
|
|
186
|
-
assert captured["timeout"] == LONG_RUNNING_COMMAND_TIMEOUT_SECONDS
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def test_release_asset_posts_to_session_release_endpoint() -> None:
|
|
190
|
-
captured: dict[str, Any] = {}
|
|
191
|
-
|
|
192
|
-
def _capture(url: str, **kwargs: Any) -> _FakeResponse:
|
|
193
|
-
captured["url"] = url
|
|
194
|
-
captured["body"] = json.loads(kwargs["data"].decode("utf-8"))
|
|
195
|
-
captured["auth"] = kwargs["headers"].get("Authorization")
|
|
196
|
-
captured["timeout"] = kwargs["timeout"]
|
|
197
|
-
return _FakeResponse(
|
|
198
|
-
status_code=200,
|
|
199
|
-
body=b'{"assetId":"asset_test","released":true}',
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
client = HttpClient(base_url="https://example.com", api_key="bg_key")
|
|
203
|
-
|
|
204
|
-
with patch.object(client._session, "post", side_effect=_capture):
|
|
205
|
-
client.release_asset("asset_test")
|
|
206
|
-
|
|
207
|
-
assert captured == {
|
|
208
|
-
"url": "https://example.com/docs/asset_test/session/release",
|
|
209
|
-
"body": {},
|
|
210
|
-
"auth": "Bearer bg_key",
|
|
211
|
-
"timeout": 30.0,
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
148
|
def test_deeply_nested_paths() -> None:
|
|
216
149
|
"""``doc.format.paragraph.set_alignment(p)`` walks three levels and
|
|
217
150
|
serializes as ``SetParagraphAlignment``. It's a pure mutation, so it
|
|
@@ -35,15 +35,22 @@ def test_document_iter_inner_content_yields_paragraphs_then_table() -> None:
|
|
|
35
35
|
assert all(isinstance(i, (Paragraph, Table)) for i in items)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def test_document_paragraphs_prefetches_runs_in_one_listing() -> None:
|
|
39
|
+
"""``Document.paragraphs`` issues ONE rich ``blocks.list(includeRuns=True)``
|
|
40
|
+
and hydrates every proxy's text/style/runs from it — so iterating and reading
|
|
41
|
+
per-paragraph properties costs no extra ``getNodeById`` round-trips (the old
|
|
42
|
+
hot path was one query per property per paragraph). See
|
|
43
|
+
``docx-studio/PERFORMANCE_BATCHING_ANALYSIS.md``."""
|
|
39
44
|
install_fake_session()
|
|
40
45
|
from docx import Document
|
|
41
46
|
|
|
42
47
|
doc = Document("asset_fake")
|
|
43
48
|
doc.add_paragraph("Intro")
|
|
49
|
+
doc.add_heading("Section A", level=1)
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
paras = doc.paragraphs
|
|
46
52
|
|
|
53
|
+
# paragraphs() asked the server to inline the run breakdown.
|
|
47
54
|
block_list_calls = [
|
|
48
55
|
params
|
|
49
56
|
for op, params in doc._session.calls()
|
|
@@ -51,7 +58,62 @@ def test_document_paragraphs_uses_lightweight_block_listing() -> None:
|
|
|
51
58
|
and params.get("nodeTypes") == ["paragraph", "heading"]
|
|
52
59
|
]
|
|
53
60
|
assert block_list_calls
|
|
54
|
-
assert block_list_calls[-1]["
|
|
61
|
+
assert block_list_calls[-1]["includeRuns"] is True
|
|
62
|
+
|
|
63
|
+
# Reading text/style/runs off the prefetched proxies issues NO per-paragraph
|
|
64
|
+
# getNodeById round-trips.
|
|
65
|
+
calls_before: int = len(doc._session.calls())
|
|
66
|
+
for p in paras:
|
|
67
|
+
_ = p.text
|
|
68
|
+
_ = p.style
|
|
69
|
+
_ = list(p.runs)
|
|
70
|
+
extra_node_reads = [
|
|
71
|
+
op for op, _ in doc._session.calls()[calls_before:] if op == "getNodeById"
|
|
72
|
+
]
|
|
73
|
+
assert extra_node_reads == []
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_paragraph_text_mutation_invalidates_prefetch_snapshot() -> None:
|
|
77
|
+
"""A content mutation on a prefetched paragraph drops its snapshot, so the
|
|
78
|
+
next read reflects the new state instead of the stale prefetched value."""
|
|
79
|
+
install_fake_session()
|
|
80
|
+
from docx import Document
|
|
81
|
+
|
|
82
|
+
doc = Document("asset_fake")
|
|
83
|
+
doc.add_paragraph("Original")
|
|
84
|
+
|
|
85
|
+
p = doc.paragraphs[0]
|
|
86
|
+
assert p.text == "Original" # served from the prefetch snapshot
|
|
87
|
+
|
|
88
|
+
p.text = "Replaced" # content mutation → snapshot invalidated
|
|
89
|
+
|
|
90
|
+
calls_before: int = len(doc._session.calls())
|
|
91
|
+
assert p.text == "Replaced" # re-read reflects the mutation
|
|
92
|
+
# The fresh read went back to the server (the stale snapshot was dropped).
|
|
93
|
+
assert any(
|
|
94
|
+
op == "getNodeById" for op, _ in doc._session.calls()[calls_before:]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_paragraph_style_set_invalidates_prefetch_snapshot() -> None:
|
|
99
|
+
"""Setting ``.style`` on a prefetched paragraph drops its snapshot, so the
|
|
100
|
+
next ``.style`` read returns the new style — the restyle-loop read-after-write
|
|
101
|
+
(``for p in doc.paragraphs: p.style = ...``) the prefetch optimizes for."""
|
|
102
|
+
install_fake_session()
|
|
103
|
+
from docx import Document
|
|
104
|
+
|
|
105
|
+
doc = Document("asset_fake")
|
|
106
|
+
doc.add_paragraph("Body")
|
|
107
|
+
|
|
108
|
+
p = doc.paragraphs[0]
|
|
109
|
+
_ = p.style # prime the prefetch snapshot
|
|
110
|
+
|
|
111
|
+
p.style = "Heading1" # style mutation → snapshot must be invalidated
|
|
112
|
+
|
|
113
|
+
style_after = p.style
|
|
114
|
+
assert style_after is not None
|
|
115
|
+
# Re-read reflects the new style instead of the stale prefetched value.
|
|
116
|
+
assert style_after.name == "Heading1"
|
|
55
117
|
|
|
56
118
|
|
|
57
119
|
def test_document_tables_uses_lightweight_block_listing() -> None:
|