athena-python-docx 0.8.0__tar.gz → 0.10.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.8.0 → athena_python_docx-0.10.0}/CLAUDE.md +40 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/PKG-INFO +1 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/_buffer.py +127 -75
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/_http_doc.py +144 -22
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/_ptc.py +32 -49
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/api.py +56 -8
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/commands.py +28 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/document.py +86 -78
- athena_python_docx-0.10.0/docx/errors.py +166 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/table.py +90 -48
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/pyproject.toml +1 -1
- athena_python_docx-0.10.0/scripts/smoke_test_block_not_found.py +175 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/fake_session.py +68 -0
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +12 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -1
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/18_table_cell_text.json +9 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -3
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +1 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/23_nested_table.json +1 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +1 -3
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -2
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/35_full_report.json +9 -33
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/40_large_table_10x10.json +103 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +1 -5
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/49_resume_layout.json +3 -12
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +1 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +12 -48
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +1 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +793 -1993
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/68_invoice.json +62 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/72_legal_contract.json +6 -24
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +20 -80
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +9 -27
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +24 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -1
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/91_many_small_tables.json +153 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +1 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +3 -12
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/ex04_50x50_table.json +53 -0
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +5 -0
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/mega10_api_documentation.json → athena_python_docx-0.10.0/tests/fidelity/op_snapshots/ex10_complex_bom.json +126 -113
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +80 -320
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -1
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json → athena_python_docx-0.10.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json +36 -69
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +84 -336
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +13 -52
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +18 -72
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +21 -98
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +20 -88
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json → athena_python_docx-0.10.0/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +44 -75
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +8 -36
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json → athena_python_docx-0.10.0/tests/fidelity/op_snapshots/mega10_api_documentation.json +104 -97
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +12 -36
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +12 -36
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/rw08_table_merged_header.json +13 -0
- athena_python_docx-0.10.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +148 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +1 -4
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/baseline_gaps.json +1 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/reports/GAP_ANALYSIS.md +1 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/reports/gap_report.json +1 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/snapshots/athena_latest.json +201 -1
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_block_not_found_error.py +52 -11
- athena_python_docx-0.10.0/tests/test_document_asset_id_property.py +52 -0
- athena_python_docx-0.10.0/tests/test_document_factory_validation.py +52 -0
- athena_python_docx-0.10.0/tests/test_e2e_partial_failure_cascade.py +278 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_parity_misc.py +4 -2
- athena_python_docx-0.10.0/tests/test_partial_failure_cascade.py +346 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_ptc.py +246 -10
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_document_audit.py +55 -61
- athena_python_docx-0.10.0/tests/test_table_set_cell_perf.py +127 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_wire_contract.py +9 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/uv.lock +1 -1
- athena_python_docx-0.8.0/docx/errors.py +0 -88
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -24
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -27
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -403
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/68_invoice.json +0 -114
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -54
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -453
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -203
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -8
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -596
- athena_python_docx-0.8.0/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -35
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/.gitignore +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/README.md +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/_http.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/client.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/comments.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/section.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/settings.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/shape.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/shared.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/docx/typing.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.8.0 → athena_python_docx-0.10.0}/tests/test_zod_wire_contract.py +0 -0
|
@@ -88,13 +88,28 @@ not file-backed. Each is documented in the relevant docstring.
|
|
|
88
88
|
hits `POST {base_url}/docs/empty`. The constructor positional-arg
|
|
89
89
|
shape (`Document(asset_id)`) is preserved for parity.
|
|
90
90
|
|
|
91
|
-
- **`Document.
|
|
92
|
-
|
|
91
|
+
- **`Document.asset_id: str`** read-only property — the Athena asset
|
|
92
|
+
id this Document is bound to (`asset_<uuid>`). Agent-callable so
|
|
93
|
+
the typical pattern `doc = Document.create(...); print(doc.asset_id)`
|
|
94
|
+
works without reaching into `doc._session._asset_id`. Stock
|
|
95
|
+
python-docx has no analogue (it operates on local files), so this
|
|
96
|
+
is an Athena-only addition. Mirrors the parallel public accessors
|
|
97
|
+
on the other studios: `Workbook.workbook_id` (xlsx) and
|
|
98
|
+
`Presentation.deck_id` (pptx).
|
|
99
|
+
|
|
100
|
+
- **`Document.save(path_or_stream=None)`** — argument is optional and,
|
|
101
|
+
when supplied, raises
|
|
102
|
+
:class:`docx.errors.LocalSaveTargetNotSupportedError`. Stock
|
|
103
|
+
python-docx requires it and `TypeError`s on no-arg, but in an
|
|
93
104
|
asset-backed SDK there is no local file to write — writes are always
|
|
94
105
|
in-place against the Y.Doc. Forcing the upstream signature broke
|
|
95
106
|
every agent invocation that reflexively called `doc.save()` (a
|
|
96
|
-
near-universal Python pattern), so
|
|
97
|
-
|
|
107
|
+
near-universal Python pattern), so the no-arg form is supported.
|
|
108
|
+
When a path or stream IS supplied, the SDK cannot fulfill the
|
|
109
|
+
implied "write bytes to this target" contract, so the call raises
|
|
110
|
+
loudly rather than silently flushing — agents must use Olympus's
|
|
111
|
+
Export DOCX (which goes through the SuperDoc session that already
|
|
112
|
+
has the bytes) instead of expecting a local file.
|
|
98
113
|
|
|
99
114
|
- **`Document.track_revisions: bool`** — when `True`, all subsequent
|
|
100
115
|
mutations are recorded as tracked revisions instead of direct edits.
|
|
@@ -181,6 +196,27 @@ This is a **thin HTTP client** that mimics the sync python-docx API.
|
|
|
181
196
|
and the follow-up `doc.insert` is buffered, so plain cell
|
|
182
197
|
assignments are 0 HTTP requests until the next flush. See
|
|
183
198
|
``docx.table._is_plain_text`` for the trigger predicate.
|
|
199
|
+
- **Partial-failure cascade fix (0.9.0+):** when an HTTP batch raises
|
|
200
|
+
on a 207 partial-failure (e.g. one cell-paragraph mistake mid-script),
|
|
201
|
+
the SDK now rewrites ``proxy_id_refs`` for the ``applied`` prefix
|
|
202
|
+
the server reports as successful — BEFORE re-raising. Pre-0.9.0 the
|
|
203
|
+
rewrite was inside the success-path-only branch, so every Create that
|
|
204
|
+
committed server-side left its Python proxy stuck on the client UUID;
|
|
205
|
+
the next batch shipped dead client UUIDs and the cascade restarted.
|
|
206
|
+
Preview-session ``thread_bafba02b`` turned one cell-paragraph error
|
|
207
|
+
into thirteen downstream "Block not found" failures + a
|
|
208
|
+
``DOCUMENT_IDENTITY_CONFLICT`` before this fix. The path goes
|
|
209
|
+
``_http_post_json`` (attaches ``applied[]`` via
|
|
210
|
+
``DocxError.with_partial_applied``) → ``CommandBuffer.flush`` /
|
|
211
|
+
``_eager_flush_with`` (catches ``DocxError``, drains the prefix
|
|
212
|
+
through ``_apply_proxy_id_rewrites``, re-raises). See
|
|
213
|
+
``tests/test_partial_failure_cascade.py`` for the contract.
|
|
214
|
+
- **Table-query hint (0.9.0+):** ``BlockNotFoundError`` on
|
|
215
|
+
``TablesGet`` / ``TablesGetCells`` / etc. used to fall through with
|
|
216
|
+
no hint (the cell-paragraph workaround doesn't apply). Now carries
|
|
217
|
+
a dedicated "stale client-table-UUID" hint pointing at the actual
|
|
218
|
+
recovery (``doc.save()`` to drain pending + re-anchor proxies). See
|
|
219
|
+
``_TABLE_CLIENT_ID_HINT`` in ``docx/_http_doc.py``.
|
|
184
220
|
- The path-proxy in `_http_doc.py` is an internal translation layer:
|
|
185
221
|
call sites that look like `await self._session.doc.create.paragraph(p)`
|
|
186
222
|
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.10.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]
|
|
@@ -30,6 +30,7 @@ format is now typed end-to-end either way.
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import json
|
|
33
|
+
import os
|
|
33
34
|
from typing import Any
|
|
34
35
|
|
|
35
36
|
import requests
|
|
@@ -106,6 +107,7 @@ from docx.commands import (
|
|
|
106
107
|
TablesSetRowHeight,
|
|
107
108
|
TablesSetStyle,
|
|
108
109
|
TablesSetTableOptions,
|
|
110
|
+
TableSetCell,
|
|
109
111
|
TrackChangesDecide,
|
|
110
112
|
TrackChangesGet,
|
|
111
113
|
TrackChangesList,
|
|
@@ -154,17 +156,102 @@ def _looks_like_block_not_found(err_obj: dict) -> bool:
|
|
|
154
156
|
return 'block "' in lower or ('block ' in lower and ' was not found' in lower)
|
|
155
157
|
|
|
156
158
|
|
|
159
|
+
# Commands that target a paragraph block (or its inline range) and
|
|
160
|
+
# therefore trip the SuperDoc 1.8.1 cell-paragraph addressing gap when
|
|
161
|
+
# the target paragraph is nested inside a ``tableCell``. Used by the
|
|
162
|
+
# ``BlockNotFoundError`` hint logic to decide whether to surface the
|
|
163
|
+
# materialization workaround. Membership is checked against the
|
|
164
|
+
# ``err_obj["type"]`` field that ``apps/api`` echoes back on partial
|
|
165
|
+
# failure, so the gate covers both error-message shapes the SuperDoc
|
|
166
|
+
# CLI emits:
|
|
167
|
+
#
|
|
168
|
+
# * ``Block "paragraph:<uuid>" was not found.`` (SetParagraph*, etc.)
|
|
169
|
+
# * ``Block "<uuid>" not found.`` (Insert)
|
|
170
|
+
#
|
|
171
|
+
# Without this gate, the hint either fired for stale-id misses on
|
|
172
|
+
# unrelated command types (Greptile #20555) or skipped the Insert form
|
|
173
|
+
# entirely (preview session
|
|
174
|
+
# thread_b952794f, where 3 of 4 failures were bare-UUID Inserts and
|
|
175
|
+
# the agent burned 4 retries without ever seeing the workaround).
|
|
176
|
+
_PARAGRAPH_TARGETING_COMMANDS: frozenset[str] = frozenset(
|
|
177
|
+
{
|
|
178
|
+
"Insert",
|
|
179
|
+
"FormatApply",
|
|
180
|
+
"SetParagraphAlignment",
|
|
181
|
+
"ClearParagraphAlignment",
|
|
182
|
+
"SetParagraphStyle",
|
|
183
|
+
"SetParagraphIndentation",
|
|
184
|
+
"ClearParagraphIndentation",
|
|
185
|
+
"SetParagraphSpacing",
|
|
186
|
+
"ClearParagraphSpacing",
|
|
187
|
+
"SetParagraphKeepOptions",
|
|
188
|
+
"SetParagraphFlowOptions",
|
|
189
|
+
"SetParagraphTabStop",
|
|
190
|
+
"ClearParagraphTabStops",
|
|
191
|
+
"Replace",
|
|
192
|
+
"InsertLineBreak",
|
|
193
|
+
"InsertTab",
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
157
198
|
_CELL_PARAGRAPH_HINT: str = (
|
|
158
199
|
"\n\nHint: SuperDoc 1.8.1 cannot format paragraphs nested inside "
|
|
159
200
|
"table cells via SetParagraphAlignment / SetParagraphStyle / "
|
|
160
|
-
"SetParagraphIndentation / SetParagraphSpacing
|
|
161
|
-
"a paragraph-block target
|
|
162
|
-
"returned by cell.getNodeById but
|
|
163
|
-
"block. Materialize the cell's
|
|
164
|
-
'``cell.text = "value"``, then re-read
|
|
165
|
-
"apply format ops to that post-
|
|
166
|
-
"Tracked upstream at
|
|
167
|
-
"§ 'cell-inner paragraph
|
|
201
|
+
"SetParagraphIndentation / SetParagraphSpacing / doc.insert with "
|
|
202
|
+
"a paragraph-block target, or any FormatApply on a cell-inner run. "
|
|
203
|
+
"The cell's inner paragraph id is returned by cell.getNodeById but "
|
|
204
|
+
"isn't a top-level addressable block. Materialize the cell's "
|
|
205
|
+
'paragraph first via ``cell.text = "value"``, then re-read '
|
|
206
|
+
"``cell.paragraphs[0]`` and apply format ops to that post-"
|
|
207
|
+
"materialization Paragraph proxy. Tracked upstream at "
|
|
208
|
+
"docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md § 'cell-inner paragraph "
|
|
209
|
+
"addressing'."
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ``tables.get`` / ``tables.getCells`` / ``tables.getProperties`` carry
|
|
214
|
+
# the table id at the top-level ``nodeId`` field — and the agent code
|
|
215
|
+
# that triggers them (``table.cell(0, 0)``, ``table.rows``, etc.) runs
|
|
216
|
+
# *immediately* after ``doc.add_table(...)``. The first such query
|
|
217
|
+
# triggers an eager flush that drains the buffered CreateTable in the
|
|
218
|
+
# same HTTP batch, so the applier's per-batch ``clientIdMap`` rewrite
|
|
219
|
+
# normally catches it. But when an EARLIER batch (e.g. a doomed cell-
|
|
220
|
+
# paragraph mutation a few lines up in the user script) raised before
|
|
221
|
+
# the SDK could rewrite ``proxy_id_refs``, the Table proxy still holds
|
|
222
|
+
# the client UUID. The next batch ships the dead id straight to SuperDoc
|
|
223
|
+
# and we land here. The hint points the agent at the canonical recovery
|
|
224
|
+
# pattern (force a save() to reseat the proxy refs, or split the
|
|
225
|
+
# offending mutation into its own execution).
|
|
226
|
+
_TABLE_QUERY_COMMANDS: frozenset[str] = frozenset(
|
|
227
|
+
{
|
|
228
|
+
"TablesGet",
|
|
229
|
+
"TablesGetCells",
|
|
230
|
+
"TablesGetProperties",
|
|
231
|
+
"TablesSetStyle",
|
|
232
|
+
"TablesSetLayout",
|
|
233
|
+
"TablesSetTableOptions",
|
|
234
|
+
"TablesSetCellProperties",
|
|
235
|
+
"TablesSetColumnWidth",
|
|
236
|
+
"TablesSetRowHeight",
|
|
237
|
+
"TablesInsertRow",
|
|
238
|
+
"TablesInsertColumn",
|
|
239
|
+
"TablesMergeCells",
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
_TABLE_CLIENT_ID_HINT: str = (
|
|
245
|
+
"\n\nHint: this looks like a stale client-side table UUID "
|
|
246
|
+
'(``t_xxxxxxxxxxxx``). Either an earlier batch in this execution '
|
|
247
|
+
"raised before the SDK could rewrite the Table proxy's id from the "
|
|
248
|
+
"client UUID to the real SuperDoc id, OR the table belongs to a "
|
|
249
|
+
"prior execution whose state has been discarded. Recover by calling "
|
|
250
|
+
"``doc.save()`` to drain pending mutations + re-anchor live "
|
|
251
|
+
"proxies, then re-query via ``doc.tables`` to get fresh proxies "
|
|
252
|
+
"with real ids. If you saw a cell-paragraph error in the same "
|
|
253
|
+
"execution, address that first — its partial-failure is what "
|
|
254
|
+
"abandoned the rewrite."
|
|
168
255
|
)
|
|
169
256
|
|
|
170
257
|
|
|
@@ -287,6 +374,9 @@ def _http_post_json(
|
|
|
287
374
|
"Accept": "application/json",
|
|
288
375
|
"User-Agent": _user_agent(),
|
|
289
376
|
}
|
|
377
|
+
custom_attr = os.environ.get("ATHENA_DOCX_CUSTOM_ATTRIBUTIONS")
|
|
378
|
+
if custom_attr:
|
|
379
|
+
headers["X-Custom-Attributions"] = custom_attr
|
|
290
380
|
try:
|
|
291
381
|
resp = session.post(
|
|
292
382
|
url,
|
|
@@ -314,6 +404,20 @@ def _http_post_json(
|
|
|
314
404
|
parsed = json.loads(body) if body else {}
|
|
315
405
|
except json.JSONDecodeError:
|
|
316
406
|
parsed = {"raw": body}
|
|
407
|
+
# Extract the ``applied`` prefix so callers can rewrite
|
|
408
|
+
# ``proxy_id_refs`` for commands that DID succeed before the
|
|
409
|
+
# batch hit its first failure. Without this, the SDK throws
|
|
410
|
+
# away every successful Create's client-UUID → real-id mapping
|
|
411
|
+
# the moment ONE command fails, and the next batch keeps
|
|
412
|
+
# shipping dead client UUIDs. That cascade turned the preview-
|
|
413
|
+
# session thread_bafba02b's first cell-paragraph mistake into
|
|
414
|
+
# thirteen downstream "Block not found" errors and finally a
|
|
415
|
+
# ``DOCUMENT_IDENTITY_CONFLICT``. The structure is wire-shape:
|
|
416
|
+
# ``[{index, type, result: {client_node_id, real_node_id, …}}]``.
|
|
417
|
+
applied_raw = parsed.get("applied") if isinstance(parsed, dict) else None
|
|
418
|
+
partial_applied: list[dict] = []
|
|
419
|
+
if isinstance(applied_raw, list):
|
|
420
|
+
partial_applied = [a for a in applied_raw if isinstance(a, dict)]
|
|
317
421
|
# If the failing command's error looks like "no such entity",
|
|
318
422
|
# raise a typed :class:`NotFoundError` so speculative-read call
|
|
319
423
|
# sites (``Comments.get``) can coerce it to ``None`` without
|
|
@@ -325,27 +429,44 @@ def _http_post_json(
|
|
|
325
429
|
# likely targeting a cell-inner paragraph or stale-session
|
|
326
430
|
# block id. Surface the typed exception plus an agent-readable
|
|
327
431
|
# workaround so the next attempt doesn't repeat the same
|
|
328
|
-
# mistake.
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
#
|
|
432
|
+
# mistake.
|
|
433
|
+
#
|
|
434
|
+
# The cell-paragraph hint applies when the failing command is
|
|
435
|
+
# a paragraph-targeting op (Insert, FormatApply, SetParagraph*,
|
|
436
|
+
# …). We can't gate on the ``paragraph:`` prefix alone — the
|
|
437
|
+
# bare-UUID ``Block "<uuid>" not found.`` shape that SuperDoc's
|
|
438
|
+
# CLI emits for ``Insert`` failures is the dominant form of
|
|
439
|
+
# this bug in practice (preview-session thread_b952794f hit
|
|
440
|
+
# it 3 of 4 times without ever seeing the workaround under the
|
|
441
|
+
# prefix-only gate).
|
|
334
442
|
if _looks_like_block_not_found(err_obj):
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
isinstance(
|
|
338
|
-
and
|
|
443
|
+
cmd_type = err_obj.get("type")
|
|
444
|
+
paragraph_targeting = (
|
|
445
|
+
isinstance(cmd_type, str)
|
|
446
|
+
and cmd_type in _PARAGRAPH_TARGETING_COMMANDS
|
|
339
447
|
)
|
|
448
|
+
table_query = (
|
|
449
|
+
isinstance(cmd_type, str)
|
|
450
|
+
and cmd_type in _TABLE_QUERY_COMMANDS
|
|
451
|
+
)
|
|
452
|
+
if paragraph_targeting:
|
|
453
|
+
hint = _CELL_PARAGRAPH_HINT
|
|
454
|
+
elif table_query:
|
|
455
|
+
hint = _TABLE_CLIENT_ID_HINT
|
|
456
|
+
else:
|
|
457
|
+
hint = ""
|
|
340
458
|
raise BlockNotFoundError(
|
|
341
|
-
base_msg +
|
|
459
|
+
base_msg + hint,
|
|
342
460
|
payload=err_obj,
|
|
343
|
-
)
|
|
461
|
+
).with_partial_applied(partial_applied)
|
|
344
462
|
if _looks_like_not_found(err_obj):
|
|
345
|
-
raise NotFoundError(
|
|
463
|
+
raise NotFoundError(
|
|
464
|
+
base_msg,
|
|
465
|
+
payload=err_obj,
|
|
466
|
+
).with_partial_applied(partial_applied)
|
|
346
467
|
raise DocxError(
|
|
347
468
|
f"docx-studio batch reported a partial failure: {parsed!r}",
|
|
348
|
-
)
|
|
469
|
+
).with_partial_applied(partial_applied)
|
|
349
470
|
|
|
350
471
|
if 200 <= resp.status_code < 300:
|
|
351
472
|
try:
|
|
@@ -531,6 +652,7 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
531
652
|
"tables.set_cell_properties": TablesSetCellProperties,
|
|
532
653
|
"tables.set_column_width": TablesSetColumnWidth,
|
|
533
654
|
"tables.set_row_height": TablesSetRowHeight,
|
|
655
|
+
"tables.set_cell": TableSetCell,
|
|
534
656
|
# Images (mutations)
|
|
535
657
|
"images.set_size": SetImageSize,
|
|
536
658
|
"images.set_alt_text": SetImageAltText,
|