athena-python-docx 0.10.0__tar.gz → 0.11.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.10.0 → athena_python_docx-0.11.0}/CLAUDE.md +213 -5
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/PKG-INFO +1 -1
- athena_python_docx-0.11.0/docx/__init__.py +46 -0
- athena_python_docx-0.11.0/docx/_athena_extension.py +384 -0
- athena_python_docx-0.11.0/docx/_http.py +379 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/_http_doc.py +158 -11
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/_image_utils.py +27 -0
- athena_python_docx-0.11.0/docx/_table_styles.py +92 -0
- athena_python_docx-0.11.0/docx/bookmarks.py +194 -0
- athena_python_docx-0.11.0/docx/charts.py +142 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/client.py +34 -0
- athena_python_docx-0.11.0/docx/commands.py +1812 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/document.py +470 -15
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/errors.py +24 -0
- athena_python_docx-0.11.0/docx/fields.py +151 -0
- athena_python_docx-0.11.0/docx/footnotes.py +248 -0
- athena_python_docx-0.11.0/docx/math.py +81 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/revisions.py +12 -0
- athena_python_docx-0.11.0/docx/sdt.py +216 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/section.py +77 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/shape.py +167 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/table.py +317 -20
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/paragraph.py +250 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/run.py +261 -0
- athena_python_docx-0.11.0/docx/toc.py +79 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/pyproject.toml +1 -1
- athena_python_docx-0.11.0/tests/fidelity/cases.py +473 -0
- athena_python_docx-0.11.0/tests/fidelity/firm_templates/README.md +107 -0
- athena_python_docx-0.11.0/tests/fidelity/firm_templates/__init__.py +11 -0
- athena_python_docx-0.11.0/tests/fidelity/firm_templates/_runner.py +144 -0
- athena_python_docx-0.11.0/tests/fidelity/firm_templates/extractor.py +183 -0
- athena_python_docx-0.11.0/tests/fidelity/firm_templates/test_pw_corpus.py +364 -0
- athena_python_docx-0.11.0/tests/fidelity/firm_templates/test_pw_research_digest.py +129 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/intentional_deviations.json +27 -1
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/reports/GAP_ANALYSIS.md +4 -3
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/reports/gap_report.json +51 -3
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/snapshots/athena_latest.json +9623 -3320
- athena_python_docx-0.11.0/tests/test_athena_extensions_contract.py +448 -0
- athena_python_docx-0.11.0/tests/test_athena_extensions_registry.py +298 -0
- athena_python_docx-0.11.0/tests/test_document_create_from_template.py +312 -0
- athena_python_docx-0.11.0/tests/test_merged_cell_secondary_slot.py +251 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_parity_round2.py +34 -1
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_style_acceptance.py +5 -2
- athena_python_docx-0.11.0/tests/test_table_style_id_resolution.py +173 -0
- athena_python_docx-0.11.0/tests/test_temporarily_unavailable.py +99 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_wire_contract.py +195 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/uv.lock +1 -1
- athena_python_docx-0.10.0/docx/__init__.py +0 -28
- athena_python_docx-0.10.0/docx/_http.py +0 -189
- athena_python_docx-0.10.0/docx/commands.py +0 -953
- athena_python_docx-0.10.0/tests/fidelity/cases.py +0 -176
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/.gitignore +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/README.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/api.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/comments.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/settings.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/shared.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/docx/typing.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.11.0}/tests/test_zod_wire_contract.py +0 -0
|
@@ -81,12 +81,46 @@ remaining work* for the exact wire-op shape needed to unblock each.
|
|
|
81
81
|
These differ from stock python-docx because the SDK is asset-backed,
|
|
82
82
|
not file-backed. Each is documented in the relevant docstring.
|
|
83
83
|
|
|
84
|
-
-
|
|
84
|
+
- **Built-in table style friendly names resolve to OOXML styleIds.**
|
|
85
|
+
Word's ``styles.xml`` ships built-in table styles whose styleIds
|
|
86
|
+
(``LightGrid-Accent1``, ``MediumShading1-Accent2``, …) differ from
|
|
87
|
+
their display names (``"Light Grid Accent 1"``, …). Stock
|
|
88
|
+
python-docx resolves the friendly form against the loaded
|
|
89
|
+
document's styles collection at write time. We can't read
|
|
90
|
+
``styles.xml`` over HTTP, so ``Document.add_table(style=...)`` /
|
|
91
|
+
``Table.style`` setter consult an in-process map
|
|
92
|
+
(``docx._table_styles.BUILTIN_TABLE_STYLE_IDS``) covering every
|
|
93
|
+
Word 2007 and Word 2013+ built-in table style (204 entries) before
|
|
94
|
+
the value reaches ``<w:tblStyle w:val="..."/>``. Without this
|
|
95
|
+
mapping Word can't resolve the friendly name in ``styles.xml``
|
|
96
|
+
and silently falls back to ``Normal Table`` (no borders, no
|
|
97
|
+
header fill, no banding). Custom user-defined style names pass
|
|
98
|
+
through unchanged. Whether Word actually renders a Word 2013+
|
|
99
|
+
style after resolution also depends on SuperDoc's exported
|
|
100
|
+
``styles.xml`` containing the matching ``<w:style w:type="table">``
|
|
101
|
+
definition — at SuperDoc 1.8.1 the exported ``styles.xml`` only
|
|
102
|
+
carries the Word 2007 set, so Word 2013+ styleIds resolve in the
|
|
103
|
+
``<w:tblStyle>`` element but still fall back to ``Normal Table``
|
|
104
|
+
until SuperDoc upstream ships an expanded ``styles.xml``.
|
|
105
|
+
|
|
106
|
+
- **`Document.create(name=, base_url=, api_key=, parent_folder_id=, workspace_id=, docx=, agora_base_url=)`**
|
|
85
107
|
classmethod. Stock python-docx returns a blank in-memory document
|
|
86
108
|
for `Document(None)`; we can't fabricate a SuperDocument asset
|
|
87
109
|
client-side, so net-new asset creation is a separate factory that
|
|
88
110
|
hits `POST {base_url}/docs/empty`. The constructor positional-arg
|
|
89
111
|
shape (`Document(asset_id)`) is preserved for parity.
|
|
112
|
+
- **`docx=path`** mirrors the upstream `Document(docx="template.docx")`
|
|
113
|
+
constructor kwarg: when provided, the local `.docx` is uploaded to
|
|
114
|
+
agora's `/api/super-docs/create-from-upload` and used as the seed
|
|
115
|
+
for the new SuperDocument. The bytes are mirrored to S3 server-side
|
|
116
|
+
so subsequent template-asset reuse is fast. Without `docx=`, the
|
|
117
|
+
default Athena template is applied as today.
|
|
118
|
+
- **`agora_base_url=`** is a transport-detail kwarg only required when
|
|
119
|
+
the SDK can't derive the agora host from `base_url` (or from
|
|
120
|
+
`$ATHENA_AGORA_BASE_URL`). The default substitution rule replaces
|
|
121
|
+
`docx-studio` with `agora` in the docx-studio host; explicit
|
|
122
|
+
`agora_base_url` is required for local development or unusual
|
|
123
|
+
deployment topologies.
|
|
90
124
|
|
|
91
125
|
- **`Document.asset_id: str`** read-only property — the Athena asset
|
|
92
126
|
id this Document is bound to (`asset_<uuid>`). Agent-callable so
|
|
@@ -141,6 +175,174 @@ not file-backed. Each is documented in the relevant docstring.
|
|
|
141
175
|
has zero track-changes API, so this entire surface is an Athena
|
|
142
176
|
extension wired to SuperDoc's `doc.trackChanges.{list,get,decide}`.
|
|
143
177
|
|
|
178
|
+
### Athena extensions for most-requested upstream features (v0.11.0+)
|
|
179
|
+
|
|
180
|
+
These surfaces are **additions** to python-docx, not deviations from
|
|
181
|
+
existing behavior — every upstream class, method, property, and
|
|
182
|
+
parameter remains 1:1 with python-docx 1.2.0. The parity ratchet at
|
|
183
|
+
`tests/parity/` is one-directional (upstream → athena) so Athena
|
|
184
|
+
extensions do not produce findings; they're listed here for human
|
|
185
|
+
review and so future contributors can find the rationale.
|
|
186
|
+
|
|
187
|
+
Each item below corresponds to a long-standing upstream feature gap.
|
|
188
|
+
Issue numbers reference `python-openxml/python-docx`.
|
|
189
|
+
|
|
190
|
+
- **`Paragraph.add_hyperlink(text, address, *, fragment=None,
|
|
191
|
+
tooltip=None) -> Hyperlink`** — appends a hyperlinked run.
|
|
192
|
+
python-docx surfaces `Hyperlink` as read-only; PR #74 has been open
|
|
193
|
+
since 2014 and is the single most-requested missing feature. Routes
|
|
194
|
+
through `CreateHyperlink` (SuperDoc `doc.hyperlinks.create`). Returns
|
|
195
|
+
the same `Hyperlink` class the read path already exposes. Also
|
|
196
|
+
available as `Run.add_hyperlink` for convenience.
|
|
197
|
+
|
|
198
|
+
- **`Run.add_field(kind, *, args=None, cached_result=None) -> Field`**
|
|
199
|
+
+ **`Document.fields: Fields`** — generic Word field insertion.
|
|
200
|
+
Supports `"PAGE"`, `"NUMPAGES"`, `"DATE"`, `"TIME"`, `"AUTHOR"`,
|
|
201
|
+
`"FILENAME"`, `"NUMWORDS"`, `"REF"`, `"SEQ"`, `"TOC"`, and the
|
|
202
|
+
generic `"FIELD"` escape hatch. python-docx has no field API
|
|
203
|
+
whatsoever; issue #498 (page numbers) and #31 (23 comments) demand
|
|
204
|
+
this. `Fields` is sequence-like; `Fields.refresh_all(scope?)`
|
|
205
|
+
forces a recompute. Routes through `CreateField` / `FieldsRefresh`
|
|
206
|
+
/ `FieldsList`.
|
|
207
|
+
|
|
208
|
+
- **`Document.bookmarks: Bookmarks`** + **`Paragraph.add_bookmark
|
|
209
|
+
(name)`** + **`Document.add_bookmark(name, *, paragraph=None,
|
|
210
|
+
run=None) -> Bookmark`** — named anchors for cross-references,
|
|
211
|
+
TOC entries, captions, and internal hyperlinks. python-docx
|
|
212
|
+
upstream has no bookmark API; issue #425 (35 comments) has been
|
|
213
|
+
open since 2017. Routes through `CreateBookmark` /
|
|
214
|
+
`DeleteBookmark` / `BookmarksList` / `BookmarksGet`.
|
|
215
|
+
|
|
216
|
+
- **`Document.footnotes: Footnotes`** + **`Run.add_footnote(text, *,
|
|
217
|
+
custom_mark=None) -> Footnote`** — footnote references with a
|
|
218
|
+
separate body story. python-docx Issue #1 (the first issue ever
|
|
219
|
+
filed, open since 2014) demands this; it drove the `bayoo-docx`
|
|
220
|
+
fork. Routes through `CreateFootnote` / `FootnotesList` /
|
|
221
|
+
`FootnotesGet` / `FootnotesPatch` / `FootnotesDelete`. The
|
|
222
|
+
parallel **`Document.endnotes: Endnotes`** + **`Run.add_endnote
|
|
223
|
+
(text, *, custom_mark=None)`** mirrors the same machinery for
|
|
224
|
+
end-of-section notes.
|
|
225
|
+
|
|
226
|
+
- **`Document.add_toc(*, levels=(1, 3), style_id=None,
|
|
227
|
+
include_hyperlinks=True, title=None, tab_leader="dot", at=None)
|
|
228
|
+
-> TableOfContents`** — inserts a TOC field at the cursor.
|
|
229
|
+
python-docx issue #36 (closed-without-action), #1207, #1403 demand
|
|
230
|
+
this. The returned `TableOfContents` proxy exposes `refresh()` to
|
|
231
|
+
recompute the cached result. Routes through `CreateTOC` /
|
|
232
|
+
`RefreshTOC`.
|
|
233
|
+
|
|
234
|
+
- **`Paragraph.add_caption(text, *, label="Figure", bookmark=None,
|
|
235
|
+
style=None) -> Paragraph`** — adds a caption paragraph with a SEQ
|
|
236
|
+
field for auto-numbering. python-docx issue #676 (11 👍). Routes
|
|
237
|
+
through `CreateCaption`.
|
|
238
|
+
|
|
239
|
+
- **`Run.add_cross_reference(target, *, reference_kind="text",
|
|
240
|
+
hyperlink=True) -> Field`** — REF field pointing at a bookmark or
|
|
241
|
+
SEQ. python-docx issue #97 (33 comments). Reference kinds:
|
|
242
|
+
`"text"`, `"number"`, `"page"`, `"label-number"`, `"above-below"`,
|
|
243
|
+
`"paragraph"`. Routes through `CreateCrossReference`.
|
|
244
|
+
|
|
245
|
+
- **`_Cell.set_borders(*, top=, bottom=, left=, right=,
|
|
246
|
+
inside_horizontal=, inside_vertical=)`** + **`_Cell.borders`**
|
|
247
|
+
property + **`Table.set_borders(...)`** — per-edge border control.
|
|
248
|
+
python-docx issue #201 (5 👍), #1238 demand a cell-borders API.
|
|
249
|
+
Routes through `SetCellBorders` / `SetTableBorders`.
|
|
250
|
+
|
|
251
|
+
- **`_Cell.set_shading(*, fill=, pattern=, color=)`** +
|
|
252
|
+
**`_Cell.shading`** property — cell fill / shading. python-docx
|
|
253
|
+
issue #146 (12 comments) demands this. Routes through
|
|
254
|
+
`SetCellShading`.
|
|
255
|
+
|
|
256
|
+
- **`_Cell.v_merge` / `_Cell.is_merge_continuation` /
|
|
257
|
+
`_Cell.is_merge_origin`** — robust merged-cell detection.
|
|
258
|
+
python-docx surfaces `_Cell.grid_span` (horizontal) but the
|
|
259
|
+
vertical merge state and master detection are issue #1312, #1434,
|
|
260
|
+
#1458. Pure derived properties from existing `tables.getCells`
|
|
261
|
+
data.
|
|
262
|
+
|
|
263
|
+
- **`Table.add_row(*, copy_format_from=None)`** — when `copy_format_from`
|
|
264
|
+
is provided (int row index or `_Row`), the new row inherits height,
|
|
265
|
+
borders, shading, vertical alignment, and run formatting from the
|
|
266
|
+
source row. python-docx issue #205 (9 👍). The no-arg form is
|
|
267
|
+
unchanged.
|
|
268
|
+
|
|
269
|
+
- **`InlineShape.alt_text` / `InlineShape.title` /
|
|
270
|
+
`InlineShape.decorative`** + mirrors on `Picture` — accessibility
|
|
271
|
+
metadata. python-docx PR #1530 is in flight but unreleased; the
|
|
272
|
+
feature has been requested for years. Routes through
|
|
273
|
+
`SetImageAltText` / `SetImageDecorative` (already wired in
|
|
274
|
+
SuperDoc).
|
|
275
|
+
|
|
276
|
+
- **`InlineShape.anchor(*, wrap="square", position_x_pt=,
|
|
277
|
+
position_y_pt=, relative_from_x=, relative_from_y=,
|
|
278
|
+
behind_text=)`** — promote an inline image to a floating
|
|
279
|
+
(anchored) image. python-docx is inline-only; issue #159 has been
|
|
280
|
+
open since 2017. Wrap modes: `"inline"`, `"square"`, `"tight"`,
|
|
281
|
+
`"through"`, `"top-bottom"`, `"behind"`, `"in-front"`. Routes
|
|
282
|
+
through `SetImageAnchor`.
|
|
283
|
+
|
|
284
|
+
- **`Document.add_watermark(*, text=None, image_path=None, ...)
|
|
285
|
+
-> str`** — page-watermark on every page. python-docx issue #845
|
|
286
|
+
(9 👍). Pass either `text` or `image_path`. Routes through
|
|
287
|
+
`CreateWatermark`. `Document.delete_watermark` is available via
|
|
288
|
+
the lower-level `DeleteWatermark` command.
|
|
289
|
+
|
|
290
|
+
- **`Document.charts: Charts`** + **`Document.add_chart(chart_type,
|
|
291
|
+
data, *, title=None, width_pt=None, height_pt=None, at=None)
|
|
292
|
+
-> Chart`** — chart insertion + data editing. python-docx issue
|
|
293
|
+
#179 (12 👍, 19 comments) demands creation, #1141 demands editing.
|
|
294
|
+
`chart_type` covers all Word 2016+ families. `Chart.data` setter
|
|
295
|
+
replaces the underlying table; `Chart.chart_type` setter switches
|
|
296
|
+
family. Routes through `CreateChart` / `SetChartData` /
|
|
297
|
+
`SetChartType` / `ChartsList` / `ChartsGet`.
|
|
298
|
+
|
|
299
|
+
- **`Run.add_math(content, *, format="mathml", display=False)
|
|
300
|
+
-> MathEquation`** — math equations. python-docx issue #320 (23
|
|
301
|
+
comments). Formats: `"mathml"`, `"omml"` (Word native), `"latex"`
|
|
302
|
+
(server-converted). Routes through `CreateMath` / `MathPatch`.
|
|
303
|
+
|
|
304
|
+
- **`Document.content_controls: ContentControls`** +
|
|
305
|
+
**`Document.add_content_control(control_kind, ...) -> ContentControl`**
|
|
306
|
+
— structured-document-tag (SDT) wrappers. python-docx issues #155,
|
|
307
|
+
#224 (15 comments), #1417 demand write support. Kinds:
|
|
308
|
+
`"rich-text"`, `"plain-text"`, `"picture"`, `"checkbox"`,
|
|
309
|
+
`"date"`, `"dropdown"`, `"combobox"`, `"repeating-section"`,
|
|
310
|
+
`"group"`. Routes through `CreateContentControl` /
|
|
311
|
+
`ContentControlsList` / `ContentControlsGet` /
|
|
312
|
+
`ContentControlsPatch` / `ContentControlsDelete`.
|
|
313
|
+
|
|
314
|
+
- **`Section.set_columns(*, count, equal_width=None, widths=None,
|
|
315
|
+
space_pt=None, separator=None)`** + **`Section.column_count`** +
|
|
316
|
+
**`Section.column_widths`** — multi-column section layout.
|
|
317
|
+
python-docx issue #167 (12 comments). Routes through
|
|
318
|
+
`SetSectionColumns`.
|
|
319
|
+
|
|
320
|
+
- **`Document.find_replace(pattern, replacement, *, regex=False,
|
|
321
|
+
match_case=False, whole_word=False, in_=None, max_replacements=None)
|
|
322
|
+
-> int`** — formatting-preserving find-and-replace primitive.
|
|
323
|
+
python-docx issues #30 (39 comments), #980 (12 👍) demand the
|
|
324
|
+
run-isolation primitive this provides. `in_` scopes to
|
|
325
|
+
`"body"` / `"headers"` / `"footers"` / `"footnotes"` /
|
|
326
|
+
`"endnotes"` / `"comments"` / `"all"`. Routes through
|
|
327
|
+
`FindReplace`.
|
|
328
|
+
|
|
329
|
+
- **`Paragraph.list_id` / `Paragraph.list_level` /
|
|
330
|
+
`Paragraph.list_format` / `Paragraph.list_number`** — list-item
|
|
331
|
+
numbering reads. python-docx issue #471 (13 👍) demands access to
|
|
332
|
+
list metadata. Routes through `NumberingGet`.
|
|
333
|
+
|
|
334
|
+
- **`Document.to_pdf(*, quality=None, destination=None,
|
|
335
|
+
include_revisions=False) -> bytes`** — render to PDF via the
|
|
336
|
+
Daytona-hosted LibreOffice. python-docx issue #113 was closed as
|
|
337
|
+
wontfix upstream but the request keeps recurring. Routes through
|
|
338
|
+
`ExportPDF` (SuperDoc handles the actual conversion).
|
|
339
|
+
|
|
340
|
+
These additions are surfaced under their natural python-docx-shaped
|
|
341
|
+
names. When python-docx upstream eventually ships any of them
|
|
342
|
+
natively, the SDK should swap to the upstream signature in place;
|
|
343
|
+
this section is the canonical reference for what each Athena
|
|
344
|
+
extension wires up.
|
|
345
|
+
|
|
144
346
|
### If you need a deviation
|
|
145
347
|
|
|
146
348
|
If there is a genuine technical reason why a deviation from python-docx is necessary:
|
|
@@ -158,10 +360,16 @@ This is a **thin HTTP client** that mimics the sync python-docx API.
|
|
|
158
360
|
- Every internal call constructs a typed `Command` dataclass
|
|
159
361
|
(`docx.commands`) and ships it through a `CommandBuffer`
|
|
160
362
|
(`docx._buffer`) which POSTs to docx-studio's `/docs/:id/commands`.
|
|
161
|
-
- HTTP transport: `requests.Session` with a Retry (3 attempts,
|
|
162
|
-
backoff
|
|
163
|
-
|
|
164
|
-
|
|
363
|
+
- HTTP transport: `requests.Session` with a Retry (3 attempts, exponential
|
|
364
|
+
backoff `factor=2.0` plus 0.5s jitter when urllib3 ≥2.0 supports it,
|
|
365
|
+
~14s total budget, 429/502/503/504). Once retries are exhausted on
|
|
366
|
+
502/503/504, the SDK raises `DocxStudioTemporarilyUnavailable`
|
|
367
|
+
(a `SessionError`) carrying `http_status` and an optional
|
|
368
|
+
`retry_after_seconds` parsed from the server's `Retry-After` header.
|
|
369
|
+
Agent code can catch this typed error to back off at the agent level
|
|
370
|
+
vs. treating it as a script bug. The legacy `transport="direct"`
|
|
371
|
+
(embedded Superdoc CLI + y-websocket from Python) was removed in
|
|
372
|
+
0.5.0; agent pods no longer embed Superdoc.
|
|
165
373
|
- **Transparent batching (0.7.0+):** Create* commands
|
|
166
374
|
(`CreateParagraph`, `CreateHeading`, `CreateTable`, `CreateImage`)
|
|
167
375
|
ALWAYS pre-mint a client-side UUID (`client_node_id`) and defer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.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>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""athena-python-docx — drop-in replacement for python-docx.
|
|
2
|
+
|
|
3
|
+
Calls translate into Superdoc SDK operations against a Keryx Y.Doc.
|
|
4
|
+
See CLAUDE.md for the API parity contract.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__version__ = "0.11.0"
|
|
10
|
+
|
|
11
|
+
from docx.api import Document
|
|
12
|
+
from docx._buffer import flush_all
|
|
13
|
+
# Re-exports python-docx ships at docx top-level for convenience.
|
|
14
|
+
from docx.shared import Emu, Inches, Pt, Cm, Mm, Twips, Length, RGBColor
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Document",
|
|
18
|
+
"Emu",
|
|
19
|
+
"Inches",
|
|
20
|
+
"Pt",
|
|
21
|
+
"Cm",
|
|
22
|
+
"Mm",
|
|
23
|
+
"Twips",
|
|
24
|
+
"Length",
|
|
25
|
+
"RGBColor",
|
|
26
|
+
"flush_all",
|
|
27
|
+
"__version__",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Athena-extension modules (no python-docx upstream parity surface). These
|
|
33
|
+
# are documented in python-sdk/CLAUDE.md § "Intentional deviations (additions
|
|
34
|
+
# / different semantics)" so the parity ratchet at tests/parity/ doesn't
|
|
35
|
+
# regress them. We don't auto-import them here because every one adds
|
|
36
|
+
# import-time cost for the common ``Document(asset_id)`` path; import the
|
|
37
|
+
# specific module you need:
|
|
38
|
+
#
|
|
39
|
+
# from docx.bookmarks import Bookmark
|
|
40
|
+
# from docx.fields import Field
|
|
41
|
+
# from docx.footnotes import Footnote, Endnote
|
|
42
|
+
# from docx.toc import TableOfContents
|
|
43
|
+
# from docx.charts import Chart
|
|
44
|
+
# from docx.math import MathEquation
|
|
45
|
+
# from docx.sdt import ContentControl
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""Marker decorator + audit helpers for Athena extensions beyond
|
|
2
|
+
python-docx 1.x.
|
|
3
|
+
|
|
4
|
+
This SDK MUST be a 1:1 replica of python-docx (see ``CLAUDE.md`` § *API
|
|
5
|
+
Parity Rule*). Anything that is NOT in upstream python-docx is an
|
|
6
|
+
**Athena extension** and must be:
|
|
7
|
+
|
|
8
|
+
1. Documented in ``CLAUDE.md`` § *Intentional deviations (additions /
|
|
9
|
+
different semantics)* — for the human-readable rationale.
|
|
10
|
+
2. Marked at runtime with :func:`athena_extension` — so introspection
|
|
11
|
+
tooling (parity audits, docs generation, IDE plugins) can identify
|
|
12
|
+
the addition without grepping prose, and so the
|
|
13
|
+
:func:`iter_athena_extensions` walker can enumerate every addition
|
|
14
|
+
in one place.
|
|
15
|
+
|
|
16
|
+
The decorator stamps the wrapped object with introspectable attributes:
|
|
17
|
+
|
|
18
|
+
* ``__athena_extension__`` — always ``True``
|
|
19
|
+
* ``__athena_extension_issue__`` — upstream python-docx issue ref, e.g.
|
|
20
|
+
``"python-docx#74"``
|
|
21
|
+
* ``__athena_extension_description__`` — one-line summary
|
|
22
|
+
* ``__athena_extension_since__`` — athena-python-docx SDK version that
|
|
23
|
+
introduced the surface
|
|
24
|
+
|
|
25
|
+
Apply at every Athena-extension layer:
|
|
26
|
+
|
|
27
|
+
* **Class** — decorator on the ``class`` statement::
|
|
28
|
+
|
|
29
|
+
@athena_extension(issue=1, description="Footnote proxy")
|
|
30
|
+
class Footnote: ...
|
|
31
|
+
|
|
32
|
+
* **Method / function** — decorator on the ``def`` statement::
|
|
33
|
+
|
|
34
|
+
@athena_extension(issue=74, description="Paragraph.add_hyperlink")
|
|
35
|
+
def add_hyperlink(self, ...): ...
|
|
36
|
+
|
|
37
|
+
* **Property** — decorator on the **getter** (before ``@property``)::
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@athena_extension(issue=471)
|
|
41
|
+
def list_id(self): ...
|
|
42
|
+
|
|
43
|
+
When the property has a setter, mark the setter's underlying function
|
|
44
|
+
too (so writes are auditable independently of reads)::
|
|
45
|
+
|
|
46
|
+
@list_id.setter
|
|
47
|
+
@athena_extension(issue=471)
|
|
48
|
+
def list_id(self, value): ...
|
|
49
|
+
|
|
50
|
+
* **Module-level constant** — for whole modules that are entirely
|
|
51
|
+
Athena extensions, set at top of file::
|
|
52
|
+
|
|
53
|
+
__athena_extension_module__ = True
|
|
54
|
+
__athena_extension_issue__ = "python-docx#1"
|
|
55
|
+
__athena_extension_description__ = "Footnotes / Endnotes"
|
|
56
|
+
|
|
57
|
+
The walker picks these up alongside per-member markers.
|
|
58
|
+
|
|
59
|
+
The decorator is import-time-cheap, has no runtime overhead at call
|
|
60
|
+
sites (it returns the original object after stamping), and never
|
|
61
|
+
modifies docstrings or signatures. The introspectable attributes are
|
|
62
|
+
parity-neutral — the upstream-vs-athena comparator in
|
|
63
|
+
``tests/parity/compare.py`` only looks at signatures + kinds, not at
|
|
64
|
+
function ``__dict__`` keys.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
from __future__ import annotations
|
|
68
|
+
|
|
69
|
+
import importlib
|
|
70
|
+
import pkgutil
|
|
71
|
+
from typing import Any, Callable, Iterator, TypeVar
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Attribute names — kept as module constants so audits and tests can
|
|
75
|
+
# reference them without re-typing the string everywhere.
|
|
76
|
+
ATHENA_EXTENSION_ATTR: str = "__athena_extension__"
|
|
77
|
+
ATHENA_EXTENSION_ISSUE_ATTR: str = "__athena_extension_issue__"
|
|
78
|
+
ATHENA_EXTENSION_DESCRIPTION_ATTR: str = "__athena_extension_description__"
|
|
79
|
+
ATHENA_EXTENSION_SINCE_ATTR: str = "__athena_extension_since__"
|
|
80
|
+
|
|
81
|
+
ATHENA_EXTENSION_MODULE_ATTR: str = "__athena_extension_module__"
|
|
82
|
+
|
|
83
|
+
# Default ``since`` value — overridable per-marker. Bumped when a new
|
|
84
|
+
# wave of additions lands; pre-existing extensions (Document.create,
|
|
85
|
+
# Document.revisions, etc.) carry their original since values.
|
|
86
|
+
_DEFAULT_SINCE: str = "0.11.0"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
T = TypeVar("T")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def athena_extension(
|
|
93
|
+
*,
|
|
94
|
+
issue: int | str | None = None,
|
|
95
|
+
description: str = "",
|
|
96
|
+
since: str = _DEFAULT_SINCE,
|
|
97
|
+
) -> Callable[[T], T]:
|
|
98
|
+
"""Decorator that marks a class / function / method as an Athena
|
|
99
|
+
extension beyond python-docx 1.x.
|
|
100
|
+
|
|
101
|
+
Stamps four introspectable attributes on the wrapped object:
|
|
102
|
+
|
|
103
|
+
- ``__athena_extension__`` — always ``True``
|
|
104
|
+
- ``__athena_extension_issue__`` — ``"python-docx#<n>"`` when
|
|
105
|
+
``issue`` is an int; passed through verbatim when a string;
|
|
106
|
+
``None`` when omitted
|
|
107
|
+
- ``__athena_extension_description__`` — ``description``
|
|
108
|
+
- ``__athena_extension_since__`` — ``since``
|
|
109
|
+
|
|
110
|
+
Returns the original object unchanged (apart from the new
|
|
111
|
+
attributes), so call sites pay zero runtime cost.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
issue: python-docx upstream issue / PR number that this
|
|
115
|
+
extension addresses. ``int`` (e.g. ``74``) is auto-formatted
|
|
116
|
+
as ``"python-docx#74"``; ``str`` is used verbatim.
|
|
117
|
+
description: One-line human summary. Optional but recommended
|
|
118
|
+
for non-obvious additions.
|
|
119
|
+
since: The athena-python-docx SDK version that first shipped
|
|
120
|
+
this surface. Defaults to the current default-since
|
|
121
|
+
(``0.11.0``).
|
|
122
|
+
|
|
123
|
+
Example::
|
|
124
|
+
|
|
125
|
+
@athena_extension(issue=74, description="Add hyperlink (closes issue #74)")
|
|
126
|
+
def add_hyperlink(self, text, address, *, fragment=None, tooltip=None):
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
For properties, decorate the underlying getter (and setter) BEFORE
|
|
130
|
+
``@property`` so the markers stick to ``prop.fget`` /
|
|
131
|
+
``prop.fset``::
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
@athena_extension(issue=471)
|
|
135
|
+
def list_id(self): ...
|
|
136
|
+
"""
|
|
137
|
+
issue_value: str | None
|
|
138
|
+
if issue is None:
|
|
139
|
+
issue_value = None
|
|
140
|
+
elif isinstance(issue, int):
|
|
141
|
+
issue_value = f"python-docx#{issue}"
|
|
142
|
+
else:
|
|
143
|
+
issue_value = str(issue)
|
|
144
|
+
|
|
145
|
+
def decorator(obj: T) -> T:
|
|
146
|
+
# ``setattr`` instead of ``obj.__athena_extension__ = …`` so the
|
|
147
|
+
# decorator works on classes, plain functions, lambdas, and any
|
|
148
|
+
# object whose namespace accepts attribute assignment. Built-in
|
|
149
|
+
# ``property`` descriptors don't (which is why callers must
|
|
150
|
+
# decorate the underlying getter, not the property itself).
|
|
151
|
+
try:
|
|
152
|
+
setattr(obj, ATHENA_EXTENSION_ATTR, True)
|
|
153
|
+
if issue_value is not None:
|
|
154
|
+
setattr(obj, ATHENA_EXTENSION_ISSUE_ATTR, issue_value)
|
|
155
|
+
if description:
|
|
156
|
+
setattr(obj, ATHENA_EXTENSION_DESCRIPTION_ATTR, description)
|
|
157
|
+
setattr(obj, ATHENA_EXTENSION_SINCE_ATTR, since)
|
|
158
|
+
except (AttributeError, TypeError):
|
|
159
|
+
# E.g. someone applied the decorator directly to a
|
|
160
|
+
# ``property`` object (which rejects setattr). Re-raise with
|
|
161
|
+
# a hint so the fix is obvious.
|
|
162
|
+
raise TypeError(
|
|
163
|
+
f"@athena_extension cannot be applied to "
|
|
164
|
+
f"{type(obj).__name__} — built-in descriptor rejects "
|
|
165
|
+
f"setattr. For properties, decorate the underlying "
|
|
166
|
+
f"getter/setter function BEFORE @property:\n\n"
|
|
167
|
+
f" @property\n"
|
|
168
|
+
f" @athena_extension(...)\n"
|
|
169
|
+
f" def my_prop(self): ...\n",
|
|
170
|
+
)
|
|
171
|
+
return obj
|
|
172
|
+
|
|
173
|
+
return decorator
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def is_athena_extension(obj: Any) -> bool:
|
|
177
|
+
"""Return ``True`` if ``obj`` is marked as an Athena extension.
|
|
178
|
+
|
|
179
|
+
Handles the property-wrapping case: a ``property`` descriptor
|
|
180
|
+
proxies to its ``fget`` (and ``fset``) — either marker counts.
|
|
181
|
+
"""
|
|
182
|
+
if getattr(obj, ATHENA_EXTENSION_ATTR, False):
|
|
183
|
+
return True
|
|
184
|
+
if isinstance(obj, property):
|
|
185
|
+
if obj.fget is not None and getattr(obj.fget, ATHENA_EXTENSION_ATTR, False):
|
|
186
|
+
return True
|
|
187
|
+
if obj.fset is not None and getattr(obj.fset, ATHENA_EXTENSION_ATTR, False):
|
|
188
|
+
return True
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def athena_extension_metadata(obj: Any) -> dict[str, Any] | None:
|
|
193
|
+
"""Return a dict of marker attributes for ``obj``, or ``None`` if
|
|
194
|
+
it isn't an Athena extension."""
|
|
195
|
+
target: Any = obj
|
|
196
|
+
if isinstance(obj, property):
|
|
197
|
+
if obj.fget is not None and getattr(obj.fget, ATHENA_EXTENSION_ATTR, False):
|
|
198
|
+
target = obj.fget
|
|
199
|
+
elif obj.fset is not None and getattr(obj.fset, ATHENA_EXTENSION_ATTR, False):
|
|
200
|
+
target = obj.fset
|
|
201
|
+
else:
|
|
202
|
+
return None
|
|
203
|
+
if not getattr(target, ATHENA_EXTENSION_ATTR, False):
|
|
204
|
+
return None
|
|
205
|
+
return {
|
|
206
|
+
"issue": getattr(target, ATHENA_EXTENSION_ISSUE_ATTR, None),
|
|
207
|
+
"description": getattr(target, ATHENA_EXTENSION_DESCRIPTION_ATTR, ""),
|
|
208
|
+
"since": getattr(target, ATHENA_EXTENSION_SINCE_ATTR, None),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Module-level helpers should NOT pull these in transitively at SDK
|
|
213
|
+
# import time — keep the walker lazy.
|
|
214
|
+
def iter_athena_extensions(package: str = "docx") -> Iterator[dict[str, Any]]:
|
|
215
|
+
"""Walk every submodule of ``package`` and yield one record per
|
|
216
|
+
Athena extension found.
|
|
217
|
+
|
|
218
|
+
Records have shape::
|
|
219
|
+
|
|
220
|
+
{
|
|
221
|
+
"kind": "module" | "class" | "function" | "method" |
|
|
222
|
+
"property" | "command",
|
|
223
|
+
"location": "docx.bookmarks.Bookmarks.add",
|
|
224
|
+
"issue": "python-docx#74",
|
|
225
|
+
"description": "Append a hyperlinked text run",
|
|
226
|
+
"since": "0.11.0",
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
The walker:
|
|
230
|
+
|
|
231
|
+
* Imports every module under ``package`` (it tolerates import
|
|
232
|
+
errors gracefully — a broken module is skipped, not fatal).
|
|
233
|
+
* Yields a ``"module"`` record for any submodule with
|
|
234
|
+
``__athena_extension_module__ = True`` at module scope.
|
|
235
|
+
* Yields one record per top-level callable / class with the
|
|
236
|
+
marker.
|
|
237
|
+
* For each marked class, walks the class body and yields a
|
|
238
|
+
``"method"`` / ``"property"`` record per marked member.
|
|
239
|
+
|
|
240
|
+
Used by ``tests/test_athena_extensions_registry.py`` to verify
|
|
241
|
+
coverage stays in sync with the surfaces documented in CLAUDE.md.
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
root = importlib.import_module(package)
|
|
245
|
+
except ImportError:
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
# The root package itself may carry the module-level marker.
|
|
249
|
+
if getattr(root, ATHENA_EXTENSION_MODULE_ATTR, False):
|
|
250
|
+
yield {
|
|
251
|
+
"kind": "module",
|
|
252
|
+
"location": package,
|
|
253
|
+
"issue": getattr(root, ATHENA_EXTENSION_ISSUE_ATTR, None),
|
|
254
|
+
"description": getattr(root, ATHENA_EXTENSION_DESCRIPTION_ATTR, ""),
|
|
255
|
+
"since": getattr(root, ATHENA_EXTENSION_SINCE_ATTR, None),
|
|
256
|
+
}
|
|
257
|
+
yield from _iter_module_members(root, package)
|
|
258
|
+
|
|
259
|
+
if not hasattr(root, "__path__"):
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
for _finder, sub_name, _ispkg in pkgutil.walk_packages(
|
|
263
|
+
root.__path__, prefix=f"{package}.",
|
|
264
|
+
):
|
|
265
|
+
try:
|
|
266
|
+
sub = importlib.import_module(sub_name)
|
|
267
|
+
except Exception: # noqa: BLE001 — broken submodule, skip.
|
|
268
|
+
continue
|
|
269
|
+
if getattr(sub, ATHENA_EXTENSION_MODULE_ATTR, False):
|
|
270
|
+
yield {
|
|
271
|
+
"kind": "module",
|
|
272
|
+
"location": sub_name,
|
|
273
|
+
"issue": getattr(sub, ATHENA_EXTENSION_ISSUE_ATTR, None),
|
|
274
|
+
"description": getattr(sub, ATHENA_EXTENSION_DESCRIPTION_ATTR, ""),
|
|
275
|
+
"since": getattr(sub, ATHENA_EXTENSION_SINCE_ATTR, None),
|
|
276
|
+
}
|
|
277
|
+
yield from _iter_module_members(sub, sub_name)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _iter_module_members(
|
|
281
|
+
module: Any, module_name: str,
|
|
282
|
+
) -> Iterator[dict[str, Any]]:
|
|
283
|
+
"""Walk ``vars(module)`` and yield extension records for top-level
|
|
284
|
+
callables / classes. For each class, recurse into its members.
|
|
285
|
+
|
|
286
|
+
Underscore-prefixed *classes* (``_Cell``, ``_Row``, ``_Column``,
|
|
287
|
+
``_NoteBase``, etc.) ARE walked — python-docx uses leading-
|
|
288
|
+
underscore class names for collection types that are part of the
|
|
289
|
+
public API. Underscore-prefixed *functions* / module attrs are
|
|
290
|
+
treated as private and skipped.
|
|
291
|
+
"""
|
|
292
|
+
seen_classes: set[int] = set()
|
|
293
|
+
for attr_name, attr in vars(module).items():
|
|
294
|
+
is_class = isinstance(attr, type)
|
|
295
|
+
if attr_name.startswith("_") and not is_class:
|
|
296
|
+
continue
|
|
297
|
+
# Top-level marked function / dataclass.
|
|
298
|
+
if (
|
|
299
|
+
not is_class
|
|
300
|
+
and getattr(attr, ATHENA_EXTENSION_ATTR, False)
|
|
301
|
+
):
|
|
302
|
+
kind = "command" if attr_name[0:1].isupper() else "function"
|
|
303
|
+
meta = athena_extension_metadata(attr) or {}
|
|
304
|
+
yield {
|
|
305
|
+
"kind": kind,
|
|
306
|
+
"location": f"{module_name}.{attr_name}",
|
|
307
|
+
**meta,
|
|
308
|
+
}
|
|
309
|
+
# Class: emit the class record (if marked) then recurse.
|
|
310
|
+
if is_class:
|
|
311
|
+
qual = getattr(attr, "__qualname__", attr_name)
|
|
312
|
+
mod_of_class = getattr(attr, "__module__", "")
|
|
313
|
+
# Skip re-exports — only emit the canonical definition's
|
|
314
|
+
# members. (E.g. ``docx.shape.Picture`` is re-exported from
|
|
315
|
+
# ``docx.shape``; emit once.)
|
|
316
|
+
if mod_of_class and mod_of_class != module_name:
|
|
317
|
+
continue
|
|
318
|
+
if id(attr) in seen_classes:
|
|
319
|
+
continue
|
|
320
|
+
seen_classes.add(id(attr))
|
|
321
|
+
if getattr(attr, ATHENA_EXTENSION_ATTR, False):
|
|
322
|
+
meta = athena_extension_metadata(attr) or {}
|
|
323
|
+
yield {
|
|
324
|
+
"kind": "class",
|
|
325
|
+
"location": f"{module_name}.{qual}",
|
|
326
|
+
**meta,
|
|
327
|
+
}
|
|
328
|
+
yield from _iter_class_members(attr, module_name, qual)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _iter_class_members(
|
|
332
|
+
cls: type, module_name: str, class_qualname: str,
|
|
333
|
+
) -> Iterator[dict[str, Any]]:
|
|
334
|
+
"""Yield records for marked methods / properties on ``cls``.
|
|
335
|
+
|
|
336
|
+
Handles three wrapped-callable cases the bare
|
|
337
|
+
``getattr(member, ATTR, False)`` check would miss:
|
|
338
|
+
|
|
339
|
+
* ``property`` — read marker from ``fget`` / ``fset``.
|
|
340
|
+
* ``classmethod`` — marker lives on ``__func__``.
|
|
341
|
+
* ``staticmethod`` — marker lives on ``__func__``.
|
|
342
|
+
"""
|
|
343
|
+
for member_name, member in vars(cls).items():
|
|
344
|
+
if member_name.startswith("_"):
|
|
345
|
+
continue
|
|
346
|
+
# Peek into classmethod / staticmethod descriptors — the
|
|
347
|
+
# decorator wraps the underlying function, so the marker
|
|
348
|
+
# attribute lives on ``descriptor.__func__``.
|
|
349
|
+
probe: Any = member
|
|
350
|
+
if isinstance(member, (classmethod, staticmethod)):
|
|
351
|
+
probe = member.__func__
|
|
352
|
+
meta = athena_extension_metadata(probe)
|
|
353
|
+
if meta is None and probe is not member:
|
|
354
|
+
# Fall back to checking the descriptor itself (covers any
|
|
355
|
+
# marker explicitly applied to the wrapper).
|
|
356
|
+
meta = athena_extension_metadata(member)
|
|
357
|
+
if meta is None:
|
|
358
|
+
continue
|
|
359
|
+
if isinstance(member, property):
|
|
360
|
+
kind = "property"
|
|
361
|
+
elif isinstance(member, classmethod):
|
|
362
|
+
kind = "classmethod"
|
|
363
|
+
elif isinstance(member, staticmethod):
|
|
364
|
+
kind = "staticmethod"
|
|
365
|
+
else:
|
|
366
|
+
kind = "method"
|
|
367
|
+
yield {
|
|
368
|
+
"kind": kind,
|
|
369
|
+
"location": f"{module_name}.{class_qualname}.{member_name}",
|
|
370
|
+
**meta,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
__all__ = [
|
|
375
|
+
"athena_extension",
|
|
376
|
+
"is_athena_extension",
|
|
377
|
+
"athena_extension_metadata",
|
|
378
|
+
"iter_athena_extensions",
|
|
379
|
+
"ATHENA_EXTENSION_ATTR",
|
|
380
|
+
"ATHENA_EXTENSION_ISSUE_ATTR",
|
|
381
|
+
"ATHENA_EXTENSION_DESCRIPTION_ATTR",
|
|
382
|
+
"ATHENA_EXTENSION_SINCE_ATTR",
|
|
383
|
+
"ATHENA_EXTENSION_MODULE_ATTR",
|
|
384
|
+
]
|