athena-python-docx 0.7.0__tar.gz → 0.9.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.7.0 → athena_python_docx-0.9.0}/CLAUDE.md +48 -2
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/PKG-INFO +1 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_buffer.py +127 -75
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_http_doc.py +175 -6
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_ptc.py +32 -49
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/commands.py +9 -2
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/document.py +41 -39
- athena_python_docx-0.9.0/docx/errors.py +133 -0
- athena_python_docx-0.9.0/docx/oxml/__init__.py +148 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/table.py +90 -19
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/pyproject.toml +1 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -6
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/17_table_basic.json +4 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -6
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +2 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/23_nested_table.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/35_full_report.json +12 -10
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -100
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -4
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -13
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +2 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +3 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +800 -401
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -19
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -3
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -30
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +18 -10
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -10
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -100
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -4
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -50
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -2
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +90 -66
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +240 -81
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -1
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json +211 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +120 -90
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +18 -15
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +18 -19
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +28 -23
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +32 -21
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +40 -31
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +8 -7
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +64 -32
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -12
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -13
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -8
- athena_python_docx-0.7.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json → athena_python_docx-0.9.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +58 -47
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -2
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/baseline_gaps.json +1 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/compare.py +16 -1
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/reports/GAP_ANALYSIS.md +3 -4
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/reports/gap_report.json +3 -12
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/snapshots/athena_latest.json +374 -59
- athena_python_docx-0.9.0/tests/test_block_not_found_error.py +287 -0
- athena_python_docx-0.9.0/tests/test_cell_text_plain_fastpath.py +60 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_command_dataclasses.py +10 -1
- athena_python_docx-0.9.0/tests/test_insert_deferred.py +138 -0
- athena_python_docx-0.9.0/tests/test_oxml_shim.py +123 -0
- athena_python_docx-0.9.0/tests/test_partial_failure_cascade.py +346 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_ptc.py +246 -10
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/uv.lock +1 -1
- athena_python_docx-0.7.0/docx/errors.py +0 -45
- athena_python_docx-0.7.0/tests/fidelity/op_snapshots/17_table_basic.json +0 -5
- athena_python_docx-0.7.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -181
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/.gitignore +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/README.md +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_http.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/api.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/client.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/comments.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/section.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/settings.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/shape.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/shared.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/docx/typing.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.7.0 → athena_python_docx-0.9.0}/tests/test_zod_wire_contract.py +0 -0
|
@@ -39,6 +39,18 @@ These standard python-docx members don't apply to a Superdoc-backed SDK:
|
|
|
39
39
|
`Comment.tables` — SuperDoc models a comment as a single text body,
|
|
40
40
|
not a `BlockItemContainer`. Use `Comment.text` instead. The
|
|
41
41
|
paragraph/table-bearing surface raises `CommentsNotImplementedError`.
|
|
42
|
+
- **`docx.oxml.*`** — the entire raw-OOXML module tree (``ns.qn``,
|
|
43
|
+
``OxmlElement``, ``CT_*`` classes, etc.) is unavailable because the
|
|
44
|
+
SDK is HTTP-only against a SuperDoc Y.Doc; there is no local lxml
|
|
45
|
+
tree to manipulate. As of 0.8.0, ``docx.oxml`` is a real stub
|
|
46
|
+
package that raises a typed
|
|
47
|
+
:class:`docx.oxml.OxmlNotAvailableError` (subclass of
|
|
48
|
+
``ImportError``) on any attribute access, with an inline pointer at
|
|
49
|
+
the high-level API (``Document.add_paragraph``, ``Run.bold``,
|
|
50
|
+
``_Cell.text``, …) or the typed command surface in
|
|
51
|
+
``docx.commands``. Pre-0.8.0 this used to fail with the stdlib's
|
|
52
|
+
generic ``ModuleNotFoundError: No module named 'docx.oxml'`` and
|
|
53
|
+
agents had to retry to discover the gap.
|
|
42
54
|
|
|
43
55
|
### Phase-3b upstream-blocked surface
|
|
44
56
|
|
|
@@ -149,13 +161,47 @@ This is a **thin HTTP client** that mimics the sync python-docx API.
|
|
|
149
161
|
the context-manager exit drain explicitly; `docx.flush_all()` is
|
|
150
162
|
the Daytona-prelude hook that flushes every live buffer in the
|
|
151
163
|
process.
|
|
152
|
-
-
|
|
153
|
-
|
|
164
|
+
- **Insert deferral (0.8.0+):** `Insert` was removed from the
|
|
165
|
+
response-bearing set because every in-tree caller
|
|
166
|
+
(`Paragraph._insert_run_text_segments`, `Run.add_text`, `_Cell.text`)
|
|
167
|
+
ignores the response — the Run proxy wraps a known client-side range
|
|
168
|
+
and doesn't need the server's echoed node id. Net result: a styled
|
|
169
|
+
run (one `add_run` + 6 format setters) ships as 1 HTTP request
|
|
170
|
+
instead of 2. The remaining response-bearing ops without a client id
|
|
171
|
+
(`ListsCreate`, `ListsAttach`, `CommentsCreate`, `CommentsPatch`,
|
|
154
172
|
`TrackChangesDecide`) keep their eager-flush semantics so callers
|
|
155
173
|
reading back ids still see real data. A `Document.add_paragraph`
|
|
156
174
|
with `style="List Bullet"` flushes the queued CreateParagraph in
|
|
157
175
|
the same batch as the follow-up `ListsCreate` — still one HTTP
|
|
158
176
|
request per logical operation.
|
|
177
|
+
- **Plain-text cell fast path (0.8.0+):** `_Cell.text = value` skips
|
|
178
|
+
the eager `doc.markdownToFragment` query when ``value`` contains no
|
|
179
|
+
markdown control characters (the dominant case for tabular data —
|
|
180
|
+
numbers, labels, currency strings). The fragment is built locally
|
|
181
|
+
and the follow-up `doc.insert` is buffered, so plain cell
|
|
182
|
+
assignments are 0 HTTP requests until the next flush. See
|
|
183
|
+
``docx.table._is_plain_text`` for the trigger predicate.
|
|
184
|
+
- **Partial-failure cascade fix (0.9.0+):** when an HTTP batch raises
|
|
185
|
+
on a 207 partial-failure (e.g. one cell-paragraph mistake mid-script),
|
|
186
|
+
the SDK now rewrites ``proxy_id_refs`` for the ``applied`` prefix
|
|
187
|
+
the server reports as successful — BEFORE re-raising. Pre-0.9.0 the
|
|
188
|
+
rewrite was inside the success-path-only branch, so every Create that
|
|
189
|
+
committed server-side left its Python proxy stuck on the client UUID;
|
|
190
|
+
the next batch shipped dead client UUIDs and the cascade restarted.
|
|
191
|
+
Preview-session ``thread_bafba02b`` turned one cell-paragraph error
|
|
192
|
+
into thirteen downstream "Block not found" failures + a
|
|
193
|
+
``DOCUMENT_IDENTITY_CONFLICT`` before this fix. The path goes
|
|
194
|
+
``_http_post_json`` (attaches ``applied[]`` via
|
|
195
|
+
``DocxError.with_partial_applied``) → ``CommandBuffer.flush`` /
|
|
196
|
+
``_eager_flush_with`` (catches ``DocxError``, drains the prefix
|
|
197
|
+
through ``_apply_proxy_id_rewrites``, re-raises). See
|
|
198
|
+
``tests/test_partial_failure_cascade.py`` for the contract.
|
|
199
|
+
- **Table-query hint (0.9.0+):** ``BlockNotFoundError`` on
|
|
200
|
+
``TablesGet`` / ``TablesGetCells`` / etc. used to fall through with
|
|
201
|
+
no hint (the cell-paragraph workaround doesn't apply). Now carries
|
|
202
|
+
a dedicated "stale client-table-UUID" hint pointing at the actual
|
|
203
|
+
recovery (``doc.save()`` to drain pending + re-anchor proxies). See
|
|
204
|
+
``_TABLE_CLIENT_ID_HINT`` in ``docx/_http_doc.py``.
|
|
159
205
|
- The path-proxy in `_http_doc.py` is an internal translation layer:
|
|
160
206
|
call sites that look like `await self._session.doc.create.paragraph(p)`
|
|
161
207
|
resolve to `CommandBuffer.call(CreateParagraph(**p))`. Rewriting call
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
|
|
5
5
|
Project-URL: Homepage, https://athenaintelligence.ai
|
|
6
6
|
Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
|
|
@@ -35,11 +35,94 @@ from typing import TYPE_CHECKING, Any
|
|
|
35
35
|
|
|
36
36
|
from docx import _ptc
|
|
37
37
|
from docx.commands import Command, is_response_bearing, must_flush_immediately
|
|
38
|
+
from docx.errors import DocxError
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _apply_proxy_id_rewrites(
|
|
42
|
+
results: list[Any],
|
|
43
|
+
proxy_id_refs: "dict[str, list[tuple[object, str]]]",
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Walk ``results`` and rewrite registered proxy ids from client UUIDs
|
|
46
|
+
to the real SuperDoc ids the applier echoed back.
|
|
47
|
+
|
|
48
|
+
Shared by the success path in :meth:`CommandBuffer.flush` /
|
|
49
|
+
:meth:`_eager_flush_with` AND by the **partial-failure recovery
|
|
50
|
+
path** (the SDK now reads ``DocxError.partial_applied`` on raise
|
|
51
|
+
and applies the same rewrite for the prefix that committed before
|
|
52
|
+
the server hit its first failure). Without this, every Create that
|
|
53
|
+
succeeded server-side would leave its Python proxy stuck on the
|
|
54
|
+
client UUID and the next batch would ship the dead id straight
|
|
55
|
+
back to the server — that cascade is what turned one cell-
|
|
56
|
+
paragraph mistake in preview-session ``thread_bafba02b`` into
|
|
57
|
+
thirteen downstream "Block not found" errors.
|
|
58
|
+
|
|
59
|
+
Tolerant of legacy / transitional applier results that don't
|
|
60
|
+
include ``client_node_id`` / ``real_node_id`` echoes — we simply
|
|
61
|
+
leave those proxies on their client id; the applier's per-batch
|
|
62
|
+
``clientIdMap`` rewrite still resolves them server-side on the
|
|
63
|
+
*next* call.
|
|
64
|
+
"""
|
|
65
|
+
if not proxy_id_refs:
|
|
66
|
+
return
|
|
67
|
+
for result in results:
|
|
68
|
+
if not isinstance(result, dict):
|
|
69
|
+
continue
|
|
70
|
+
for cli_key, real_key in (
|
|
71
|
+
("client_node_id", "real_node_id"),
|
|
72
|
+
("client_entity_id", "real_entity_id"),
|
|
73
|
+
):
|
|
74
|
+
cli = result.get(cli_key)
|
|
75
|
+
real = result.get(real_key)
|
|
76
|
+
if not (isinstance(cli, str) and isinstance(real, str)):
|
|
77
|
+
continue
|
|
78
|
+
refs = proxy_id_refs.pop(cli, [])
|
|
79
|
+
for proxy, attr in refs:
|
|
80
|
+
try:
|
|
81
|
+
setattr(proxy, attr, real)
|
|
82
|
+
except Exception: # noqa: BLE001
|
|
83
|
+
# A proxy that rejected setattr (slots without the
|
|
84
|
+
# attr, frozen dataclass, etc.) silently keeps its
|
|
85
|
+
# client_id — the applier's rewriter will still
|
|
86
|
+
# resolve it correctly on the next batch.
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _extract_partial_results(exc: BaseException) -> list[Any]:
|
|
91
|
+
"""Pull per-command ``result`` dicts out of a partial-failure error.
|
|
92
|
+
|
|
93
|
+
The HTTP layer attaches the server's ``applied`` array to
|
|
94
|
+
:class:`DocxError` (and subclasses) as ``partial_applied``. Each
|
|
95
|
+
entry is the wire-format ``{index, type, result}`` triple. We
|
|
96
|
+
return just the ``result`` dicts so the rewrite path can share its
|
|
97
|
+
success-path code without caring whether it's running on a happy
|
|
98
|
+
batch or a salvaged prefix.
|
|
99
|
+
"""
|
|
100
|
+
applied = getattr(exc, "partial_applied", None)
|
|
101
|
+
if not isinstance(applied, list):
|
|
102
|
+
return []
|
|
103
|
+
out: list[Any] = []
|
|
104
|
+
for entry in applied:
|
|
105
|
+
if isinstance(entry, dict):
|
|
106
|
+
result = entry.get("result")
|
|
107
|
+
if isinstance(result, dict):
|
|
108
|
+
out.append(result)
|
|
109
|
+
return out
|
|
38
110
|
|
|
39
111
|
if TYPE_CHECKING:
|
|
40
112
|
from docx._http_doc import HttpClient
|
|
41
113
|
|
|
42
114
|
|
|
115
|
+
# Only commands whose class name is in this set produce PTC sub-tool-cards.
|
|
116
|
+
# Every Command subclass is a candidate, but each emit is now a synchronous
|
|
117
|
+
# HTTP POST to agora (see ``_ptc._send``), so emitting one card per low-level
|
|
118
|
+
# mutation produces hundreds of sub-cards per script and pays full network
|
|
119
|
+
# RTT on every one. The allow-list keeps the per-action signal — one card
|
|
120
|
+
# per logical paragraph creation — without the spam or the cumulative
|
|
121
|
+
# latency. Asset-level creation events (``CreateDocument``) are emitted
|
|
122
|
+
# from ``Document.create`` directly, not through this buffer path.
|
|
123
|
+
_PTC_EMIT_TOOLS: frozenset[str] = frozenset({"CreateParagraph"})
|
|
124
|
+
|
|
125
|
+
|
|
43
126
|
def _ptc_emit_end_batch(cmds: list[Command], *, is_error: bool) -> None:
|
|
44
127
|
"""Emit a PTC ``end`` event for every cmd that received a ``begin``.
|
|
45
128
|
|
|
@@ -85,9 +168,7 @@ def _unregister(buffer: "CommandBuffer") -> None:
|
|
|
85
168
|
"""
|
|
86
169
|
with _registry_lock:
|
|
87
170
|
_active_buffers[:] = [
|
|
88
|
-
ref
|
|
89
|
-
for ref in _active_buffers
|
|
90
|
-
if (b := ref()) is not None and b is not buffer
|
|
171
|
+
ref for ref in _active_buffers if (b := ref()) is not None and b is not buffer
|
|
91
172
|
]
|
|
92
173
|
|
|
93
174
|
|
|
@@ -170,14 +251,10 @@ class CommandBuffer:
|
|
|
170
251
|
# real nodeId after flush. Populated by ``add_paragraph`` /
|
|
171
252
|
# ``add_heading`` / etc. when they queue a Create with a
|
|
172
253
|
# client-assigned id. Drained on every flush.
|
|
173
|
-
self._proxy_id_refs: dict[
|
|
174
|
-
str, list[tuple[object, str]]
|
|
175
|
-
] = {}
|
|
254
|
+
self._proxy_id_refs: dict[str, list[tuple[object, str]]] = {}
|
|
176
255
|
_register(self)
|
|
177
256
|
|
|
178
|
-
def register_proxy_id_ref(
|
|
179
|
-
self, client_id: str, proxy: object, attr: str = "_node_id"
|
|
180
|
-
) -> None:
|
|
257
|
+
def register_proxy_id_ref(self, client_id: str, proxy: object, attr: str = "_node_id") -> None:
|
|
181
258
|
"""Register that ``proxy.<attr>`` should be rewritten from
|
|
182
259
|
``client_id`` to the real node id once the queue flushes.
|
|
183
260
|
|
|
@@ -215,9 +292,7 @@ class CommandBuffer:
|
|
|
215
292
|
``"direct"``, ``"tracked"``, or ``None`` to clear.
|
|
216
293
|
"""
|
|
217
294
|
if mode is not None and mode not in ("direct", "tracked"):
|
|
218
|
-
raise ValueError(
|
|
219
|
-
f"change_mode must be 'direct', 'tracked', or None; got {mode!r}"
|
|
220
|
-
)
|
|
295
|
+
raise ValueError(f"change_mode must be 'direct', 'tracked', or None; got {mode!r}")
|
|
221
296
|
with self._lock:
|
|
222
297
|
current = self._change_mode
|
|
223
298
|
if current == mode:
|
|
@@ -269,16 +344,19 @@ class CommandBuffer:
|
|
|
269
344
|
f"CommandBuffer for {self._asset_id} is closed",
|
|
270
345
|
)
|
|
271
346
|
|
|
272
|
-
# PTC begin:
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
347
|
+
# PTC begin: emit only for commands in the allow-list. Other
|
|
348
|
+
# commands still flow through the buffer (and through the batched
|
|
349
|
+
# HTTP POST), they just don't produce a sub-tool-card. ``emit_end``
|
|
350
|
+
# is automatically skipped because no ``_ptc_call_id`` was set.
|
|
351
|
+
if type(cmd).__name__ in _PTC_EMIT_TOOLS:
|
|
352
|
+
try:
|
|
353
|
+
cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
|
|
354
|
+
type(cmd).__name__,
|
|
355
|
+
cmd.to_dict(),
|
|
356
|
+
asset_id=self._asset_id,
|
|
357
|
+
)
|
|
358
|
+
except Exception: # noqa: BLE001
|
|
359
|
+
pass
|
|
282
360
|
|
|
283
361
|
if must_flush_immediately(cmd) and not self._has_client_id(cmd):
|
|
284
362
|
return self._eager_flush_with(cmd)
|
|
@@ -300,12 +378,9 @@ class CommandBuffer:
|
|
|
300
378
|
legacy callers) keep their eager-flush semantics so callers
|
|
301
379
|
that read the response still see real data.
|
|
302
380
|
"""
|
|
303
|
-
return (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
getattr(cmd, "client_node_id", None) is not None
|
|
307
|
-
or getattr(cmd, "client_entity_id", None) is not None
|
|
308
|
-
)
|
|
381
|
+
return is_response_bearing(cmd) and (
|
|
382
|
+
getattr(cmd, "client_node_id", None) is not None
|
|
383
|
+
or getattr(cmd, "client_entity_id", None) is not None
|
|
309
384
|
)
|
|
310
385
|
|
|
311
386
|
def flush(self) -> list[Any]:
|
|
@@ -339,37 +414,22 @@ class CommandBuffer:
|
|
|
339
414
|
change_mode=change_mode,
|
|
340
415
|
user=user,
|
|
341
416
|
)
|
|
417
|
+
except DocxError as exc:
|
|
418
|
+
# Partial-failure cascade protection: even though the batch
|
|
419
|
+
# raised, the server's ``applied`` prefix tells us which
|
|
420
|
+
# commands DID commit before the failure. Rewrite the
|
|
421
|
+
# corresponding proxy ids before re-raising so the next
|
|
422
|
+
# batch ships REAL ids for the survivors (vs. dead client
|
|
423
|
+
# UUIDs that would cascade into another "Block not found").
|
|
424
|
+
# See ``_apply_proxy_id_rewrites`` for the full rationale.
|
|
425
|
+
_apply_proxy_id_rewrites(_extract_partial_results(exc), proxy_id_refs)
|
|
426
|
+
_ptc_emit_end_batch(pending, is_error=True)
|
|
427
|
+
raise
|
|
342
428
|
except Exception:
|
|
343
429
|
_ptc_emit_end_batch(pending, is_error=True)
|
|
344
430
|
raise
|
|
345
431
|
_ptc_emit_end_batch(pending, is_error=False)
|
|
346
|
-
|
|
347
|
-
# SuperDoc ids the applier echoed back. Defensive: tolerate
|
|
348
|
-
# missing fields (legacy or transitional applier without the
|
|
349
|
-
# client-id support).
|
|
350
|
-
if proxy_id_refs:
|
|
351
|
-
for result in results:
|
|
352
|
-
if not isinstance(result, dict):
|
|
353
|
-
continue
|
|
354
|
-
for cli_key, real_key in (
|
|
355
|
-
("client_node_id", "real_node_id"),
|
|
356
|
-
("client_entity_id", "real_entity_id"),
|
|
357
|
-
):
|
|
358
|
-
cli = result.get(cli_key)
|
|
359
|
-
real = result.get(real_key)
|
|
360
|
-
if not (isinstance(cli, str) and isinstance(real, str)):
|
|
361
|
-
continue
|
|
362
|
-
refs = proxy_id_refs.pop(cli, [])
|
|
363
|
-
for proxy, attr in refs:
|
|
364
|
-
try:
|
|
365
|
-
setattr(proxy, attr, real)
|
|
366
|
-
except Exception: # noqa: BLE001
|
|
367
|
-
# A proxy that rejected setattr (slots without
|
|
368
|
-
# the attr, frozen dataclass, etc.) silently
|
|
369
|
-
# keeps its client_id — the rewriter in the
|
|
370
|
-
# applier will still resolve it correctly
|
|
371
|
-
# for the next batch.
|
|
372
|
-
pass
|
|
432
|
+
_apply_proxy_id_rewrites(results, proxy_id_refs)
|
|
373
433
|
return results
|
|
374
434
|
|
|
375
435
|
def close(self) -> None:
|
|
@@ -409,30 +469,22 @@ class CommandBuffer:
|
|
|
409
469
|
change_mode=change_mode,
|
|
410
470
|
user=user,
|
|
411
471
|
)
|
|
472
|
+
except DocxError as exc:
|
|
473
|
+
# Same partial-failure cascade protection as :meth:`flush` —
|
|
474
|
+
# rewrite proxy ids for the prefix the server reports as
|
|
475
|
+
# ``applied`` before re-raising. Critical here because the
|
|
476
|
+
# eager-flush path is hit by every query in user code (e.g.
|
|
477
|
+
# ``table.cell(0, 0)`` → ``tables.get``), so a failure on
|
|
478
|
+
# the trailing ``cmd`` would otherwise abandon every
|
|
479
|
+
# buffered Create's rewrite at once.
|
|
480
|
+
_apply_proxy_id_rewrites(_extract_partial_results(exc), proxy_id_refs)
|
|
481
|
+
_ptc_emit_end_batch(all_cmds, is_error=True)
|
|
482
|
+
raise
|
|
412
483
|
except Exception:
|
|
413
484
|
_ptc_emit_end_batch(all_cmds, is_error=True)
|
|
414
485
|
raise
|
|
415
486
|
_ptc_emit_end_batch(all_cmds, is_error=False)
|
|
416
|
-
|
|
417
|
-
# there for the contract.
|
|
418
|
-
if proxy_id_refs:
|
|
419
|
-
for result in results:
|
|
420
|
-
if not isinstance(result, dict):
|
|
421
|
-
continue
|
|
422
|
-
for cli_key, real_key in (
|
|
423
|
-
("client_node_id", "real_node_id"),
|
|
424
|
-
("client_entity_id", "real_entity_id"),
|
|
425
|
-
):
|
|
426
|
-
cli = result.get(cli_key)
|
|
427
|
-
real = result.get(real_key)
|
|
428
|
-
if not (isinstance(cli, str) and isinstance(real, str)):
|
|
429
|
-
continue
|
|
430
|
-
refs = proxy_id_refs.pop(cli, [])
|
|
431
|
-
for proxy, attr in refs:
|
|
432
|
-
try:
|
|
433
|
-
setattr(proxy, attr, real)
|
|
434
|
-
except Exception: # noqa: BLE001
|
|
435
|
-
pass
|
|
487
|
+
_apply_proxy_id_rewrites(results, proxy_id_refs)
|
|
436
488
|
if not results:
|
|
437
489
|
return {}
|
|
438
490
|
return results[-1]
|
|
@@ -112,6 +112,7 @@ from docx.commands import (
|
|
|
112
112
|
)
|
|
113
113
|
from docx.errors import (
|
|
114
114
|
AuthenticationError,
|
|
115
|
+
BlockNotFoundError,
|
|
115
116
|
DocxError,
|
|
116
117
|
NotFoundError,
|
|
117
118
|
SessionError,
|
|
@@ -134,6 +135,124 @@ _NOT_FOUND_ERROR_NAMES: frozenset[str] = frozenset(
|
|
|
134
135
|
)
|
|
135
136
|
|
|
136
137
|
|
|
138
|
+
def _looks_like_block_not_found(err_obj: dict) -> bool:
|
|
139
|
+
"""Detect SuperDoc's ``Block "<type>:<id>" was not found`` shape.
|
|
140
|
+
|
|
141
|
+
Triggers the typed :class:`BlockNotFoundError` so agent code can
|
|
142
|
+
distinguish a missing-block miss (cell-inner-paragraph addressing
|
|
143
|
+
bug, stale id from a different session) from a generic transport
|
|
144
|
+
or validation error.
|
|
145
|
+
"""
|
|
146
|
+
msg = err_obj.get("message")
|
|
147
|
+
if not isinstance(msg, str):
|
|
148
|
+
return False
|
|
149
|
+
lower = msg.lower()
|
|
150
|
+
if "not found" not in lower:
|
|
151
|
+
return False
|
|
152
|
+
# Be conservative: require either ``block "`` (the SuperDoc CLI's
|
|
153
|
+
# quoted-id format) or ``block <id>`` followed by ``not found``.
|
|
154
|
+
return 'block "' in lower or ('block ' in lower and ' was not found' in lower)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Commands that target a paragraph block (or its inline range) and
|
|
158
|
+
# therefore trip the SuperDoc 1.8.1 cell-paragraph addressing gap when
|
|
159
|
+
# the target paragraph is nested inside a ``tableCell``. Used by the
|
|
160
|
+
# ``BlockNotFoundError`` hint logic to decide whether to surface the
|
|
161
|
+
# materialization workaround. Membership is checked against the
|
|
162
|
+
# ``err_obj["type"]`` field that ``apps/api`` echoes back on partial
|
|
163
|
+
# failure, so the gate covers both error-message shapes the SuperDoc
|
|
164
|
+
# CLI emits:
|
|
165
|
+
#
|
|
166
|
+
# * ``Block "paragraph:<uuid>" was not found.`` (SetParagraph*, etc.)
|
|
167
|
+
# * ``Block "<uuid>" not found.`` (Insert)
|
|
168
|
+
#
|
|
169
|
+
# Without this gate, the hint either fired for stale-id misses on
|
|
170
|
+
# unrelated command types (Greptile #20555) or skipped the Insert form
|
|
171
|
+
# entirely (preview session
|
|
172
|
+
# thread_b952794f, where 3 of 4 failures were bare-UUID Inserts and
|
|
173
|
+
# the agent burned 4 retries without ever seeing the workaround).
|
|
174
|
+
_PARAGRAPH_TARGETING_COMMANDS: frozenset[str] = frozenset(
|
|
175
|
+
{
|
|
176
|
+
"Insert",
|
|
177
|
+
"FormatApply",
|
|
178
|
+
"SetParagraphAlignment",
|
|
179
|
+
"ClearParagraphAlignment",
|
|
180
|
+
"SetParagraphStyle",
|
|
181
|
+
"SetParagraphIndentation",
|
|
182
|
+
"ClearParagraphIndentation",
|
|
183
|
+
"SetParagraphSpacing",
|
|
184
|
+
"ClearParagraphSpacing",
|
|
185
|
+
"SetParagraphKeepOptions",
|
|
186
|
+
"SetParagraphFlowOptions",
|
|
187
|
+
"SetParagraphTabStop",
|
|
188
|
+
"ClearParagraphTabStops",
|
|
189
|
+
"Replace",
|
|
190
|
+
"InsertLineBreak",
|
|
191
|
+
"InsertTab",
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
_CELL_PARAGRAPH_HINT: str = (
|
|
197
|
+
"\n\nHint: SuperDoc 1.8.1 cannot format paragraphs nested inside "
|
|
198
|
+
"table cells via SetParagraphAlignment / SetParagraphStyle / "
|
|
199
|
+
"SetParagraphIndentation / SetParagraphSpacing / doc.insert with "
|
|
200
|
+
"a paragraph-block target, or any FormatApply on a cell-inner run. "
|
|
201
|
+
"The cell's inner paragraph id is returned by cell.getNodeById but "
|
|
202
|
+
"isn't a top-level addressable block. Materialize the cell's "
|
|
203
|
+
'paragraph first via ``cell.text = "value"``, then re-read '
|
|
204
|
+
"``cell.paragraphs[0]`` and apply format ops to that post-"
|
|
205
|
+
"materialization Paragraph proxy. Tracked upstream at "
|
|
206
|
+
"docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md § 'cell-inner paragraph "
|
|
207
|
+
"addressing'."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ``tables.get`` / ``tables.getCells`` / ``tables.getProperties`` carry
|
|
212
|
+
# the table id at the top-level ``nodeId`` field — and the agent code
|
|
213
|
+
# that triggers them (``table.cell(0, 0)``, ``table.rows``, etc.) runs
|
|
214
|
+
# *immediately* after ``doc.add_table(...)``. The first such query
|
|
215
|
+
# triggers an eager flush that drains the buffered CreateTable in the
|
|
216
|
+
# same HTTP batch, so the applier's per-batch ``clientIdMap`` rewrite
|
|
217
|
+
# normally catches it. But when an EARLIER batch (e.g. a doomed cell-
|
|
218
|
+
# paragraph mutation a few lines up in the user script) raised before
|
|
219
|
+
# the SDK could rewrite ``proxy_id_refs``, the Table proxy still holds
|
|
220
|
+
# the client UUID. The next batch ships the dead id straight to SuperDoc
|
|
221
|
+
# and we land here. The hint points the agent at the canonical recovery
|
|
222
|
+
# pattern (force a save() to reseat the proxy refs, or split the
|
|
223
|
+
# offending mutation into its own execution).
|
|
224
|
+
_TABLE_QUERY_COMMANDS: frozenset[str] = frozenset(
|
|
225
|
+
{
|
|
226
|
+
"TablesGet",
|
|
227
|
+
"TablesGetCells",
|
|
228
|
+
"TablesGetProperties",
|
|
229
|
+
"TablesSetStyle",
|
|
230
|
+
"TablesSetLayout",
|
|
231
|
+
"TablesSetTableOptions",
|
|
232
|
+
"TablesSetCellProperties",
|
|
233
|
+
"TablesSetColumnWidth",
|
|
234
|
+
"TablesSetRowHeight",
|
|
235
|
+
"TablesInsertRow",
|
|
236
|
+
"TablesInsertColumn",
|
|
237
|
+
"TablesMergeCells",
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
_TABLE_CLIENT_ID_HINT: str = (
|
|
243
|
+
"\n\nHint: this looks like a stale client-side table UUID "
|
|
244
|
+
'(``t_xxxxxxxxxxxx``). Either an earlier batch in this execution '
|
|
245
|
+
"raised before the SDK could rewrite the Table proxy's id from the "
|
|
246
|
+
"client UUID to the real SuperDoc id, OR the table belongs to a "
|
|
247
|
+
"prior execution whose state has been discarded. Recover by calling "
|
|
248
|
+
"``doc.save()`` to drain pending mutations + re-anchor live "
|
|
249
|
+
"proxies, then re-query via ``doc.tables`` to get fresh proxies "
|
|
250
|
+
"with real ids. If you saw a cell-paragraph error in the same "
|
|
251
|
+
"execution, address that first — its partial-failure is what "
|
|
252
|
+
"abandoned the rewrite."
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
137
256
|
def _looks_like_not_found(parsed: dict) -> bool:
|
|
138
257
|
"""Inspect a docx-studio partial-failure dict and decide whether it
|
|
139
258
|
describes a "no such entity" miss (vs. a real transport / validation
|
|
@@ -280,19 +399,69 @@ def _http_post_json(
|
|
|
280
399
|
parsed = json.loads(body) if body else {}
|
|
281
400
|
except json.JSONDecodeError:
|
|
282
401
|
parsed = {"raw": body}
|
|
402
|
+
# Extract the ``applied`` prefix so callers can rewrite
|
|
403
|
+
# ``proxy_id_refs`` for commands that DID succeed before the
|
|
404
|
+
# batch hit its first failure. Without this, the SDK throws
|
|
405
|
+
# away every successful Create's client-UUID → real-id mapping
|
|
406
|
+
# the moment ONE command fails, and the next batch keeps
|
|
407
|
+
# shipping dead client UUIDs. That cascade turned the preview-
|
|
408
|
+
# session thread_bafba02b's first cell-paragraph mistake into
|
|
409
|
+
# thirteen downstream "Block not found" errors and finally a
|
|
410
|
+
# ``DOCUMENT_IDENTITY_CONFLICT``. The structure is wire-shape:
|
|
411
|
+
# ``[{index, type, result: {client_node_id, real_node_id, …}}]``.
|
|
412
|
+
applied_raw = parsed.get("applied") if isinstance(parsed, dict) else None
|
|
413
|
+
partial_applied: list[dict] = []
|
|
414
|
+
if isinstance(applied_raw, list):
|
|
415
|
+
partial_applied = [a for a in applied_raw if isinstance(a, dict)]
|
|
283
416
|
# If the failing command's error looks like "no such entity",
|
|
284
417
|
# raise a typed :class:`NotFoundError` so speculative-read call
|
|
285
418
|
# sites (``Comments.get``) can coerce it to ``None`` without
|
|
286
419
|
# swallowing real transport / validation failures.
|
|
287
420
|
err_obj = parsed.get("error") if isinstance(parsed, dict) else None
|
|
288
|
-
if isinstance(err_obj, dict)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
421
|
+
if isinstance(err_obj, dict):
|
|
422
|
+
base_msg = f"docx-studio batch reported a partial failure: {parsed!r}"
|
|
423
|
+
# ``BlockNotFoundError`` is the narrower miss — caller is most
|
|
424
|
+
# likely targeting a cell-inner paragraph or stale-session
|
|
425
|
+
# block id. Surface the typed exception plus an agent-readable
|
|
426
|
+
# workaround so the next attempt doesn't repeat the same
|
|
427
|
+
# mistake.
|
|
428
|
+
#
|
|
429
|
+
# The cell-paragraph hint applies when the failing command is
|
|
430
|
+
# a paragraph-targeting op (Insert, FormatApply, SetParagraph*,
|
|
431
|
+
# …). We can't gate on the ``paragraph:`` prefix alone — the
|
|
432
|
+
# bare-UUID ``Block "<uuid>" not found.`` shape that SuperDoc's
|
|
433
|
+
# CLI emits for ``Insert`` failures is the dominant form of
|
|
434
|
+
# this bug in practice (preview-session thread_b952794f hit
|
|
435
|
+
# it 3 of 4 times without ever seeing the workaround under the
|
|
436
|
+
# prefix-only gate).
|
|
437
|
+
if _looks_like_block_not_found(err_obj):
|
|
438
|
+
cmd_type = err_obj.get("type")
|
|
439
|
+
paragraph_targeting = (
|
|
440
|
+
isinstance(cmd_type, str)
|
|
441
|
+
and cmd_type in _PARAGRAPH_TARGETING_COMMANDS
|
|
442
|
+
)
|
|
443
|
+
table_query = (
|
|
444
|
+
isinstance(cmd_type, str)
|
|
445
|
+
and cmd_type in _TABLE_QUERY_COMMANDS
|
|
446
|
+
)
|
|
447
|
+
if paragraph_targeting:
|
|
448
|
+
hint = _CELL_PARAGRAPH_HINT
|
|
449
|
+
elif table_query:
|
|
450
|
+
hint = _TABLE_CLIENT_ID_HINT
|
|
451
|
+
else:
|
|
452
|
+
hint = ""
|
|
453
|
+
raise BlockNotFoundError(
|
|
454
|
+
base_msg + hint,
|
|
455
|
+
payload=err_obj,
|
|
456
|
+
).with_partial_applied(partial_applied)
|
|
457
|
+
if _looks_like_not_found(err_obj):
|
|
458
|
+
raise NotFoundError(
|
|
459
|
+
base_msg,
|
|
460
|
+
payload=err_obj,
|
|
461
|
+
).with_partial_applied(partial_applied)
|
|
293
462
|
raise DocxError(
|
|
294
463
|
f"docx-studio batch reported a partial failure: {parsed!r}",
|
|
295
|
-
)
|
|
464
|
+
).with_partial_applied(partial_applied)
|
|
296
465
|
|
|
297
466
|
if 200 <= resp.status_code < 300:
|
|
298
467
|
try:
|