athena-python-docx 0.11.2__tar.gz → 0.12.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.11.2 → athena_python_docx-0.12.0}/CLAUDE.md +76 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/PKG-INFO +1 -1
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_http_doc.py +18 -8
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/commands.py +36 -1
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/document.py +143 -0
- athena_python_docx-0.12.0/docx/oxml/__init__.py +228 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/section.py +65 -3
- athena_python_docx-0.12.0/docx/session.py +27 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/table.py +243 -29
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/paragraph.py +114 -3
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/parfmt.py +60 -35
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/run.py +31 -6
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/pyproject.toml +1 -1
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +1 -1
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +1 -11
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_athena_extensions_registry.py +1 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_block_not_found_error.py +18 -13
- athena_python_docx-0.12.0/tests/test_oxml_shim.py +198 -0
- athena_python_docx-0.11.2/docx/oxml/__init__.py +0 -148
- athena_python_docx-0.11.2/tests/test_oxml_shim.py +0 -123
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/.gitignore +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/README.md +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_http.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/api.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/charts.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/client.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/comments.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/errors.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/fields.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/math.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/settings.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/shape.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/shared.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/toc.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/docx/typing.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.11.2 → athena_python_docx-0.12.0}/uv.lock +0 -0
|
@@ -349,6 +349,82 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
349
349
|
wontfix upstream but the request keeps recurring. Routes through
|
|
350
350
|
`ExportPDF` (SuperDoc handles the actual conversion).
|
|
351
351
|
|
|
352
|
+
- **`Document.export_docx(path=None, *, include_revisions=False)
|
|
353
|
+
-> bytes`** (0.11.4+) — export the document's current state as
|
|
354
|
+
``.docx`` bytes, optionally writing to ``path``. Closes the gap
|
|
355
|
+
left by ``Document.save(path)`` raising
|
|
356
|
+
``LocalSaveTargetNotSupportedError`` in this SDK: parity test
|
|
357
|
+
harnesses and CI workflows need a programmatic .docx export to
|
|
358
|
+
diff against ``python-docx`` output, and pointing callers at
|
|
359
|
+
Olympus's Export DOCX action breaks every headless flow.
|
|
360
|
+
Routes through ``ExportDocx`` → SuperDoc's ``exportDocx``;
|
|
361
|
+
raises ``DocxError`` when the server build doesn't expose it
|
|
362
|
+
rather than silently writing zero bytes.
|
|
363
|
+
|
|
364
|
+
### 0.11.4 behavior fixes for cell-inner paragraphs
|
|
365
|
+
|
|
366
|
+
Format ops on cell-inner paragraphs (`paragraph.alignment`,
|
|
367
|
+
`paragraph.style`, every `paragraph_format` setter, and
|
|
368
|
+
`paragraph.add_hyperlink`) used to cascade-fail the entire HTTP
|
|
369
|
+
batch. SuperDoc 1.8.1's
|
|
370
|
+
``SetParagraphAlignment``/``SetParagraphStyle``/``SetParagraphIndentation``/``SetParagraphSpacing``/``CreateHyperlink``
|
|
371
|
+
reject cell-inner block targets with ``BlockNotFoundError``, and the
|
|
372
|
+
applier's all-or-nothing batch model meant one cell-paragraph
|
|
373
|
+
mistake mid-script nuked every other unrelated create/format command
|
|
374
|
+
queued before it (``applied: []``).
|
|
375
|
+
|
|
376
|
+
As of 0.11.4 these setters check the paragraph proxy's ``_in_cell``
|
|
377
|
+
flag (set by ``_Cell.add_paragraph`` and ``_Cell.paragraphs``) and
|
|
378
|
+
emit ``docx.text.paragraph.CellInnerFormatNotSupportedWarning``
|
|
379
|
+
instead of forwarding the broken command. The format op is dropped,
|
|
380
|
+
the surrounding batch survives, and the workaround pointer (
|
|
381
|
+
``cell.text = 'value'`` to materialize, then ``cell.paragraphs[0]``
|
|
382
|
+
for the addressable proxy) is in the warning text. Tracked at
|
|
383
|
+
``docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md`` § 13. Behavior pinned
|
|
384
|
+
by ``tests/test_athena_extensions_registry.py`` and the buffer
|
|
385
|
+
tests; ``add_hyperlink`` in a cell drops BOTH the URL and the link
|
|
386
|
+
text (empirically, ``Insert`` on a cell-inner paragraph also fails
|
|
387
|
+
with ``BlockNotFoundError`` — so the obvious ``add_run(text)``
|
|
388
|
+
fallback can't even land the visible text without nuking the
|
|
389
|
+
surrounding batch). The call returns a stub ``Hyperlink`` with an
|
|
390
|
+
empty range so callers don't crash; insert any visible text via
|
|
391
|
+
``cell.text = '...'`` before calling ``add_hyperlink`` if you need
|
|
392
|
+
the link text to survive.
|
|
393
|
+
|
|
394
|
+
### 0.11.4 behavior fixes elsewhere
|
|
395
|
+
|
|
396
|
+
- **`Sections.__getitem__` short-circuits on empty docs.**
|
|
397
|
+
``Document.create()`` doesn't materialize a section client-side,
|
|
398
|
+
so ``sections.list`` returns ``[]`` or times out at the full
|
|
399
|
+
command-budget. Pre-0.11.4 ``doc.sections[0]`` either raised an
|
|
400
|
+
un-contextualized ``IndexError`` from ``items[index]`` or hung
|
|
401
|
+
for ~60s. The cache-then-raise path now fails fast with a clear
|
|
402
|
+
workaround pointer and avoids re-issuing the slow query.
|
|
403
|
+
- **`Table._fresh_node_info` rotation tracking.** Each ``Table``
|
|
404
|
+
created via ``Document.add_table`` records its document-order
|
|
405
|
+
position as ``_doc_index``. When SuperDoc rotates the table's
|
|
406
|
+
nodeId between saves, the fallback resolves the rotated address
|
|
407
|
+
by position instead of always picking the last ``doc.find`` item.
|
|
408
|
+
Pre-0.11.4 the "take the last" heuristic silently mapped N
|
|
409
|
+
rotated proxies onto a single target, surfacing as
|
|
410
|
+
``ValidationError: Cell (r,c) not found in table table-auto-…``
|
|
411
|
+
on the next cell access.
|
|
412
|
+
- **`set_borders` accepts string and dict.** Both
|
|
413
|
+
``cell.set_borders(top="single 0.5pt #DDDDDD")`` and
|
|
414
|
+
``cell.set_borders(top={"style": "single", "size_pt": 0.5,
|
|
415
|
+
"color": "#DDDDDD"})`` work; the string form is normalized via
|
|
416
|
+
``_normalize_border_spec`` to the wire shape the server's Zod
|
|
417
|
+
validator expects. Pre-0.11.4 only the dict was accepted —
|
|
418
|
+
the string variant documented in the skill produced an immediate
|
|
419
|
+
HTTP 400 from the server's validator.
|
|
420
|
+
- **`Run.add_field` segments shape.** ``run.add_field("PAGE")`` (and
|
|
421
|
+
every other field kind) now emits ``at.segments`` with
|
|
422
|
+
``[{blockId, start, end}]`` instead of the ``{kind: "selection",
|
|
423
|
+
start, end}`` cursor envelope used by other create-ops.
|
|
424
|
+
SuperDoc's ``doc.fields.insert`` rejects the cursor form with
|
|
425
|
+
``SuperDocCliError: fields insert:at.segments is required``;
|
|
426
|
+
pre-0.11.4 the call took down the whole batch on every flush.
|
|
427
|
+
|
|
352
428
|
These additions are surfaced under their natural python-docx-shaped
|
|
353
429
|
names. When python-docx upstream eventually ships any of them
|
|
354
430
|
natively, the SDK should swap to the upstream signature in place;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.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>
|
|
@@ -262,14 +262,24 @@ _CELL_PARAGRAPH_HINT: str = (
|
|
|
262
262
|
"\n\nHint: SuperDoc 1.8.1 cannot format paragraphs nested inside "
|
|
263
263
|
"table cells via SetParagraphAlignment / SetParagraphStyle / "
|
|
264
264
|
"SetParagraphIndentation / SetParagraphSpacing / doc.insert with "
|
|
265
|
-
"a paragraph-block target
|
|
266
|
-
"
|
|
267
|
-
"isn't a top-level addressable block
|
|
268
|
-
|
|
269
|
-
"
|
|
270
|
-
"
|
|
271
|
-
"
|
|
272
|
-
"
|
|
265
|
+
"a paragraph-block target. The cell's original inner paragraph id "
|
|
266
|
+
"(returned by ``cell.paragraphs[0]`` on a freshly-loaded cell) "
|
|
267
|
+
"isn't a top-level addressable block.\n\nWorkaround: create a "
|
|
268
|
+
"fresh paragraph in the cell via ``para = cell.add_paragraph(text)`` "
|
|
269
|
+
"— 0.11.1+ routes that through ``doc.insert`` against the cell "
|
|
270
|
+
"with ``placement: 'insideEnd'``, so the returned ``Paragraph`` "
|
|
271
|
+
"proxy points at an addressable block. Apply format ops to that "
|
|
272
|
+
"proxy:\n\n"
|
|
273
|
+
' para = cell.add_paragraph("MAY 2026")\n'
|
|
274
|
+
" para.alignment = WD_ALIGN_PARAGRAPH.RIGHT # works\n"
|
|
275
|
+
" para.add_run(...).bold = True # also works\n\n"
|
|
276
|
+
"Do NOT use ``cell.paragraphs[0].alignment = X`` on a freshly-"
|
|
277
|
+
"loaded cell — that targets the original cell-inner paragraph "
|
|
278
|
+
"which can't be addressed for format ops. If the cell already "
|
|
279
|
+
"has agent-authored content, use ``cell.paragraphs[-1]`` (the "
|
|
280
|
+
"most-recently-inserted paragraph, which is addressable). "
|
|
281
|
+
"Tracked upstream at docx-studio/SUPERDOC_UPSTREAM_REQUESTS.md "
|
|
282
|
+
"§ 'cell-inner paragraph addressing'."
|
|
273
283
|
)
|
|
274
284
|
|
|
275
285
|
|
|
@@ -1451,6 +1451,30 @@ class ExportPDF(Command):
|
|
|
1451
1451
|
include_revisions: bool | None = None
|
|
1452
1452
|
|
|
1453
1453
|
|
|
1454
|
+
@dataclass
|
|
1455
|
+
class ExportDocx(Command):
|
|
1456
|
+
"""Export the document as a ``.docx`` byte stream, optionally storing
|
|
1457
|
+
it under ``destination`` (an asset id, S3 URL, or path).
|
|
1458
|
+
|
|
1459
|
+
Returns ``{bytes_base64, asset_id?}``. The docx-studio applier
|
|
1460
|
+
routes through SuperDoc's ``exportDocx`` SDK call when available;
|
|
1461
|
+
when the server build doesn't expose it, the command returns an
|
|
1462
|
+
empty payload and the Python SDK raises a clear ``DocxError``
|
|
1463
|
+
rather than silently writing zero bytes.
|
|
1464
|
+
|
|
1465
|
+
Athena extension beyond python-docx 1.x — closes the gap left by
|
|
1466
|
+
``Document.save(path)`` rejecting local targets (an asset-backed
|
|
1467
|
+
SDK can't fulfill the implied "write bytes to disk" contract from
|
|
1468
|
+
the buffer alone). Parity test harnesses against upstream
|
|
1469
|
+
``python-docx`` need a byte-for-byte export to diff against, and
|
|
1470
|
+
pointing users at Olympus's Export DOCX action breaks every CI
|
|
1471
|
+
workflow that runs headless.
|
|
1472
|
+
"""
|
|
1473
|
+
|
|
1474
|
+
destination: str | None = None
|
|
1475
|
+
include_revisions: bool | None = None
|
|
1476
|
+
|
|
1477
|
+
|
|
1454
1478
|
# ---------------------------------------------------------------------------
|
|
1455
1479
|
# Numbering / List metadata reads (Athena extension — python-docx
|
|
1456
1480
|
# issue #471 demands ``Paragraph.get_listnum()``-style access. The
|
|
@@ -1580,6 +1604,8 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
|
|
|
1580
1604
|
"CreateContentControl",
|
|
1581
1605
|
# ExportPDF returns bytes the caller needs synchronously.
|
|
1582
1606
|
"ExportPDF",
|
|
1607
|
+
# ExportDocx returns .docx bytes the caller needs synchronously.
|
|
1608
|
+
"ExportDocx",
|
|
1583
1609
|
# FindReplace returns a replacement count for the caller.
|
|
1584
1610
|
"FindReplace",
|
|
1585
1611
|
}
|
|
@@ -1619,8 +1645,15 @@ from docx._athena_extension import ( # noqa: E402 — local circular import saf
|
|
|
1619
1645
|
)
|
|
1620
1646
|
|
|
1621
1647
|
|
|
1622
|
-
def _mark_athena_extension_command(
|
|
1648
|
+
def _mark_athena_extension_command(
|
|
1649
|
+
cls: type, issue: "str | None", description: str
|
|
1650
|
+
) -> None:
|
|
1623
1651
|
setattr(cls, ATHENA_EXTENSION_ATTR, True)
|
|
1652
|
+
# ``issue`` is ``None`` for additions that don't have a 1:1 upstream
|
|
1653
|
+
# python-docx issue — typically things that are *not* missing from
|
|
1654
|
+
# python-docx but that our asset-backed SDK has to surface differently
|
|
1655
|
+
# (e.g. ``Document.export_docx`` because ``Document.save(path)``
|
|
1656
|
+
# rejects local targets in this SDK).
|
|
1624
1657
|
setattr(cls, ATHENA_EXTENSION_ISSUE_ATTR, issue)
|
|
1625
1658
|
setattr(cls, ATHENA_EXTENSION_DESCRIPTION_ATTR, description)
|
|
1626
1659
|
setattr(cls, ATHENA_EXTENSION_SINCE_ATTR, "0.11.0")
|
|
@@ -1672,6 +1705,7 @@ for _cls, _issue, _desc in [
|
|
|
1672
1705
|
(FindReplace, "python-docx#30", "Formatting-preserving find/replace"),
|
|
1673
1706
|
(IterRuns, "python-docx#980", "Run stream for run-isolation"),
|
|
1674
1707
|
(ExportPDF, "python-docx#113", "PDF export"),
|
|
1708
|
+
(ExportDocx, None, "DOCX export — local file roundtrip"),
|
|
1675
1709
|
(NumberingGet, "python-docx#471", "Numbering metadata read"),
|
|
1676
1710
|
(NumberingList, "python-docx#471", "Numbering enumeration"),
|
|
1677
1711
|
]:
|
|
@@ -1803,6 +1837,7 @@ __all__ = [
|
|
|
1803
1837
|
"FindReplace",
|
|
1804
1838
|
"IterRuns",
|
|
1805
1839
|
"ExportPDF",
|
|
1840
|
+
"ExportDocx",
|
|
1806
1841
|
"NumberingGet",
|
|
1807
1842
|
"NumberingList",
|
|
1808
1843
|
# Helpers
|
|
@@ -168,6 +168,14 @@ class Document:
|
|
|
168
168
|
# ``add_paragraph`` / ``add_heading`` / ``add_table`` resets this.
|
|
169
169
|
self._last_list_item_id: str | None = None
|
|
170
170
|
self._last_list_kind: str | None = None
|
|
171
|
+
# Document-order counter for tables created via ``add_table``.
|
|
172
|
+
# Captured at the time of creation and stamped on the Table
|
|
173
|
+
# proxy as ``_doc_index`` so the rotation fallback in
|
|
174
|
+
# ``Table._fresh_node_info`` can disambiguate when SuperDoc
|
|
175
|
+
# rotates the table's id between saves. Lazy-initialized to the
|
|
176
|
+
# current top-level table count so docs opened from an existing
|
|
177
|
+
# asset account for pre-existing tables.
|
|
178
|
+
self._table_doc_index_counter: int | None = None
|
|
171
179
|
|
|
172
180
|
@classmethod
|
|
173
181
|
@athena_extension(
|
|
@@ -1018,6 +1026,94 @@ class Document:
|
|
|
1018
1026
|
return b""
|
|
1019
1027
|
return b""
|
|
1020
1028
|
|
|
1029
|
+
@athena_extension(
|
|
1030
|
+
since="0.11.4",
|
|
1031
|
+
description="Document.export_docx — write the current state to a local .docx.",
|
|
1032
|
+
)
|
|
1033
|
+
def export_docx(
|
|
1034
|
+
self,
|
|
1035
|
+
path: "str | None" = None,
|
|
1036
|
+
*,
|
|
1037
|
+
include_revisions: bool = False,
|
|
1038
|
+
) -> bytes:
|
|
1039
|
+
"""Export the document's current state as ``.docx`` bytes.
|
|
1040
|
+
|
|
1041
|
+
Athena extension beyond python-docx 1.x. ``Document.save(path)``
|
|
1042
|
+
is intentionally rejected by this SDK (``LocalSaveTargetNotSupportedError``)
|
|
1043
|
+
because the buffer holds no local bytes — the source of truth
|
|
1044
|
+
is the SuperDoc Y.Doc on docx-studio. ``export_docx`` is the
|
|
1045
|
+
explicit "ship the current Y.Doc state to disk" escape hatch:
|
|
1046
|
+
the server runs SuperDoc's ``exportDocx`` against the live
|
|
1047
|
+
session and streams the bytes back, the SDK writes them to
|
|
1048
|
+
``path`` (when given) and returns them.
|
|
1049
|
+
|
|
1050
|
+
Parameters
|
|
1051
|
+
----------
|
|
1052
|
+
path:
|
|
1053
|
+
Optional local filesystem path. When provided, the bytes
|
|
1054
|
+
are written there (parents must exist). Always returned
|
|
1055
|
+
from the call regardless.
|
|
1056
|
+
include_revisions:
|
|
1057
|
+
When ``True``, accepted/pending tracked revisions are
|
|
1058
|
+
baked into the exported document instead of being
|
|
1059
|
+
collapsed to their resolved form.
|
|
1060
|
+
|
|
1061
|
+
Returns
|
|
1062
|
+
-------
|
|
1063
|
+
bytes
|
|
1064
|
+
The full ``.docx`` payload.
|
|
1065
|
+
|
|
1066
|
+
Raises
|
|
1067
|
+
------
|
|
1068
|
+
DocxError
|
|
1069
|
+
When the server-side SuperDoc build doesn't expose
|
|
1070
|
+
``exportDocx`` (older docx-studio deploys), the command
|
|
1071
|
+
returns an empty payload and this method raises a clear
|
|
1072
|
+
``DocxError`` instead of silently writing 0 bytes.
|
|
1073
|
+
"""
|
|
1074
|
+
from docx.commands import ExportDocx
|
|
1075
|
+
from docx.errors import DocxError
|
|
1076
|
+
|
|
1077
|
+
# Drain anything pending so the export reflects every queued
|
|
1078
|
+
# mutation. ``send_command`` for a response-bearing command
|
|
1079
|
+
# already flushes, but the explicit save makes intent visible
|
|
1080
|
+
# in tracebacks if anything below fails.
|
|
1081
|
+
self.save()
|
|
1082
|
+
|
|
1083
|
+
result = run_sync(
|
|
1084
|
+
self._session.send_command(
|
|
1085
|
+
ExportDocx(
|
|
1086
|
+
destination=None,
|
|
1087
|
+
include_revisions=include_revisions,
|
|
1088
|
+
),
|
|
1089
|
+
),
|
|
1090
|
+
)
|
|
1091
|
+
payload: bytes = b""
|
|
1092
|
+
if isinstance(result, dict):
|
|
1093
|
+
import base64
|
|
1094
|
+
|
|
1095
|
+
b64 = result.get("bytesBase64") or result.get("bytes_base64")
|
|
1096
|
+
if isinstance(b64, str):
|
|
1097
|
+
try:
|
|
1098
|
+
payload = base64.b64decode(b64)
|
|
1099
|
+
except (ValueError, TypeError):
|
|
1100
|
+
payload = b""
|
|
1101
|
+
|
|
1102
|
+
if not payload:
|
|
1103
|
+
raise DocxError(
|
|
1104
|
+
"docx-studio returned no bytes for ExportDocx. Either "
|
|
1105
|
+
"the server build doesn't expose SuperDoc's exportDocx "
|
|
1106
|
+
"yet (upgrade docx-studio) or the asset is in a state "
|
|
1107
|
+
"that can't be serialized to .docx. Use to_pdf() as a "
|
|
1108
|
+
"stopgap, or fall back to Olympus's Export DOCX action "
|
|
1109
|
+
"for an interactive export."
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
if path is not None:
|
|
1113
|
+
with open(path, "wb") as f:
|
|
1114
|
+
f.write(payload)
|
|
1115
|
+
return payload
|
|
1116
|
+
|
|
1021
1117
|
@athena_extension(issue=425, description="Document.add_bookmark.")
|
|
1022
1118
|
def add_bookmark(
|
|
1023
1119
|
self,
|
|
@@ -1377,6 +1473,13 @@ class Document:
|
|
|
1377
1473
|
# SuperDoc id via per-batch ``clientIdMap``, so any subsequent
|
|
1378
1474
|
# ``set_style({nodeId: cli_id})`` resolves correctly server-side.
|
|
1379
1475
|
client_node_id = self._mint_client_node_id("t")
|
|
1476
|
+
# Resolve the next document-order position for this new table.
|
|
1477
|
+
# First call queries ``doc.find`` to account for any tables that
|
|
1478
|
+
# already existed when the Document was opened — Document.create()
|
|
1479
|
+
# docs start at 0, ``Document(existing_asset_id)`` starts at
|
|
1480
|
+
# whatever count the asset has.
|
|
1481
|
+
doc_index = self._next_table_doc_index()
|
|
1482
|
+
|
|
1380
1483
|
table_params: dict = {
|
|
1381
1484
|
"rows": rows,
|
|
1382
1485
|
"columns": cols,
|
|
@@ -1402,10 +1505,50 @@ class Document:
|
|
|
1402
1505
|
node_id=node_id,
|
|
1403
1506
|
rows=rows,
|
|
1404
1507
|
columns=cols,
|
|
1508
|
+
doc_index=doc_index,
|
|
1405
1509
|
)
|
|
1406
1510
|
self._register_proxy_id(client_node_id, tbl)
|
|
1407
1511
|
return tbl
|
|
1408
1512
|
|
|
1513
|
+
def _next_table_doc_index(self) -> int:
|
|
1514
|
+
"""Return the session-relative position the next ``add_table``
|
|
1515
|
+
will occupy, then increment the counter.
|
|
1516
|
+
|
|
1517
|
+
``_doc_index`` is intentionally session-relative (0-based among
|
|
1518
|
+
tables added in *this* Document handle's lifetime) rather than
|
|
1519
|
+
document-absolute. Pre-0.11.4 (the lazy-init attempt) queried
|
|
1520
|
+
``doc.find({"type": "table"})`` on the first ``add_table`` to
|
|
1521
|
+
seed the counter with the count of pre-existing tables — but
|
|
1522
|
+
that added a stray ``find`` op to every Document's wire log,
|
|
1523
|
+
which broke the op-snapshot drift tests in
|
|
1524
|
+
``tests/fidelity/op_snapshots/``. ``Table._fresh_node_info`` does
|
|
1525
|
+
the offset math at rotation-lookup time instead (``items[len(
|
|
1526
|
+
items) - session_count + _doc_index]``), reading the live
|
|
1527
|
+
session count off the buffer attribute set below. For
|
|
1528
|
+
``Document.create()`` docs (the common case) the offset is 0
|
|
1529
|
+
and the lookup degenerates to the obvious ``items[_doc_index]``.
|
|
1530
|
+
|
|
1531
|
+
Defensive against unit tests that construct ``Document`` via
|
|
1532
|
+
``Document.__new__`` (skipping ``__init__``) — those instances
|
|
1533
|
+
don't have ``_table_doc_index_counter`` set, so we read it via
|
|
1534
|
+
``getattr`` and fall back to a fresh local counter that we
|
|
1535
|
+
write back through ``setattr``.
|
|
1536
|
+
"""
|
|
1537
|
+
counter = getattr(self, "_table_doc_index_counter", None) or 0
|
|
1538
|
+
idx = counter
|
|
1539
|
+
new_count = idx + 1
|
|
1540
|
+
self._table_doc_index_counter = new_count
|
|
1541
|
+
# Expose the live session count via the session so Table proxies
|
|
1542
|
+
# can compute the offset in ``_fresh_node_info`` rotation
|
|
1543
|
+
# fallbacks without holding a direct reference back to Document.
|
|
1544
|
+
try:
|
|
1545
|
+
session = getattr(self, "_session", None)
|
|
1546
|
+
if session is not None:
|
|
1547
|
+
session._table_session_count = new_count # type: ignore[attr-defined]
|
|
1548
|
+
except Exception:
|
|
1549
|
+
pass
|
|
1550
|
+
return idx
|
|
1551
|
+
|
|
1409
1552
|
def add_picture(
|
|
1410
1553
|
self,
|
|
1411
1554
|
image_path_or_stream: str | BinaryIO,
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Stub package — ``docx.oxml`` is intentionally unavailable in
|
|
2
|
+
athena-python-docx.
|
|
3
|
+
|
|
4
|
+
python-docx upstream exposes raw OOXML element classes (``OxmlElement``,
|
|
5
|
+
``CT_*``, ``qn``, ``nsmap``, …) for advanced users who manipulate
|
|
6
|
+
Word's XML directly. athena-python-docx is backed by a SuperDoc Y.Doc
|
|
7
|
+
+ Keryx pipeline, not a local OOXML tree, so there is no XML element
|
|
8
|
+
to manipulate.
|
|
9
|
+
|
|
10
|
+
Before 0.8.0 attempts to ``from docx.oxml.ns import qn`` failed with
|
|
11
|
+
the stdlib's generic ``ModuleNotFoundError: No module named
|
|
12
|
+
'docx.oxml'`` — agent code had to discover the gap by retry. 0.8.0
|
|
13
|
+
turned this package into a typed stub, but the stub raised
|
|
14
|
+
``OxmlNotAvailableError`` on attribute access — which is what
|
|
15
|
+
``from docx.oxml.ns import qn`` triggers, so the import statement
|
|
16
|
+
itself crashed before any line of the agent's actual logic ran. Common
|
|
17
|
+
python-docx convention is to declare ``qn`` / ``OxmlElement`` at the
|
|
18
|
+
top of every script "just in case," and that preamble killed scripts
|
|
19
|
+
that only used the high-level API.
|
|
20
|
+
|
|
21
|
+
As of 0.11.3 the stub is **lazy**: ``from docx.oxml.ns import qn``
|
|
22
|
+
succeeds and returns a sentinel proxy that defers the typed error
|
|
23
|
+
until the symbol is actually called or further-attribute-accessed.
|
|
24
|
+
Scripts that import for parity but never use the symbol now run
|
|
25
|
+
cleanly; scripts that reach for ``qn("w:tcW")`` still get the
|
|
26
|
+
actionable error pointing at the high-level alternative.
|
|
27
|
+
|
|
28
|
+
The gap is documented in ``docx-studio/python-sdk/CLAUDE.md`` § "Intentionally
|
|
29
|
+
omitted" and locked into the parity matrix via
|
|
30
|
+
``tests/parity/intentional_deviations.json`` (``docx.oxml**``).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import sys
|
|
36
|
+
import types
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OxmlNotAvailableError(ImportError):
|
|
41
|
+
"""Raised when a ``docx.oxml.*`` symbol is actually used.
|
|
42
|
+
|
|
43
|
+
Inherits :class:`ImportError` so callers using
|
|
44
|
+
``try: from docx.oxml import X / except ImportError`` still match
|
|
45
|
+
after 0.11.3's lazy-stub change (the import itself succeeds, but
|
|
46
|
+
the first call site against the imported name raises this — and
|
|
47
|
+
code that uses ``except ImportError`` around a use-site still
|
|
48
|
+
catches it).
|
|
49
|
+
|
|
50
|
+
Use ``except OxmlNotAvailableError`` to distinguish a "no oxml
|
|
51
|
+
surface" miss from a real import-resolution failure or other
|
|
52
|
+
``ImportError`` cause.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_MESSAGE: str = (
|
|
57
|
+
"docx.oxml is not available in athena-python-docx — the SuperDoc "
|
|
58
|
+
"backend doesn't expose raw OOXML elements. Use the high-level "
|
|
59
|
+
"python-docx API (Document.add_paragraph, Run.bold, _Cell.text, …) "
|
|
60
|
+
"or the typed command surface in docx.commands for any mutation "
|
|
61
|
+
"the high-level API doesn't cover. "
|
|
62
|
+
"See docx-studio/python-sdk/CLAUDE.md § 'Intentionally omitted' for "
|
|
63
|
+
"the parity rationale."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _LazyRaisingProxy:
|
|
68
|
+
"""Sentinel returned by the ``docx.oxml`` stub for any imported name.
|
|
69
|
+
|
|
70
|
+
Behaves as a callable, an attribute-accessible namespace, and an
|
|
71
|
+
awaitable for the union of common python-docx patterns:
|
|
72
|
+
|
|
73
|
+
* ``from docx.oxml.ns import qn`` → ``qn`` is a _LazyRaisingProxy.
|
|
74
|
+
Just importing it is a no-op. ``qn("w:tcW")`` raises with the
|
|
75
|
+
educated message.
|
|
76
|
+
* ``from docx.oxml import OxmlElement`` → same. ``OxmlElement(...)``
|
|
77
|
+
raises; ``OxmlElement.something`` raises.
|
|
78
|
+
* ``isinstance(x, OxmlElement)`` → raises Python's native
|
|
79
|
+
``TypeError: isinstance() arg 2 must be a type, a tuple of
|
|
80
|
+
types, or a union`` (CPython validates the second argument is a
|
|
81
|
+
type before consulting ``__instancecheck__``, so the proxy never
|
|
82
|
+
sees the call). Agents who reach for it land in the same
|
|
83
|
+
"this surface isn't real" place via a different error class —
|
|
84
|
+
the message names ``isinstance`` so the typed
|
|
85
|
+
:class:`OxmlNotAvailableError` route isn't required to surface
|
|
86
|
+
the gap.
|
|
87
|
+
|
|
88
|
+
The proxy carries the full dotted path so the error message points
|
|
89
|
+
at exactly the offending symbol — ``docx.oxml.ns.qn`` rather than a
|
|
90
|
+
generic "docx.oxml".
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
__slots__ = ("_path",)
|
|
94
|
+
|
|
95
|
+
def __init__(self, path: str) -> None:
|
|
96
|
+
# Bypass __setattr__ guard.
|
|
97
|
+
object.__setattr__(self, "_path", path)
|
|
98
|
+
|
|
99
|
+
def _raise(self, suffix: str = "") -> None:
|
|
100
|
+
path = object.__getattribute__(self, "_path")
|
|
101
|
+
target = f"{path}{suffix}" if suffix else path
|
|
102
|
+
raise OxmlNotAvailableError(f"{target} — {_MESSAGE}")
|
|
103
|
+
|
|
104
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any: # noqa: ANN401
|
|
105
|
+
self._raise()
|
|
106
|
+
|
|
107
|
+
def __getattr__(self, name: str) -> Any: # noqa: ANN401
|
|
108
|
+
if name.startswith("__") and name.endswith("__"):
|
|
109
|
+
raise AttributeError(name)
|
|
110
|
+
self._raise(f".{name}")
|
|
111
|
+
|
|
112
|
+
def __setattr__(self, name: str, value: Any) -> None: # noqa: ANN401
|
|
113
|
+
# Block writes to the proxy so agents that try
|
|
114
|
+
# ``OxmlElement.tag = "..."`` get the typed error rather than a
|
|
115
|
+
# silent attribute set on the sentinel.
|
|
116
|
+
if name == "_path":
|
|
117
|
+
object.__setattr__(self, name, value)
|
|
118
|
+
return
|
|
119
|
+
self._raise(f".{name}")
|
|
120
|
+
|
|
121
|
+
def __repr__(self) -> str:
|
|
122
|
+
path = object.__getattribute__(self, "_path")
|
|
123
|
+
return f"<docx.oxml lazy stub {path!r} — call to raise>"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class _OxmlStubModule(types.ModuleType):
|
|
127
|
+
"""ModuleType subclass that returns :class:`_LazyRaisingProxy`
|
|
128
|
+
sentinels for any attribute access.
|
|
129
|
+
|
|
130
|
+
Installed into :data:`sys.modules` at package-init time so
|
|
131
|
+
qualified imports like ``from docx.oxml.ns import qn`` find a real
|
|
132
|
+
module object (avoiding ``ModuleNotFoundError``). The proxy
|
|
133
|
+
returned defers the typed error until the symbol is actually
|
|
134
|
+
invoked, letting top-level python-docx import blocks succeed.
|
|
135
|
+
|
|
136
|
+
No ``__slots__`` — ``types.ModuleType`` itself carries a
|
|
137
|
+
``__dict__``, so a slots declaration on a subclass is a no-op.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __getattr__(self, name: str) -> Any: # noqa: ANN401
|
|
141
|
+
if name.startswith("__") and name.endswith("__"):
|
|
142
|
+
# Dunder access (e.g. ``__path__`` during submodule import)
|
|
143
|
+
# must fall through to AttributeError so Python's import
|
|
144
|
+
# machinery can probe without triggering our typed error.
|
|
145
|
+
raise AttributeError(name)
|
|
146
|
+
path = self.__name__
|
|
147
|
+
proxy = _LazyRaisingProxy(f"{path}.{name}")
|
|
148
|
+
# Cache so repeated `getattr` returns the same proxy and so
|
|
149
|
+
# `from X import Y` followed by another `from X import Y`
|
|
150
|
+
# gives the same instance.
|
|
151
|
+
object.__setattr__(self, name, proxy)
|
|
152
|
+
return proxy
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Common upstream submodule paths. Listed explicitly rather than wild-
|
|
156
|
+
# carded so adding a new one is a visible diff. Every entry maps to a
|
|
157
|
+
# single sentinel module; no per-submodule state.
|
|
158
|
+
_SUBMODULES: tuple[str, ...] = (
|
|
159
|
+
"ns",
|
|
160
|
+
"parser",
|
|
161
|
+
"element",
|
|
162
|
+
"xmlchemy",
|
|
163
|
+
"exceptions",
|
|
164
|
+
"ooxml",
|
|
165
|
+
"coreprops",
|
|
166
|
+
"document",
|
|
167
|
+
"text",
|
|
168
|
+
"table",
|
|
169
|
+
"comments",
|
|
170
|
+
"header_footer",
|
|
171
|
+
"footnote",
|
|
172
|
+
"endnote",
|
|
173
|
+
"shared",
|
|
174
|
+
"shape",
|
|
175
|
+
"section",
|
|
176
|
+
"settings",
|
|
177
|
+
"styles",
|
|
178
|
+
"numbering",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _install_stub_submodules() -> None:
|
|
183
|
+
"""Pre-populate :data:`sys.modules` with stub entries for every
|
|
184
|
+
well-known ``docx.oxml.*`` submodule.
|
|
185
|
+
|
|
186
|
+
Idempotent — re-running is a no-op for entries already installed
|
|
187
|
+
(covers the rare case of a test that reloads the package).
|
|
188
|
+
"""
|
|
189
|
+
for short in _SUBMODULES:
|
|
190
|
+
full = f"docx.oxml.{short}"
|
|
191
|
+
if full in sys.modules:
|
|
192
|
+
continue
|
|
193
|
+
sys.modules[full] = _OxmlStubModule(full)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
_install_stub_submodules()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def __getattr__(name: str) -> Any: # noqa: ANN401
|
|
200
|
+
"""PEP 562 attribute hook for ``from docx.oxml import X``.
|
|
201
|
+
|
|
202
|
+
Submodule names (handled via :data:`sys.modules` injection in
|
|
203
|
+
:func:`_install_stub_submodules`) and dunder names fall through
|
|
204
|
+
normally. Everything else returns a :class:`_LazyRaisingProxy` so
|
|
205
|
+
the import succeeds and the typed error fires only on actual use.
|
|
206
|
+
|
|
207
|
+
The proxy is cached into module globals so a second access skips
|
|
208
|
+
``__getattr__`` and returns the same instance — matches the
|
|
209
|
+
per-submodule caching done by :meth:`_OxmlStubModule.__getattr__`.
|
|
210
|
+
"""
|
|
211
|
+
if name.startswith("__") and name.endswith("__"):
|
|
212
|
+
raise AttributeError(name)
|
|
213
|
+
if name in _SUBMODULES:
|
|
214
|
+
# Should already be present in sys.modules; defensive fallback
|
|
215
|
+
# so we never leak ModuleNotFoundError if the caller reloaded
|
|
216
|
+
# the package between calls.
|
|
217
|
+
full = f"docx.oxml.{name}"
|
|
218
|
+
mod = sys.modules.get(full)
|
|
219
|
+
if mod is None:
|
|
220
|
+
mod = _OxmlStubModule(full)
|
|
221
|
+
sys.modules[full] = mod
|
|
222
|
+
return mod
|
|
223
|
+
proxy = _LazyRaisingProxy(f"docx.oxml.{name}")
|
|
224
|
+
globals()[name] = proxy
|
|
225
|
+
return proxy
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
__all__ = ["OxmlNotAvailableError"]
|