athena-python-docx 0.6.1__tar.gz → 0.7.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.6.1 → athena_python_docx-0.7.0}/CLAUDE.md +21 -5
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/PKG-INFO +1 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/_buffer.py +71 -107
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/_http_doc.py +12 -3
- athena_python_docx-0.7.0/docx/_ptc.py +138 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/commands.py +78 -13
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/document.py +260 -200
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/settings.py +20 -20
- athena_python_docx-0.7.0/docx/text/hyperlink.py +167 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/paragraph.py +122 -21
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/pyproject.toml +1 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/fake_session.py +43 -4
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +2 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +6 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/ours_spec.json +1822 -289
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/parity_crawl.py +26 -3
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/parity_diff.json +87 -185
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/stock_spec.json +16 -16
- athena_python_docx-0.7.0/tests/fidelity/test_e2e_against_staging.py +523 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/baseline_gaps.json +1 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/reports/GAP_ANALYSIS.md +1 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/reports/gap_report.json +8 -2
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/snapshots/athena_latest.json +291 -16
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_batching_perf.py +96 -126
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_buffer.py +30 -13
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_commands.py +12 -2
- athena_python_docx-0.7.0/tests/test_hyperlink_coalescing.py +199 -0
- athena_python_docx-0.7.0/tests/test_list_styles.py +226 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_ptc.py +55 -3
- athena_python_docx-0.7.0/tests/test_silent_stub_settings.py +75 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_wire_contract.py +21 -1
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/uv.lock +1 -1
- athena_python_docx-0.6.1/docx/_ptc.py +0 -173
- athena_python_docx-0.6.1/docx/text/hyperlink.py +0 -123
- athena_python_docx-0.6.1/tests/test_silent_stub_settings.py +0 -68
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/.gitignore +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/README.md +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/_http.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/api.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/client.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/comments.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/errors.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/section.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/shape.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/shared.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/table.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/docx/typing.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.6.1 → athena_python_docx-0.7.0}/tests/test_zod_wire_contract.py +0 -0
|
@@ -135,11 +135,27 @@ This is a **thin HTTP client** that mimics the sync python-docx API.
|
|
|
135
135
|
backoff, 429/502/503/504). The legacy `transport="direct"` (embedded
|
|
136
136
|
Superdoc CLI + y-websocket from Python) was removed in 0.5.0; agent
|
|
137
137
|
pods no longer embed Superdoc.
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
- **Transparent batching (0.7.0+):** Create* commands
|
|
139
|
+
(`CreateParagraph`, `CreateHeading`, `CreateTable`, `CreateImage`)
|
|
140
|
+
ALWAYS pre-mint a client-side UUID (`client_node_id`) and defer
|
|
141
|
+
through the buffer. The applier resolves UUIDs to real SuperDoc
|
|
142
|
+
ids via per-batch `clientIdMap` on flush, and the buffer rewrites
|
|
143
|
+
each proxy's `_node_id` in-place. Net result: 30 `add_paragraph`
|
|
144
|
+
calls = 1 HTTP request, with no explicit `batch()` context manager
|
|
145
|
+
in the public API (1:1 python-docx parity).
|
|
146
|
+
- Flush triggers: queries (`BlocksList`, `Find`, `GetNodeById`, …)
|
|
147
|
+
drain pending ops before running; the 100 ms idle timer fires on
|
|
148
|
+
inactivity so collab updates stream live; `Document.save()` and
|
|
149
|
+
the context-manager exit drain explicitly; `docx.flush_all()` is
|
|
150
|
+
the Daytona-prelude hook that flushes every live buffer in the
|
|
151
|
+
process.
|
|
152
|
+
- Other response-bearing ops without a client id (`Insert`,
|
|
153
|
+
`ListsCreate`, `ListsAttach`, `CommentsCreate`, `CommentsPatch`,
|
|
154
|
+
`TrackChangesDecide`) keep their eager-flush semantics so callers
|
|
155
|
+
reading back ids still see real data. A `Document.add_paragraph`
|
|
156
|
+
with `style="List Bullet"` flushes the queued CreateParagraph in
|
|
157
|
+
the same batch as the follow-up `ListsCreate` — still one HTTP
|
|
158
|
+
request per logical operation.
|
|
143
159
|
- The path-proxy in `_http_doc.py` is an internal translation layer:
|
|
144
160
|
call sites that look like `await self._session.doc.create.paragraph(p)`
|
|
145
161
|
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.7.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>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
"""HTTP command buffer for the docx-studio SDK.
|
|
2
2
|
|
|
3
3
|
The buffer sits between SDK call sites and the HTTP transport. Every SDK
|
|
4
|
-
mutation routes through it; we flush eagerly for queries and
|
|
5
|
-
ops
|
|
6
|
-
one HTTP request instead of N.
|
|
4
|
+
mutation routes through it; we flush eagerly for queries, and idle-batch
|
|
5
|
+
all other ops — including Create* — so a burst of ``add_paragraph`` calls
|
|
6
|
+
ships as one HTTP request instead of N.
|
|
7
|
+
|
|
8
|
+
Create* commands are deferred because the SDK pre-mints a client-side
|
|
9
|
+
UUID for every new node (see ``Document._mint_client_node_id``). The
|
|
10
|
+
proxy returned to the caller is stamped with that UUID; the applier
|
|
11
|
+
resolves UUIDs to real SuperDoc ids via ``clientIdMap`` and echoes the
|
|
12
|
+
mapping back, at which point the buffer rewrites each proxy's
|
|
13
|
+
``_node_id``. This makes batching invisible at the call site — user
|
|
14
|
+
code looks like python-docx, runs at python-docx-batched speed.
|
|
7
15
|
|
|
8
16
|
Concurrency model: the SDK serializes calls through ``_batching.run_sync``
|
|
9
17
|
(one persistent event-loop thread), so the buffer's primary thread is the
|
|
@@ -23,11 +31,10 @@ from __future__ import annotations
|
|
|
23
31
|
import sys
|
|
24
32
|
import threading
|
|
25
33
|
import weakref
|
|
26
|
-
from
|
|
27
|
-
from typing import TYPE_CHECKING, Any, Generator
|
|
34
|
+
from typing import TYPE_CHECKING, Any
|
|
28
35
|
|
|
29
36
|
from docx import _ptc
|
|
30
|
-
from docx.commands import Command, must_flush_immediately
|
|
37
|
+
from docx.commands import Command, is_response_bearing, must_flush_immediately
|
|
31
38
|
|
|
32
39
|
if TYPE_CHECKING:
|
|
33
40
|
from docx._http_doc import HttpClient
|
|
@@ -125,12 +132,17 @@ class CommandBuffer:
|
|
|
125
132
|
"""Buffers commands for one Document/asset.
|
|
126
133
|
|
|
127
134
|
Behaviour:
|
|
128
|
-
- Queries
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
- Queries (BlocksList, Find, GetNodeById, …) flush immediately and
|
|
136
|
+
drain any pending mutations in the same batch so ordering is preserved.
|
|
137
|
+
- Every other op — formatters, setters, AND Create* — queues. The idle
|
|
138
|
+
timer flushes after :data:`DEFAULT_AUTO_FLUSH_SECONDS` of inactivity,
|
|
139
|
+
the next eager (query) call drains, or explicit ``flush()`` drains.
|
|
140
|
+
|
|
141
|
+
Create* commands defer because the SDK pre-mints a client-side UUID
|
|
142
|
+
(``client_node_id`` / ``client_entity_id``); the applier translates
|
|
143
|
+
the UUID to a real SuperDoc id in a per-batch ``clientIdMap`` and
|
|
144
|
+
echoes the mapping back so the SDK can rewrite each proxy's
|
|
145
|
+
``_node_id`` on flush.
|
|
134
146
|
"""
|
|
135
147
|
|
|
136
148
|
def __init__(
|
|
@@ -154,11 +166,6 @@ class CommandBuffer:
|
|
|
154
166
|
# original semantics.
|
|
155
167
|
self._change_mode: str | None = None
|
|
156
168
|
self._user: dict[str, str] | None = None
|
|
157
|
-
# Batch mode flag — when True, Create* commands buffer instead
|
|
158
|
-
# of flushing immediately, and proxies bind to a client-side
|
|
159
|
-
# UUID instead of waiting for the server response. See
|
|
160
|
-
# :meth:`Document.batch` and ``PERFORMANCE_BATCHING_ANALYSIS.md``.
|
|
161
|
-
self._batch_depth: int = 0
|
|
162
169
|
# client_node_id → list of (proxy, attr) pairs to update with the
|
|
163
170
|
# real nodeId after flush. Populated by ``add_paragraph`` /
|
|
164
171
|
# ``add_heading`` / etc. when they queue a Create with a
|
|
@@ -168,22 +175,11 @@ class CommandBuffer:
|
|
|
168
175
|
] = {}
|
|
169
176
|
_register(self)
|
|
170
177
|
|
|
171
|
-
@property
|
|
172
|
-
def is_batching(self) -> bool:
|
|
173
|
-
"""``True`` if a ``with doc.batch():`` block is currently open.
|
|
174
|
-
|
|
175
|
-
SDK call sites read this to decide whether to defer Create*
|
|
176
|
-
commands or eager-flush them. Reentrant — nested ``batch()``
|
|
177
|
-
blocks increment the counter and decrement on exit; the outer
|
|
178
|
-
block is what actually flushes.
|
|
179
|
-
"""
|
|
180
|
-
return self._batch_depth > 0
|
|
181
|
-
|
|
182
178
|
def register_proxy_id_ref(
|
|
183
179
|
self, client_id: str, proxy: object, attr: str = "_node_id"
|
|
184
180
|
) -> None:
|
|
185
181
|
"""Register that ``proxy.<attr>`` should be rewritten from
|
|
186
|
-
``client_id`` to the real node id once the
|
|
182
|
+
``client_id`` to the real node id once the queue flushes.
|
|
187
183
|
|
|
188
184
|
Called by ``Document.add_paragraph`` (and equivalents) when they
|
|
189
185
|
queue a Create with a ``client_node_id``. The flush loop walks
|
|
@@ -254,21 +250,19 @@ class CommandBuffer:
|
|
|
254
250
|
def call(self, cmd: Command) -> Any:
|
|
255
251
|
"""Execute or buffer ``cmd``.
|
|
256
252
|
|
|
257
|
-
For
|
|
258
|
-
|
|
259
|
-
the
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
proxy
|
|
270
|
-
applier's ``real_node_id`` / ``real_entity_id`` echo lets the
|
|
271
|
-
SDK rewrite the proxy in-place.
|
|
253
|
+
For queries (``BlocksList``, ``Find``, ``GetNodeById``, …)
|
|
254
|
+
AND response-bearing commands that don't carry a client-side
|
|
255
|
+
id, drains the pending queue and runs ``cmd`` in the same
|
|
256
|
+
batch; returns the per-cmd result dict.
|
|
257
|
+
|
|
258
|
+
Otherwise — pure mutations (formatters, setters) AND Create*
|
|
259
|
+
commands that carry a pre-minted ``client_node_id`` /
|
|
260
|
+
``client_entity_id`` — appends to the queue, resets the idle
|
|
261
|
+
timer, and returns ``None``. Caller MUST NOT rely on the return
|
|
262
|
+
value of a buffered mutation. For deferred Creates, the
|
|
263
|
+
applier resolves the client UUID to a real SuperDoc id via
|
|
264
|
+
``clientIdMap`` on flush and the buffer rewrites each registered
|
|
265
|
+
proxy's ``_node_id`` in-place.
|
|
272
266
|
"""
|
|
273
267
|
if self._closed:
|
|
274
268
|
raise RuntimeError(
|
|
@@ -278,11 +272,15 @@ class CommandBuffer:
|
|
|
278
272
|
# PTC begin: one event per user-facing method call, before any
|
|
279
273
|
# batching. Failures here can't crash the user's code path.
|
|
280
274
|
try:
|
|
281
|
-
cmd._ptc_call_id = _ptc.emit_begin(
|
|
275
|
+
cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
|
|
276
|
+
type(cmd).__name__,
|
|
277
|
+
cmd.to_dict(),
|
|
278
|
+
asset_id=self._asset_id,
|
|
279
|
+
)
|
|
282
280
|
except Exception: # noqa: BLE001
|
|
283
281
|
pass
|
|
284
282
|
|
|
285
|
-
if must_flush_immediately(cmd) and not self.
|
|
283
|
+
if must_flush_immediately(cmd) and not self._has_client_id(cmd):
|
|
286
284
|
return self._eager_flush_with(cmd)
|
|
287
285
|
|
|
288
286
|
with self._lock:
|
|
@@ -290,23 +288,25 @@ class CommandBuffer:
|
|
|
290
288
|
self._reset_timer_locked()
|
|
291
289
|
return None
|
|
292
290
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
291
|
+
@staticmethod
|
|
292
|
+
def _has_client_id(cmd: Command) -> bool:
|
|
293
|
+
"""Return ``True`` iff this command carries a pre-minted
|
|
294
|
+
client-side id (``client_node_id`` for nodes,
|
|
295
|
+
``client_entity_id`` for comments).
|
|
296
|
+
|
|
297
|
+
Used by :meth:`call` to decide whether a response-bearing
|
|
298
|
+
Create can defer. Pure response-bearing ops without a client
|
|
299
|
+
id (``Insert``, ``CommentsPatch``, ``TrackChangesDecide``,
|
|
300
|
+
legacy callers) keep their eager-flush semantics so callers
|
|
301
|
+
that read the response still see real data.
|
|
304
302
|
"""
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
303
|
+
return (
|
|
304
|
+
is_response_bearing(cmd)
|
|
305
|
+
and (
|
|
306
|
+
getattr(cmd, "client_node_id", None) is not None
|
|
307
|
+
or getattr(cmd, "client_entity_id", None) is not None
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
310
|
|
|
311
311
|
def flush(self) -> list[Any]:
|
|
312
312
|
"""Flush pending commands as one HTTP batch.
|
|
@@ -317,9 +317,10 @@ class CommandBuffer:
|
|
|
317
317
|
After the batch returns, walks per-cmd results for
|
|
318
318
|
``real_node_id`` / ``real_entity_id`` echoes and updates any
|
|
319
319
|
proxies registered via :meth:`register_proxy_id_ref` with the
|
|
320
|
-
resolved server ids. This
|
|
321
|
-
|
|
322
|
-
real ids
|
|
320
|
+
resolved server ids. This completes the round-trip for the
|
|
321
|
+
always-on transparent batching path — the SDK's caller-facing
|
|
322
|
+
proxies pick up real ids without the caller ever seeing the
|
|
323
|
+
UUID swap.
|
|
323
324
|
"""
|
|
324
325
|
with self._lock:
|
|
325
326
|
self._cancel_timer_locked()
|
|
@@ -371,43 +372,6 @@ class CommandBuffer:
|
|
|
371
372
|
pass
|
|
372
373
|
return results
|
|
373
374
|
|
|
374
|
-
@contextmanager
|
|
375
|
-
def batch(self) -> Generator[None, None, None]:
|
|
376
|
-
"""Group calls into one HTTP batch.
|
|
377
|
-
|
|
378
|
-
Inside the ``with`` block:
|
|
379
|
-
|
|
380
|
-
- Queries (``BlocksList``, ``Find``, ``GetNodeById``, …) still
|
|
381
|
-
eager-flush — the caller is awaiting their result, so we have
|
|
382
|
-
no choice.
|
|
383
|
-
- Pure mutations (formatters, setters) accumulate without their
|
|
384
|
-
idle timer firing.
|
|
385
|
-
- Create*-shape commands that carry a ``client_node_id`` /
|
|
386
|
-
``client_entity_id`` (set by ``Document.add_paragraph`` and
|
|
387
|
-
friends when they detect batch mode) also accumulate — the
|
|
388
|
-
server resolves the client UUIDs to real SuperDoc ids in a
|
|
389
|
-
single batch via the per-request ``clientIdMap``.
|
|
390
|
-
|
|
391
|
-
On exit, drains anything left. Reentrant: nested ``batch()``
|
|
392
|
-
blocks share the same accumulating queue; only the outermost
|
|
393
|
-
block flushes.
|
|
394
|
-
"""
|
|
395
|
-
with self._lock:
|
|
396
|
-
self._cancel_timer_locked()
|
|
397
|
-
old_window = self._auto_flush_seconds
|
|
398
|
-
self._auto_flush_seconds = 0.0 # disable timer-scheduled flush
|
|
399
|
-
self._batch_depth += 1
|
|
400
|
-
try:
|
|
401
|
-
yield
|
|
402
|
-
finally:
|
|
403
|
-
with self._lock:
|
|
404
|
-
self._batch_depth -= 1
|
|
405
|
-
outermost = self._batch_depth == 0
|
|
406
|
-
if outermost:
|
|
407
|
-
self._auto_flush_seconds = old_window
|
|
408
|
-
if outermost:
|
|
409
|
-
self.flush()
|
|
410
|
-
|
|
411
375
|
def close(self) -> None:
|
|
412
376
|
"""Flush and disable. Idempotent."""
|
|
413
377
|
if self._closed:
|
|
@@ -423,11 +387,11 @@ class CommandBuffer:
|
|
|
423
387
|
def _eager_flush_with(self, cmd: Command) -> Any:
|
|
424
388
|
"""Drain pending + run ``cmd`` in one batch; return cmd's result.
|
|
425
389
|
|
|
426
|
-
When pending commands include
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
get their real ids written back via the same flush-time
|
|
430
|
-
that :meth:`flush` does.
|
|
390
|
+
When pending commands include Creates with registered proxy
|
|
391
|
+
refs (queries mid-stream cause this — the caller is awaiting
|
|
392
|
+
their result and the queue must drain first), the proxies
|
|
393
|
+
still get their real ids written back via the same flush-time
|
|
394
|
+
rewrite that :meth:`flush` does.
|
|
431
395
|
"""
|
|
432
396
|
with self._lock:
|
|
433
397
|
self._cancel_timer_locked()
|
|
@@ -68,6 +68,8 @@ from docx.commands import (
|
|
|
68
68
|
Insert,
|
|
69
69
|
InsertLineBreak,
|
|
70
70
|
InsertTab,
|
|
71
|
+
ListsAttach,
|
|
72
|
+
ListsCreate,
|
|
71
73
|
ListsMerge,
|
|
72
74
|
ListsSplit,
|
|
73
75
|
MarkdownToFragment,
|
|
@@ -91,6 +93,7 @@ from docx.commands import (
|
|
|
91
93
|
SetSectionPageMargins,
|
|
92
94
|
SetSectionPageSetup,
|
|
93
95
|
SetSectionTitlePage,
|
|
96
|
+
SetSectionsOddEvenHeadersFooters,
|
|
94
97
|
TablesGet,
|
|
95
98
|
TablesGetCells,
|
|
96
99
|
TablesGetProperties,
|
|
@@ -463,6 +466,7 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
463
466
|
"sections.set_header_footer_margins": SetSectionHeaderFooterMargins,
|
|
464
467
|
"sections.set_break_type": SetSectionBreakType,
|
|
465
468
|
"sections.set_title_page": SetSectionTitlePage,
|
|
469
|
+
"sections.set_odd_even_headers_footers": SetSectionsOddEvenHeadersFooters,
|
|
466
470
|
"sections.set_link_to_previous": SetSectionLinkToPrevious,
|
|
467
471
|
# Tables (mutations)
|
|
468
472
|
"tables.insert_row": TablesInsertRow,
|
|
@@ -503,9 +507,14 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
503
507
|
"header_footers.get": HeaderFootersGet,
|
|
504
508
|
"header_footers.resolve": HeaderFootersResolve,
|
|
505
509
|
"header_footers.refs.set_linked_to_previous": HeaderFootersRefsSetLinkedToPrevious,
|
|
506
|
-
# Lists & Selection
|
|
507
|
-
#
|
|
508
|
-
#
|
|
510
|
+
# Lists & Selection. ``lists.create`` and ``lists.attach`` are
|
|
511
|
+
# consumed internally by ``Document.add_paragraph`` to make
|
|
512
|
+
# ``style="List Bullet" | "List Number"`` produce real list
|
|
513
|
+
# structure (not just a style attribute on a flat paragraph).
|
|
514
|
+
# ``lists.merge``/``lists.split`` stay typed-bus-only — no
|
|
515
|
+
# python-docx parity surface for them.
|
|
516
|
+
"lists.create": ListsCreate,
|
|
517
|
+
"lists.attach": ListsAttach,
|
|
509
518
|
"lists.merge": ListsMerge,
|
|
510
519
|
"lists.split": ListsSplit,
|
|
511
520
|
"selection.current": SelectionCurrent,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Programmatic Tool Calling (PTC) — client side.
|
|
2
|
+
|
|
3
|
+
Activated only when ``ATHENA_PTC_URL`` is set. The URL is a presigned
|
|
4
|
+
endpoint URL with an HMAC-signed token embedded as ``?t=…``; the token
|
|
5
|
+
carries the (thread, parent_tool_call_id, run) triple and an expiry.
|
|
6
|
+
The SDK doesn't need to know that triple — it just POSTs to the URL
|
|
7
|
+
verbatim, and the server derives identity from the token.
|
|
8
|
+
|
|
9
|
+
Without ``ATHENA_PTC_URL`` set, every call here is a no-op.
|
|
10
|
+
|
|
11
|
+
Emits are fire-and-forget on a background daemon thread:
|
|
12
|
+
|
|
13
|
+
- Never raise into user code.
|
|
14
|
+
- Never block the calling thread.
|
|
15
|
+
- Re-read ``ATHENA_PTC_URL`` on every emit so updates between sandbox
|
|
16
|
+
executions take effect immediately (the SDK module itself is cached
|
|
17
|
+
across runs).
|
|
18
|
+
- Snapshot the URL at ``emit_begin`` time and carry it to ``emit_end``
|
|
19
|
+
so late end events can't be misattributed if a new sandbox run
|
|
20
|
+
swapped in a different URL between begin and end.
|
|
21
|
+
|
|
22
|
+
This module is *intentionally byte-identical* across the docx / pptx /
|
|
23
|
+
xlsx SDKs — the three SDKs have no shared base. Duplicating ~100 LOC
|
|
24
|
+
of stdlib code is cheaper than spinning up a 4th release pipeline.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import queue
|
|
32
|
+
import threading
|
|
33
|
+
import time
|
|
34
|
+
import urllib.error
|
|
35
|
+
import urllib.request
|
|
36
|
+
import uuid
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
_MAX_QUEUE = 4096
|
|
40
|
+
_MAX_BODY = 64 * 1024
|
|
41
|
+
_HTTP_TIMEOUT = 2.0
|
|
42
|
+
|
|
43
|
+
# Singleton state. PTC has exactly one outbox per process.
|
|
44
|
+
_outbox: queue.Queue[tuple[str, dict[str, Any]]] = queue.Queue(maxsize=_MAX_QUEUE)
|
|
45
|
+
_thread_lock = threading.Lock()
|
|
46
|
+
_thread_started = False
|
|
47
|
+
# call_id -> URL snapshot at begin time; lets emit_end target the
|
|
48
|
+
# original run's endpoint even if ATHENA_PTC_URL changed since.
|
|
49
|
+
_call_url: dict[str, str] = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _drain() -> None:
|
|
53
|
+
while True:
|
|
54
|
+
item = _outbox.get()
|
|
55
|
+
if item is None:
|
|
56
|
+
return
|
|
57
|
+
url, body = item
|
|
58
|
+
try:
|
|
59
|
+
raw = json.dumps(body).encode("utf-8")
|
|
60
|
+
if len(raw) > _MAX_BODY:
|
|
61
|
+
key = "args" if body.get("phase") == "begin" else "result"
|
|
62
|
+
body[key] = {"__truncated__": True}
|
|
63
|
+
raw = json.dumps(body).encode("utf-8")
|
|
64
|
+
req = urllib.request.Request(
|
|
65
|
+
url,
|
|
66
|
+
data=raw,
|
|
67
|
+
method="POST",
|
|
68
|
+
headers={"Content-Type": "application/json"},
|
|
69
|
+
)
|
|
70
|
+
urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT).close()
|
|
71
|
+
except (urllib.error.URLError, OSError, ValueError):
|
|
72
|
+
pass # never propagate
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _ensure_thread() -> None:
|
|
76
|
+
global _thread_started
|
|
77
|
+
if _thread_started:
|
|
78
|
+
return
|
|
79
|
+
with _thread_lock:
|
|
80
|
+
if _thread_started:
|
|
81
|
+
return
|
|
82
|
+
threading.Thread(target=_drain, name="athena-ptc", daemon=True).start()
|
|
83
|
+
_thread_started = True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _send(url: str, body: dict[str, Any]) -> None:
|
|
87
|
+
_ensure_thread()
|
|
88
|
+
try:
|
|
89
|
+
_outbox.put_nowait((url, body))
|
|
90
|
+
except queue.Full:
|
|
91
|
+
pass # drop on backpressure; never block user code
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def emit_begin(
|
|
95
|
+
tool_name: str,
|
|
96
|
+
args: dict[str, Any],
|
|
97
|
+
*,
|
|
98
|
+
asset_id: str | None = None,
|
|
99
|
+
) -> str:
|
|
100
|
+
call_id = uuid.uuid4().hex
|
|
101
|
+
url = os.environ.get("ATHENA_PTC_URL")
|
|
102
|
+
if not url:
|
|
103
|
+
return call_id
|
|
104
|
+
_call_url[call_id] = url
|
|
105
|
+
body: dict[str, Any] = {
|
|
106
|
+
"callId": call_id,
|
|
107
|
+
"toolName": tool_name,
|
|
108
|
+
"phase": "begin",
|
|
109
|
+
"args": args,
|
|
110
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
|
|
111
|
+
}
|
|
112
|
+
if asset_id is not None:
|
|
113
|
+
body["assetId"] = asset_id
|
|
114
|
+
_send(url, body)
|
|
115
|
+
return call_id
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def emit_end(
|
|
119
|
+
*,
|
|
120
|
+
call_id: str,
|
|
121
|
+
tool_name: str,
|
|
122
|
+
result: dict[str, Any] | None,
|
|
123
|
+
is_error: bool,
|
|
124
|
+
) -> None:
|
|
125
|
+
url = _call_url.pop(call_id, None)
|
|
126
|
+
if url is None:
|
|
127
|
+
return # PTC was disabled at begin, or begin wasn't emitted
|
|
128
|
+
_send(
|
|
129
|
+
url,
|
|
130
|
+
{
|
|
131
|
+
"callId": call_id,
|
|
132
|
+
"toolName": tool_name,
|
|
133
|
+
"phase": "end",
|
|
134
|
+
"result": result,
|
|
135
|
+
"isError": is_error,
|
|
136
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
|
|
137
|
+
},
|
|
138
|
+
)
|
|
@@ -59,17 +59,19 @@ class Command:
|
|
|
59
59
|
|
|
60
60
|
# --- Create* commands carry an optional client-assigned node id ---
|
|
61
61
|
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# applier
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
62
|
+
# The SDK pre-generates a UUID for every new Paragraph/Heading/Table/
|
|
63
|
+
# Image and stamps the proxy with it. The applier translates
|
|
64
|
+
# ``client_node_id`` → real SuperDoc nodeId in a per-batch map (see
|
|
65
|
+
# ``apps/api/src/commands/applier.ts``), so subsequent commands in the
|
|
66
|
+
# same batch can target the new block by its client-side id before the
|
|
67
|
+
# response has even returned. After flush, the buffer reads the
|
|
68
|
+
# resolved ``real_node_id`` out of each Create's response and updates
|
|
69
|
+
# the proxy in-place.
|
|
70
|
+
#
|
|
71
|
+
# Direct internal callers that bypass ``Document`` (legacy helpers,
|
|
72
|
+
# fidelity tests) may leave the field unset; the buffer then eager-
|
|
73
|
+
# flushes the Create immediately so callers that read the response
|
|
74
|
+
# still see real data.
|
|
73
75
|
#
|
|
74
76
|
# The field is ``client_node_id`` on the Python side and arrives at
|
|
75
77
|
# the server as ``clientNodeId`` via ``_snake_to_camel`` — matches the
|
|
@@ -283,6 +285,23 @@ class SetSectionTitlePage(Command):
|
|
|
283
285
|
enabled: bool
|
|
284
286
|
|
|
285
287
|
|
|
288
|
+
@dataclass
|
|
289
|
+
class SetSectionsOddEvenHeadersFooters(Command):
|
|
290
|
+
"""Toggle the document-level ``<w:evenAndOddHeaders/>`` setting.
|
|
291
|
+
|
|
292
|
+
Despite living under the ``sections.*`` namespace on the SuperDoc
|
|
293
|
+
bound-doc API, this op carries no ``target`` — it sets a single
|
|
294
|
+
doc-level flag that applies to every section. Word stores the
|
|
295
|
+
underlying value on ``word/settings.xml`` as
|
|
296
|
+
``<w:evenAndOddHeaders/>``, which is what python-docx's
|
|
297
|
+
``Settings.odd_and_even_pages_header_footer`` writes to.
|
|
298
|
+
|
|
299
|
+
Available since SuperDoc 1.8.1 (``DocSectionsSetOddEvenHeadersFooters``).
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
enabled: bool
|
|
303
|
+
|
|
304
|
+
|
|
286
305
|
@dataclass
|
|
287
306
|
class SetSectionLinkToPrevious(Command):
|
|
288
307
|
"""Toggle the section-side ``linked-to-previous`` flag for one
|
|
@@ -532,8 +551,13 @@ class CommentsCreate(Command):
|
|
|
532
551
|
text: str | None = None
|
|
533
552
|
target: dict[str, Any] | None = None
|
|
534
553
|
parent_id: str | None = None
|
|
535
|
-
# Optional client-assigned entity id for
|
|
536
|
-
#
|
|
554
|
+
# Optional client-assigned entity id for transparent-batched
|
|
555
|
+
# CommentsCreate (parallel to ``client_node_id`` on Create*; the
|
|
556
|
+
# server applier resolves UUIDs via the same per-batch
|
|
557
|
+
# ``clientIdMap``). ``Comments.add_comment`` currently leaves this
|
|
558
|
+
# unset, so CommentsCreate eager-flushes — left in place for
|
|
559
|
+
# future callers that want to defer comment creates alongside
|
|
560
|
+
# other ops.
|
|
537
561
|
client_entity_id: str | None = None
|
|
538
562
|
|
|
539
563
|
|
|
@@ -619,6 +643,40 @@ class HeaderFootersRefsSetLinkedToPrevious(Command):
|
|
|
619
643
|
# every other op.
|
|
620
644
|
|
|
621
645
|
|
|
646
|
+
@dataclass
|
|
647
|
+
class ListsCreate(Command):
|
|
648
|
+
"""Create a new list — either an empty list at an anchor, or by
|
|
649
|
+
converting an existing paragraph (range) into a list.
|
|
650
|
+
|
|
651
|
+
SuperDoc: ``doc.lists.create``.
|
|
652
|
+
|
|
653
|
+
- ``mode="empty"`` requires ``at`` (BlockAddress).
|
|
654
|
+
- ``mode="fromParagraphs"`` requires ``target`` (BlockAddressOrRange).
|
|
655
|
+
- ``kind`` is ``"bullet"`` or ``"ordered"``.
|
|
656
|
+
"""
|
|
657
|
+
|
|
658
|
+
mode: str = "fromParagraphs"
|
|
659
|
+
at: dict[str, Any] | None = None
|
|
660
|
+
target: dict[str, Any] | None = None
|
|
661
|
+
kind: str | None = None
|
|
662
|
+
level: int | None = None
|
|
663
|
+
preset: str | None = None
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
@dataclass
|
|
667
|
+
class ListsAttach(Command):
|
|
668
|
+
"""Attach an existing paragraph to an existing list as a new item.
|
|
669
|
+
|
|
670
|
+
SuperDoc: ``doc.lists.attach``. ``attach_to`` is the list item to
|
|
671
|
+
attach next to (defines list membership); ``target`` is the
|
|
672
|
+
paragraph being converted.
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
attach_to: dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
676
|
+
target: dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
677
|
+
level: int | None = None
|
|
678
|
+
|
|
679
|
+
|
|
622
680
|
@dataclass
|
|
623
681
|
class ListsMerge(Command):
|
|
624
682
|
"""Merge a list item with an adjacent list / list item.
|
|
@@ -768,6 +826,12 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
|
|
|
768
826
|
# describing the entities affected by accept/reject — callers
|
|
769
827
|
# need that synchronously to invalidate their Revision proxies.
|
|
770
828
|
"TrackChangesDecide",
|
|
829
|
+
# ListsCreate / ListsAttach return {item: {nodeId: <listItemId>}}.
|
|
830
|
+
# Document.add_paragraph reads listItemId to (1) update the
|
|
831
|
+
# Paragraph proxy's node_id so subsequent ops target the
|
|
832
|
+
# listItem, and (2) chain the next list item via lists.attach.
|
|
833
|
+
"ListsCreate",
|
|
834
|
+
"ListsAttach",
|
|
771
835
|
}
|
|
772
836
|
)
|
|
773
837
|
|
|
@@ -816,6 +880,7 @@ __all__ = [
|
|
|
816
880
|
"SetSectionHeaderFooterMargins",
|
|
817
881
|
"SetSectionBreakType",
|
|
818
882
|
"SetSectionTitlePage",
|
|
883
|
+
"SetSectionsOddEvenHeadersFooters",
|
|
819
884
|
"SetSectionLinkToPrevious",
|
|
820
885
|
# Tables (mutations)
|
|
821
886
|
"TablesInsertRow",
|