athena-python-docx 0.5.0__tar.gz → 0.5.2__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.5.0 → athena_python_docx-0.5.2}/CLAUDE.md +38 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/PKG-INFO +1 -1
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/__init__.py +1 -1
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_buffer.py +72 -2
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_http_doc.py +32 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/api.py +15 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/commands.py +75 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/document.py +186 -13
- athena_python_docx-0.5.2/docx/revisions.py +377 -0
- athena_python_docx-0.5.2/docx/settings.py +71 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/pyproject.toml +1 -1
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/scripts/release.sh +8 -8
- athena_python_docx-0.5.2/scripts/round_trip_smoke.py +232 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_buffer.py +9 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_http_transport.py +90 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_parity_misc.py +19 -0
- athena_python_docx-0.5.2/tests/test_revisions.py +966 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_wire_contract.py +21 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/uv.lock +1 -1
- athena_python_docx-0.5.0/docx/settings.py +0 -30
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/.gitignore +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/README.md +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_batching.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_http.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/client.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/comments.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/section.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/style.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/table.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/text.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/errors.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/exceptions.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/section.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/shape.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/shared.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/styles/style.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/table.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/font.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/run.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/typing.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/scripts/publish.sh +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/conftest.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/README.md +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_commands.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_comments.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_zod_wire_contract.py +0 -0
|
@@ -76,6 +76,44 @@ not file-backed. Each is documented in the relevant docstring.
|
|
|
76
76
|
hits `POST {base_url}/docs/empty`. The constructor positional-arg
|
|
77
77
|
shape (`Document(asset_id)`) is preserved for parity.
|
|
78
78
|
|
|
79
|
+
- **`Document.save(path_or_stream=None)`** — argument is optional.
|
|
80
|
+
Stock python-docx requires it and `TypeError`s on no-arg, but in an
|
|
81
|
+
asset-backed SDK there is no local file to write — writes are always
|
|
82
|
+
in-place against the Y.Doc. Forcing the upstream signature broke
|
|
83
|
+
every agent invocation that reflexively called `doc.save()` (a
|
|
84
|
+
near-universal Python pattern), so we accept it for parity-friendly
|
|
85
|
+
call sites and ignore the value at runtime.
|
|
86
|
+
|
|
87
|
+
- **`Document.track_revisions: bool`** — when `True`, all subsequent
|
|
88
|
+
mutations are recorded as tracked revisions instead of direct edits.
|
|
89
|
+
python-docx upstream has no top-level toggle (the closest is
|
|
90
|
+
hand-edited `<w:trackChanges/>` on `Document.settings.element`).
|
|
91
|
+
We expose this as a Document-level property because every SuperDoc
|
|
92
|
+
mutation accepts a per-call `changeMode` envelope field. The
|
|
93
|
+
python-docx-parity alias `Document.settings.track_revisions` reads
|
|
94
|
+
and writes the same flag so call sites that walk `doc.settings`
|
|
95
|
+
see consistent state.
|
|
96
|
+
|
|
97
|
+
- **`Document.tracked_revisions()` context manager** — scope
|
|
98
|
+
`track_revisions=True` to a `with` block; restores the previous
|
|
99
|
+
value on exit (even on exception). Block-scoped track-changes is a
|
|
100
|
+
Pythonic ergonomic that doesn't exist in upstream python-docx
|
|
101
|
+
because the upstream package has no track-changes API at all.
|
|
102
|
+
|
|
103
|
+
- **`Document.user_name: str | None` / `Document.user_email: str |
|
|
104
|
+
None`** — author identity threaded through to SuperDoc on session-
|
|
105
|
+
open for tracked-change attribution. python-docx's
|
|
106
|
+
`Document.core_properties.last_modified_by` is the closest upstream
|
|
107
|
+
surface; we use the SuperDoc-native fields directly because SuperDoc
|
|
108
|
+
bakes them into the change records.
|
|
109
|
+
|
|
110
|
+
- **`Document.revisions: Revisions`** — collection of tracked changes
|
|
111
|
+
with iteration, `get(id)`, `accept_all()`, `reject_all()`,
|
|
112
|
+
`accept(id)`, `reject(id)`. Per-revision `Revision.accept()` /
|
|
113
|
+
`Revision.reject()` resolve a single change. python-docx upstream
|
|
114
|
+
has zero track-changes API, so this entire surface is an Athena
|
|
115
|
+
extension wired to SuperDoc's `doc.trackChanges.{list,get,decide}`.
|
|
116
|
+
|
|
79
117
|
### If you need a deviation
|
|
80
118
|
|
|
81
119
|
If there is a genuine technical reason why a deviation from python-docx is necessary:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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>
|
|
@@ -123,6 +123,13 @@ class CommandBuffer:
|
|
|
123
123
|
self._timer: threading.Timer | None = None
|
|
124
124
|
self._auto_flush_seconds: float = auto_flush_seconds
|
|
125
125
|
self._closed: bool = False
|
|
126
|
+
# Track-changes envelope state — propagated as request-level
|
|
127
|
+
# ``changeMode`` and ``user`` on every batch. ``Document``
|
|
128
|
+
# mutates these via :meth:`set_change_mode` / :meth:`set_user`,
|
|
129
|
+
# which flush before changing so prior buffered ops keep their
|
|
130
|
+
# original semantics.
|
|
131
|
+
self._change_mode: str | None = None
|
|
132
|
+
self._user: dict[str, str] | None = None
|
|
126
133
|
_register(self)
|
|
127
134
|
|
|
128
135
|
@property
|
|
@@ -134,6 +141,55 @@ class CommandBuffer:
|
|
|
134
141
|
with self._lock:
|
|
135
142
|
return len(self._pending)
|
|
136
143
|
|
|
144
|
+
@property
|
|
145
|
+
def change_mode(self) -> str | None:
|
|
146
|
+
return self._change_mode
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def user(self) -> "dict[str, str] | None":
|
|
150
|
+
return None if self._user is None else dict(self._user)
|
|
151
|
+
|
|
152
|
+
def set_change_mode(self, mode: str | None) -> None:
|
|
153
|
+
"""Set the batch-level change mode and flush prior pending ops.
|
|
154
|
+
|
|
155
|
+
Flushing first means commands queued under the previous mode
|
|
156
|
+
retain those semantics — switching modes mid-stream doesn't
|
|
157
|
+
retro-actively re-tag earlier mutations. ``mode`` is one of
|
|
158
|
+
``"direct"``, ``"tracked"``, or ``None`` to clear.
|
|
159
|
+
"""
|
|
160
|
+
if mode is not None and mode not in ("direct", "tracked"):
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"change_mode must be 'direct', 'tracked', or None; got {mode!r}"
|
|
163
|
+
)
|
|
164
|
+
with self._lock:
|
|
165
|
+
current = self._change_mode
|
|
166
|
+
if current == mode:
|
|
167
|
+
return
|
|
168
|
+
# Drain any queued commands with the *previous* mode before
|
|
169
|
+
# adopting the new one.
|
|
170
|
+
self.flush()
|
|
171
|
+
with self._lock:
|
|
172
|
+
self._change_mode = mode
|
|
173
|
+
|
|
174
|
+
def set_user(self, name: str | None, email: str | None = None) -> None:
|
|
175
|
+
"""Set the batch-level user identity.
|
|
176
|
+
|
|
177
|
+
Sent on every commands request envelope. SuperDoc honors the
|
|
178
|
+
identity at session-open time only — subsequent batches against
|
|
179
|
+
a pooled session ignore the value, but the payload is still
|
|
180
|
+
included so a fresh session picks it up.
|
|
181
|
+
"""
|
|
182
|
+
if name is None:
|
|
183
|
+
with self._lock:
|
|
184
|
+
self._user = None
|
|
185
|
+
return
|
|
186
|
+
if not name:
|
|
187
|
+
raise ValueError("user name must be a non-empty string")
|
|
188
|
+
with self._lock:
|
|
189
|
+
self._user = {"name": name}
|
|
190
|
+
if email:
|
|
191
|
+
self._user["email"] = email
|
|
192
|
+
|
|
137
193
|
def call(self, cmd: Command) -> Any:
|
|
138
194
|
"""Execute or buffer ``cmd``.
|
|
139
195
|
|
|
@@ -167,9 +223,16 @@ class CommandBuffer:
|
|
|
167
223
|
self._cancel_timer_locked()
|
|
168
224
|
pending = self._pending
|
|
169
225
|
self._pending = []
|
|
226
|
+
change_mode = self._change_mode
|
|
227
|
+
user = None if self._user is None else dict(self._user)
|
|
170
228
|
if not pending:
|
|
171
229
|
return []
|
|
172
|
-
return self._client.execute_batch(
|
|
230
|
+
return self._client.execute_batch(
|
|
231
|
+
self._asset_id,
|
|
232
|
+
pending,
|
|
233
|
+
change_mode=change_mode,
|
|
234
|
+
user=user,
|
|
235
|
+
)
|
|
173
236
|
|
|
174
237
|
@contextmanager
|
|
175
238
|
def batch(self) -> Generator[None, None, None]:
|
|
@@ -212,8 +275,15 @@ class CommandBuffer:
|
|
|
212
275
|
self._cancel_timer_locked()
|
|
213
276
|
pending = self._pending
|
|
214
277
|
self._pending = []
|
|
278
|
+
change_mode = self._change_mode
|
|
279
|
+
user = None if self._user is None else dict(self._user)
|
|
215
280
|
all_cmds: list[Command] = [*pending, cmd]
|
|
216
|
-
results: list[Any] = self._client.execute_batch(
|
|
281
|
+
results: list[Any] = self._client.execute_batch(
|
|
282
|
+
self._asset_id,
|
|
283
|
+
all_cmds,
|
|
284
|
+
change_mode=change_mode,
|
|
285
|
+
user=user,
|
|
286
|
+
)
|
|
217
287
|
if not results:
|
|
218
288
|
return {}
|
|
219
289
|
return results[-1]
|
|
@@ -103,6 +103,9 @@ from docx.commands import (
|
|
|
103
103
|
TablesSetRowHeight,
|
|
104
104
|
TablesSetStyle,
|
|
105
105
|
TablesSetTableOptions,
|
|
106
|
+
TrackChangesDecide,
|
|
107
|
+
TrackChangesGet,
|
|
108
|
+
TrackChangesList,
|
|
106
109
|
)
|
|
107
110
|
from docx.errors import (
|
|
108
111
|
AuthenticationError,
|
|
@@ -260,9 +263,23 @@ class HttpClient:
|
|
|
260
263
|
self,
|
|
261
264
|
asset_id: str,
|
|
262
265
|
commands: list[Command],
|
|
266
|
+
*,
|
|
267
|
+
change_mode: str | None = None,
|
|
268
|
+
user: dict[str, str] | None = None,
|
|
263
269
|
) -> list[Any]:
|
|
264
270
|
"""POST a batch of typed commands, return per-command result dicts.
|
|
265
271
|
|
|
272
|
+
Envelope fields:
|
|
273
|
+
change_mode: ``"direct"`` | ``"tracked"`` | ``None``. When
|
|
274
|
+
set, the server injects this into every mutation
|
|
275
|
+
forwarded to SuperDoc — tracked edits land as
|
|
276
|
+
``<w:ins>`` / ``<w:del>`` revisions instead of replacing
|
|
277
|
+
the underlying text.
|
|
278
|
+
user: ``{"name": ..., "email": ...}`` identity for
|
|
279
|
+
tracked-change attribution. Honored only when SuperDoc
|
|
280
|
+
opens the per-asset session for the first time; ignored
|
|
281
|
+
on subsequent batches against the same pooled session.
|
|
282
|
+
|
|
266
283
|
On success, length matches len(commands). On 207 partial-failure,
|
|
267
284
|
the server returns 207 with `applied` (the successful prefix) plus
|
|
268
285
|
an `error` for the failing command — :func:`_http_post_json`
|
|
@@ -278,6 +295,10 @@ class HttpClient:
|
|
|
278
295
|
"commands": [c.to_dict() for c in commands],
|
|
279
296
|
"return": {"snapshot": False},
|
|
280
297
|
}
|
|
298
|
+
if change_mode is not None:
|
|
299
|
+
body["changeMode"] = change_mode
|
|
300
|
+
if user is not None:
|
|
301
|
+
body["user"] = user
|
|
281
302
|
resp: dict = _http_post_json(
|
|
282
303
|
session=self._session,
|
|
283
304
|
url=url,
|
|
@@ -396,6 +417,12 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
396
417
|
"lists.merge": ListsMerge,
|
|
397
418
|
"lists.split": ListsSplit,
|
|
398
419
|
"selection.current": SelectionCurrent,
|
|
420
|
+
# Track Changes (Athena extension — see docx.revisions.Revisions).
|
|
421
|
+
# python-docx upstream has no track-changes API; the SDK exposes
|
|
422
|
+
# this as Document.revisions and Document.track_revisions.
|
|
423
|
+
"track_changes.list": TrackChangesList,
|
|
424
|
+
"track_changes.get": TrackChangesGet,
|
|
425
|
+
"track_changes.decide": TrackChangesDecide,
|
|
399
426
|
}
|
|
400
427
|
|
|
401
428
|
|
|
@@ -423,6 +450,11 @@ _FIELD_RENAMES: dict[str, dict[str, str]] = {
|
|
|
423
450
|
"create.paragraph": {"in": "in_"},
|
|
424
451
|
"create.heading": {"in": "in_"},
|
|
425
452
|
"create.table": {"in": "in_"},
|
|
453
|
+
# SuperDoc's track-changes-list filter is named `type:` on the wire
|
|
454
|
+
# (matching the rest of its API), but `type` is our Command
|
|
455
|
+
# discriminator. Rename to `change_type` on the Python side; the
|
|
456
|
+
# server applier renames back when calling SuperDoc.
|
|
457
|
+
"track_changes.list": {"type": "change_type", "in": "in_"},
|
|
426
458
|
}
|
|
427
459
|
|
|
428
460
|
|
|
@@ -26,6 +26,9 @@ def Document(
|
|
|
26
26
|
*,
|
|
27
27
|
base_url: str | None = None,
|
|
28
28
|
api_key: str | None = None,
|
|
29
|
+
user_name: str | None = None,
|
|
30
|
+
user_email: str | None = None,
|
|
31
|
+
track_revisions: bool = False,
|
|
29
32
|
) -> _Document:
|
|
30
33
|
"""Open a Word document for editing.
|
|
31
34
|
|
|
@@ -39,6 +42,15 @@ def Document(
|
|
|
39
42
|
``$ATHENA_DOCX_BASE_URL``.
|
|
40
43
|
api_key: Athena API key / PropelAuth bearer. Falls back to
|
|
41
44
|
``$ATHENA_DOCX_API_KEY``.
|
|
45
|
+
user_name: (Athena extension) author identity for tracked
|
|
46
|
+
changes. Threaded through to SuperDoc on session-open;
|
|
47
|
+
ignored on subsequent requests against a pooled session.
|
|
48
|
+
user_email: (Athena extension) optional email for the
|
|
49
|
+
tracked-change author tooltip.
|
|
50
|
+
track_revisions: (Athena extension) start the document with
|
|
51
|
+
track-changes mode enabled. Equivalent to setting
|
|
52
|
+
:attr:`Document.track_revisions` to ``True`` after
|
|
53
|
+
construction.
|
|
42
54
|
|
|
43
55
|
Returns:
|
|
44
56
|
A Document instance bound to the asset.
|
|
@@ -59,6 +71,9 @@ def Document(
|
|
|
59
71
|
asset_id=docx,
|
|
60
72
|
http_base_url=base_url,
|
|
61
73
|
http_api_key=api_key,
|
|
74
|
+
user_name=user_name,
|
|
75
|
+
user_email=user_email,
|
|
76
|
+
track_revisions=track_revisions,
|
|
62
77
|
)
|
|
63
78
|
|
|
64
79
|
|
|
@@ -622,6 +622,70 @@ class SelectionCurrent(Command):
|
|
|
622
622
|
include_text: bool | None = None
|
|
623
623
|
|
|
624
624
|
|
|
625
|
+
# ---------------------------------------------------------------------------
|
|
626
|
+
# Track Changes
|
|
627
|
+
# ---------------------------------------------------------------------------
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
@dataclass
|
|
631
|
+
class TrackChangesList(Command):
|
|
632
|
+
"""List tracked changes (insertions, deletions, formatting marks).
|
|
633
|
+
|
|
634
|
+
Mirrors SuperDoc's ``doc.trackChanges.list``. The wire field for
|
|
635
|
+
"filter by change type" is ``type`` on SuperDoc's side; we send
|
|
636
|
+
``changeType`` instead and the server applier renames in transit so
|
|
637
|
+
it doesn't collide with our top-level Command discriminator
|
|
638
|
+
(``type: "TrackChangesList"``).
|
|
639
|
+
|
|
640
|
+
``in_`` accepts either the literal string ``"all"`` (apply across
|
|
641
|
+
every story — body, headers, footnotes, ...) or a story locator
|
|
642
|
+
dict ``{kind: "story", storyType, ...}``. Trailing-underscore on
|
|
643
|
+
the field name avoids the Python keyword collision; the
|
|
644
|
+
``_snake_to_camel`` helper strips it for the wire.
|
|
645
|
+
"""
|
|
646
|
+
|
|
647
|
+
change_type: str | None = None # "insert" | "delete" | "format"
|
|
648
|
+
limit: int | None = None
|
|
649
|
+
offset: int | None = None
|
|
650
|
+
in_: dict[str, Any] | str | None = None
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
@dataclass
|
|
654
|
+
class TrackChangesGet(Command):
|
|
655
|
+
"""Read a single tracked change by id.
|
|
656
|
+
|
|
657
|
+
Returns ``{address, id, type, author, authorEmail, date, excerpt,
|
|
658
|
+
wordRevisionIds?}``. ``story`` narrows the lookup to a specific
|
|
659
|
+
body/header/footnote scope when the change id might collide across
|
|
660
|
+
stories (rare, but supported).
|
|
661
|
+
"""
|
|
662
|
+
|
|
663
|
+
id: str = ""
|
|
664
|
+
story: dict[str, Any] | None = None
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
@dataclass
|
|
668
|
+
class TrackChangesDecide(Command):
|
|
669
|
+
"""Accept or reject one or all tracked changes.
|
|
670
|
+
|
|
671
|
+
Mirrors SuperDoc's ``doc.trackChanges.decide``. ``decision`` is
|
|
672
|
+
``"accept"`` or ``"reject"``. ``target`` is either a single change
|
|
673
|
+
address (``{id, story?}``) or the bulk sentinel
|
|
674
|
+
(``{scope: "all"}``).
|
|
675
|
+
|
|
676
|
+
``change_mode`` overrides the batch-level mode for *this one
|
|
677
|
+
decide* — useful when you want to decide a change that itself was
|
|
678
|
+
made under tracked mode without recursively producing more
|
|
679
|
+
changes. Default behaviour: omit and let SuperDoc apply ``direct``.
|
|
680
|
+
"""
|
|
681
|
+
|
|
682
|
+
decision: str = "accept" # "accept" | "reject"
|
|
683
|
+
target: dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
684
|
+
force: bool | None = None
|
|
685
|
+
expected_revision: int | None = None
|
|
686
|
+
change_mode: str | None = None
|
|
687
|
+
|
|
688
|
+
|
|
625
689
|
# ---------------------------------------------------------------------------
|
|
626
690
|
# Helpers
|
|
627
691
|
# ---------------------------------------------------------------------------
|
|
@@ -649,6 +713,9 @@ _QUERY_TYPES: frozenset[str] = frozenset(
|
|
|
649
713
|
"HeaderFootersGet",
|
|
650
714
|
"HeaderFootersResolve",
|
|
651
715
|
"SelectionCurrent",
|
|
716
|
+
# Track-changes reads — never mutate, always flush.
|
|
717
|
+
"TrackChangesList",
|
|
718
|
+
"TrackChangesGet",
|
|
652
719
|
}
|
|
653
720
|
)
|
|
654
721
|
|
|
@@ -671,6 +738,10 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
|
|
|
671
738
|
# it in a Comment proxy.
|
|
672
739
|
"CommentsCreate",
|
|
673
740
|
"CommentsPatch",
|
|
741
|
+
# TrackChangesDecide returns {success, inserted, updated, removed}
|
|
742
|
+
# describing the entities affected by accept/reject — callers
|
|
743
|
+
# need that synchronously to invalidate their Revision proxies.
|
|
744
|
+
"TrackChangesDecide",
|
|
674
745
|
}
|
|
675
746
|
)
|
|
676
747
|
|
|
@@ -745,6 +816,10 @@ __all__ = [
|
|
|
745
816
|
"TablesGetCells",
|
|
746
817
|
"TablesGetProperties",
|
|
747
818
|
"MarkdownToFragment",
|
|
819
|
+
# Track Changes
|
|
820
|
+
"TrackChangesList",
|
|
821
|
+
"TrackChangesGet",
|
|
822
|
+
"TrackChangesDecide",
|
|
748
823
|
# Helpers
|
|
749
824
|
"is_query",
|
|
750
825
|
"is_response_bearing",
|
|
@@ -17,7 +17,8 @@ from __future__ import annotations
|
|
|
17
17
|
import base64
|
|
18
18
|
import io
|
|
19
19
|
import sys
|
|
20
|
-
from
|
|
20
|
+
from contextlib import contextmanager
|
|
21
|
+
from typing import TYPE_CHECKING, BinaryIO, Generator, Iterator
|
|
21
22
|
|
|
22
23
|
from docx._batching import run_sync
|
|
23
24
|
from docx.client import Session
|
|
@@ -34,6 +35,7 @@ if TYPE_CHECKING:
|
|
|
34
35
|
from collections.abc import Sequence
|
|
35
36
|
|
|
36
37
|
from docx.comments import Comment, Comments
|
|
38
|
+
from docx.revisions import Revisions
|
|
37
39
|
from docx.shape import InlineShape
|
|
38
40
|
from docx.styles.style import ParagraphStyle, TableStyle
|
|
39
41
|
from docx.table import Table
|
|
@@ -57,6 +59,9 @@ class Document:
|
|
|
57
59
|
asset_id: str,
|
|
58
60
|
http_base_url: str | None = None,
|
|
59
61
|
http_api_key: str | None = None,
|
|
62
|
+
user_name: str | None = None,
|
|
63
|
+
user_email: str | None = None,
|
|
64
|
+
track_revisions: bool = False,
|
|
60
65
|
) -> None:
|
|
61
66
|
self._session: Session = Session(
|
|
62
67
|
asset_id=asset_id,
|
|
@@ -65,6 +70,12 @@ class Document:
|
|
|
65
70
|
)
|
|
66
71
|
self._saved: bool = False
|
|
67
72
|
self._closed: bool = False
|
|
73
|
+
# Track-changes state. Defaults are deferred to the buffer (set
|
|
74
|
+
# only when non-default) so existing callers retain the
|
|
75
|
+
# baseline "no envelope changeMode/user" behavior.
|
|
76
|
+
self._pending_user_name: str | None = user_name
|
|
77
|
+
self._pending_user_email: str | None = user_email
|
|
78
|
+
self._pending_track_revisions: bool = bool(track_revisions)
|
|
68
79
|
|
|
69
80
|
@classmethod
|
|
70
81
|
def create(
|
|
@@ -75,6 +86,9 @@ class Document:
|
|
|
75
86
|
api_key: str | None = None,
|
|
76
87
|
parent_folder_id: str | None = None,
|
|
77
88
|
workspace_id: str | None = None,
|
|
89
|
+
user_name: str | None = None,
|
|
90
|
+
user_email: str | None = None,
|
|
91
|
+
track_revisions: bool = False,
|
|
78
92
|
) -> "Document":
|
|
79
93
|
"""Create a new SuperDocument asset and return an opened Document.
|
|
80
94
|
|
|
@@ -120,6 +134,9 @@ class Document:
|
|
|
120
134
|
asset_id=result["asset_id"],
|
|
121
135
|
http_base_url=base_url,
|
|
122
136
|
http_api_key=api_key,
|
|
137
|
+
user_name=user_name,
|
|
138
|
+
user_email=user_email,
|
|
139
|
+
track_revisions=track_revisions,
|
|
123
140
|
)
|
|
124
141
|
|
|
125
142
|
# ---- Public properties ----
|
|
@@ -262,10 +279,16 @@ class Document:
|
|
|
262
279
|
|
|
263
280
|
@property
|
|
264
281
|
def settings(self):
|
|
265
|
-
"""Return a minimal Settings proxy
|
|
282
|
+
"""Return a minimal Settings proxy.
|
|
283
|
+
|
|
284
|
+
Most Word app settings aren't surfaced by Superdoc.
|
|
285
|
+
``Settings.track_revisions`` is wired through to
|
|
286
|
+
:attr:`Document.track_revisions` so python-docx-style call sites
|
|
287
|
+
(``doc.settings.track_revisions = True``) work end-to-end.
|
|
288
|
+
"""
|
|
266
289
|
from docx.settings import Settings
|
|
267
290
|
|
|
268
|
-
return Settings(session=self._session)
|
|
291
|
+
return Settings(session=self._session, document=self)
|
|
269
292
|
|
|
270
293
|
@property
|
|
271
294
|
def element(self):
|
|
@@ -274,6 +297,121 @@ class Document:
|
|
|
274
297
|
|
|
275
298
|
part = element
|
|
276
299
|
|
|
300
|
+
# ---- Track changes (Athena extension — see docx.revisions) ----
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def track_revisions(self) -> bool:
|
|
304
|
+
"""Whether subsequent mutations are recorded as tracked
|
|
305
|
+
revisions instead of direct edits.
|
|
306
|
+
|
|
307
|
+
Setting this to ``True`` flushes any pending buffered commands
|
|
308
|
+
(so they retain their previous semantics) before switching the
|
|
309
|
+
mode for new commands. Toggling repeatedly is safe — a no-op
|
|
310
|
+
when the value doesn't change.
|
|
311
|
+
|
|
312
|
+
DEVIATION FROM python-docx: upstream has no top-level
|
|
313
|
+
``track_revisions`` property. The closest thing is
|
|
314
|
+
``Document.settings.element`` carrying ``<w:trackChanges/>``.
|
|
315
|
+
We expose this as a Document-level toggle for ergonomics; the
|
|
316
|
+
``Document.settings.track_revisions`` alias gives parity-
|
|
317
|
+
friendly call sites the same surface.
|
|
318
|
+
"""
|
|
319
|
+
return self._pending_track_revisions
|
|
320
|
+
|
|
321
|
+
@track_revisions.setter
|
|
322
|
+
def track_revisions(self, value: bool) -> None:
|
|
323
|
+
new_value = bool(value)
|
|
324
|
+
if new_value == self._pending_track_revisions:
|
|
325
|
+
return
|
|
326
|
+
self._pending_track_revisions = new_value
|
|
327
|
+
# If the session is open, push immediately so subsequent
|
|
328
|
+
# mutations land under the new mode. Otherwise the value is
|
|
329
|
+
# captured and synced at session-open time.
|
|
330
|
+
buffer = self._buffer()
|
|
331
|
+
if buffer is not None:
|
|
332
|
+
buffer.set_change_mode("tracked" if new_value else None)
|
|
333
|
+
|
|
334
|
+
@contextmanager
|
|
335
|
+
def tracked_revisions(self) -> "Generator[None, None, None]":
|
|
336
|
+
"""Scope ``track_revisions=True`` to a ``with`` block.
|
|
337
|
+
|
|
338
|
+
Restores the previous value (typically ``False``) on exit, even
|
|
339
|
+
if the body raises. Useful for "make these specific edits as
|
|
340
|
+
tracked changes" without leaking the mode to surrounding code::
|
|
341
|
+
|
|
342
|
+
with doc.tracked_revisions():
|
|
343
|
+
doc.add_paragraph("Reviewer note: please verify.")
|
|
344
|
+
# ...
|
|
345
|
+
|
|
346
|
+
The mode change forces a buffer flush at entry and exit, so
|
|
347
|
+
prior buffered ops keep their original mode.
|
|
348
|
+
"""
|
|
349
|
+
previous: bool = self._pending_track_revisions
|
|
350
|
+
self.track_revisions = True
|
|
351
|
+
try:
|
|
352
|
+
yield
|
|
353
|
+
finally:
|
|
354
|
+
self.track_revisions = previous
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def user_name(self) -> "str | None":
|
|
358
|
+
"""The author identity threaded through to SuperDoc on
|
|
359
|
+
session-open, used for tracked-change attribution.
|
|
360
|
+
|
|
361
|
+
``None`` means "fall back to the docx-studio service account".
|
|
362
|
+
Setting after the session is open does not retro-actively
|
|
363
|
+
rename existing changes — SuperDoc honors the value at session
|
|
364
|
+
creation.
|
|
365
|
+
"""
|
|
366
|
+
return self._pending_user_name
|
|
367
|
+
|
|
368
|
+
@user_name.setter
|
|
369
|
+
def user_name(self, value: "str | None") -> None:
|
|
370
|
+
if value is not None and not isinstance(value, str):
|
|
371
|
+
raise ValidationError(
|
|
372
|
+
f"user_name must be a string or None; got {type(value)!r}"
|
|
373
|
+
)
|
|
374
|
+
self._pending_user_name = value or None
|
|
375
|
+
buffer = self._buffer()
|
|
376
|
+
if buffer is not None:
|
|
377
|
+
buffer.set_user(self._pending_user_name, self._pending_user_email)
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def user_email(self) -> "str | None":
|
|
381
|
+
"""Optional email displayed alongside the author name in
|
|
382
|
+
tracked-change tooltips. Same lifecycle caveats as
|
|
383
|
+
:attr:`user_name`."""
|
|
384
|
+
return self._pending_user_email
|
|
385
|
+
|
|
386
|
+
@user_email.setter
|
|
387
|
+
def user_email(self, value: "str | None") -> None:
|
|
388
|
+
if value is not None and not isinstance(value, str):
|
|
389
|
+
raise ValidationError(
|
|
390
|
+
f"user_email must be a string or None; got {type(value)!r}"
|
|
391
|
+
)
|
|
392
|
+
self._pending_user_email = value or None
|
|
393
|
+
buffer = self._buffer()
|
|
394
|
+
if buffer is not None:
|
|
395
|
+
buffer.set_user(self._pending_user_name, self._pending_user_email)
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def revisions(self) -> "Revisions":
|
|
399
|
+
"""Return the document's tracked-changes collection.
|
|
400
|
+
|
|
401
|
+
Iterating yields :class:`docx.revisions.Revision` proxies.
|
|
402
|
+
Bulk-resolve via :meth:`Revisions.accept_all` /
|
|
403
|
+
:meth:`Revisions.reject_all`; per-change resolve via
|
|
404
|
+
:meth:`Revision.accept` / :meth:`Revision.reject`.
|
|
405
|
+
|
|
406
|
+
DEVIATION FROM python-docx: upstream has no track-changes API
|
|
407
|
+
whatsoever. This is an Athena extension wired to SuperDoc's
|
|
408
|
+
``doc.trackChanges.{list,get,decide}``.
|
|
409
|
+
"""
|
|
410
|
+
from docx.revisions import Revisions
|
|
411
|
+
|
|
412
|
+
self._ensure_open()
|
|
413
|
+
return Revisions(session=self._session)
|
|
414
|
+
|
|
277
415
|
# ---- Comments (stubbed — see docx-studio/PYTHON_DOCX_PARITY_GAPS.md § 3.1) ----
|
|
278
416
|
|
|
279
417
|
@property
|
|
@@ -661,15 +799,20 @@ class Document:
|
|
|
661
799
|
run_sync(self._session.save(in_place=True))
|
|
662
800
|
self._saved = True
|
|
663
801
|
|
|
664
|
-
def save(self, path_or_stream: "str | BinaryIO") -> None: # noqa: ARG002
|
|
802
|
+
def save(self, path_or_stream: "str | BinaryIO | None" = None) -> None: # noqa: ARG002
|
|
665
803
|
"""Flush pending edits to Keryx.
|
|
666
804
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
``save()``
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
805
|
+
Intentional deviation from python-docx: ``path_or_stream`` is
|
|
806
|
+
optional. Stock python-docx requires it and raises ``TypeError``
|
|
807
|
+
on ``save()`` no-arg, but in this asset-backed SDK there is no
|
|
808
|
+
local file to write to — writes are always in-place against the
|
|
809
|
+
Y.Doc. Agent-generated code reflexively calls ``doc.save()`` and
|
|
810
|
+
forcing the upstream signature broke every such invocation
|
|
811
|
+
(`Untitled Word Document` session
|
|
812
|
+
``thread_3f6b59e0-ae25-4f6e-8391-c286b17a3334``). The argument
|
|
813
|
+
is accepted for parity-friendly call sites but ignored at
|
|
814
|
+
runtime. To export to a local file, use the Olympus UI's
|
|
815
|
+
Export DOCX.
|
|
673
816
|
"""
|
|
674
817
|
self._flush()
|
|
675
818
|
|
|
@@ -688,9 +831,9 @@ class Document:
|
|
|
688
831
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001
|
|
689
832
|
if exc_type is None and not self._saved and self._session.is_open:
|
|
690
833
|
try:
|
|
691
|
-
#
|
|
692
|
-
#
|
|
693
|
-
#
|
|
834
|
+
# Drain via _flush() directly — save() works no-arg as
|
|
835
|
+
# an intentional deviation, but skipping it avoids the
|
|
836
|
+
# public-API hop on the autosave path.
|
|
694
837
|
self._flush()
|
|
695
838
|
except Exception as e:
|
|
696
839
|
_log_warn(f"autosave failed for {self._session.asset_id}: {e}")
|
|
@@ -705,6 +848,36 @@ class Document:
|
|
|
705
848
|
)
|
|
706
849
|
if not self._session.is_open:
|
|
707
850
|
run_sync(self._session.open())
|
|
851
|
+
# Propagate pending track-changes state to the freshly-built
|
|
852
|
+
# buffer. Done here (and not at construction) because the
|
|
853
|
+
# buffer doesn't exist until session.open() runs.
|
|
854
|
+
self._sync_track_changes_state()
|
|
855
|
+
|
|
856
|
+
def _buffer(self):
|
|
857
|
+
"""Return the live :class:`CommandBuffer`, or ``None`` if the
|
|
858
|
+
session hasn't been opened yet."""
|
|
859
|
+
handle = getattr(self._session, "_doc_handle", None)
|
|
860
|
+
return getattr(handle, "buffer", None) if handle is not None else None
|
|
861
|
+
|
|
862
|
+
def _sync_track_changes_state(self) -> None:
|
|
863
|
+
"""Push pending track-changes state into the buffer.
|
|
864
|
+
|
|
865
|
+
Called when the session is opened (deferred init) and on
|
|
866
|
+
subsequent property setters. Idempotent — applying twice with
|
|
867
|
+
the same values is cheap (the buffer skips the flush when the
|
|
868
|
+
value didn't change).
|
|
869
|
+
"""
|
|
870
|
+
buffer = self._buffer()
|
|
871
|
+
if buffer is None:
|
|
872
|
+
return
|
|
873
|
+
if self._pending_user_name is not None or self._pending_user_email is not None:
|
|
874
|
+
buffer.set_user(
|
|
875
|
+
self._pending_user_name,
|
|
876
|
+
self._pending_user_email,
|
|
877
|
+
)
|
|
878
|
+
buffer.set_change_mode(
|
|
879
|
+
"tracked" if self._pending_track_revisions else None
|
|
880
|
+
)
|
|
708
881
|
|
|
709
882
|
|
|
710
883
|
# ---- Module-level helpers ----
|