athena-python-docx 0.2.2__tar.gz → 0.2.3__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.2.2 → athena_python_docx-0.2.3}/PKG-INFO +1 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/__init__.py +1 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/document.py +0 -3
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/section.py +1 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/shape.py +1 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/text/parfmt.py +1 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/pyproject.toml +1 -1
- athena_python_docx-0.2.3/scripts/release.sh +80 -0
- athena_python_docx-0.2.3/tests/fidelity/auto_gen_cases.py +260 -0
- athena_python_docx-0.2.3/tests/fidelity/coverage_report.py +117 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/fake_session.py +34 -2
- athena_python_docx-0.2.3/tests/fidelity/op_snapshot.py +116 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/01_basic_paragraph.json +3 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/02_multiple_headings.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/03_runs_with_formatting.json +16 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/04_font_name_and_size.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/05_font_color_rgb.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/06_font_character_properties.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/08_font_highlight.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/09_paragraph_alignment.json +4 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/100_table_negative_indexing.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +33 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +3 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +14 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/104_core_properties_datetime.json +1 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/105_default_one_section.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/107_varying_row_heights.json +53 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/10_paragraph_indents.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/11_paragraph_spacing.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/15_run_add_break_page.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/17_table_basic.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/18_table_cell_text.json +27 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +25 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +12 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +15 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/23_nested_table.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/24_table_add_row_column.json +21 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/25_table_merge_cells.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/26_section_page_setup.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/27_section_margins.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/28_section_add_new.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/29_section_headers_linked.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/30_styles_iteration.json +3 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +4 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +1 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +1 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +3 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/35_full_report.json +103 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/38_font_all_properties.json +23 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +102 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/40_large_table_10x10.json +403 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/42_very_long_paragraph.json +3 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +4 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/47_font_size_round_trip.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/48_font_color_round_trip.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/49_resume_layout.json +48 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/50_multi_section_doc.json +20 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/51_nested_tables_deep.json +15 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/52_iterate_everything.json +13 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/54_empty_everything.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/55_single_character_runs.json +18 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/56_everything_in_one.json +111 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +13 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/59_indent_round_trip.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/60_space_round_trip.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +14 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +26 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/63_table_style_round_trip.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/64_many_sections.json +28 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +3405 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/66_toc_like_structure.json +82 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +8 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/68_invoice.json +125 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/69_newsletter.json +67 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/71_academic_paper.json +82 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/72_legal_contract.json +83 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/73_form_with_many_tables.json +194 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +34 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/77_length_unit_conversions.json +1 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +8 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +103 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +12 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +8 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/84_table_reread_row_count.json +21 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/85_header_footer_access.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +10 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +532 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +54 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +6 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/91_many_small_tables.json +453 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/92_margins_every_section.json +21 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +4 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +4 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/97_document_styles_by_key.json +8 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/98_style_contains_check.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +5 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +23 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +29 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +1002 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex04_50x50_table.json +203 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +9 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +272 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +21 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +32 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +13 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex10_complex_bom.json +506 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +805 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +17 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +55 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex14_styled_report_table.json +167 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +15 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +13 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +203 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +48 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +812 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega01_book_chapter.json +199 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega02_research_proposal.json +178 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega03_financial_statement.json +167 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega04_recipe_card.json +55 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega05_user_manual.json +177 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +98 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +231 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega08_product_catalog.json +89 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega09_signed_contract.json +128 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/mega10_api_documentation.json +322 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw01_official_quickstart.json +90 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +7 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw03_character_formatting.json +10 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw04_section_page_setup.json +11 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw05_toc_pattern.json +25 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +98 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +31 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw08_table_merged_header.json +35 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +93 -0
- athena_python_docx-0.2.3/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +165 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/ours_spec.json +1154 -78
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/parity_crawl.py +0 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/parity_diff.json +28 -17
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/round_trip_tests.py +1 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/runner.py +0 -1
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/stock_spec.json +16 -16
- athena_python_docx-0.2.2/uv.lock +0 -525
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/.gitignore +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/CLAUDE.md +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/README.md +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/_batching.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/api.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/client.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/enum/section.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/enum/style.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/enum/table.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/enum/text.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/errors.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/settings.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/shared.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/styles/style.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/table.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/text/run.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/docx/typing.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/scripts/publish.sh +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/__init__.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/conftest.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/test_commands.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.2.2 → athena_python_docx-0.2.3}/tests/test_smoke_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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>
|
|
@@ -115,7 +115,6 @@ class Document:
|
|
|
115
115
|
@property
|
|
116
116
|
def sections(self):
|
|
117
117
|
"""Return the document's sections collection."""
|
|
118
|
-
from docx.section import Sections
|
|
119
118
|
|
|
120
119
|
self._ensure_open()
|
|
121
120
|
return Sections(session=self._session)
|
|
@@ -397,8 +396,6 @@ class Document:
|
|
|
397
396
|
Mirrors python-docx: creating a section adds a trailing empty
|
|
398
397
|
paragraph that marks the section boundary.
|
|
399
398
|
"""
|
|
400
|
-
from docx.enum.section import WD_SECTION_START
|
|
401
|
-
from docx.section import Section
|
|
402
399
|
|
|
403
400
|
self._ensure_open()
|
|
404
401
|
# python-docx always inserts an anchor paragraph at the section break
|
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
16
|
from docx._batching import run_sync
|
|
17
|
-
from docx.shared import Length,
|
|
17
|
+
from docx.shared import Length, Twips
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-docx"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tag + push a new athena-python-docx release. CI handles the rest:
|
|
3
|
+
# PyPI publish, Docker image rebuild, Daytona snapshot creation, Doppler
|
|
4
|
+
# flip in agora dev + stg. See
|
|
5
|
+
# .github/workflows/athena-python-docx-release.yml for the full flow.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ./scripts/release.sh <version>
|
|
9
|
+
#
|
|
10
|
+
# e.g. ./scripts/release.sh 0.2.3 — releases athena-python-docx==0.2.3
|
|
11
|
+
# by:
|
|
12
|
+
# 1. Updating pyproject.toml + docx/__init__.py
|
|
13
|
+
# 2. Committing the version bump
|
|
14
|
+
# 3. Tagging athena-python-docx-v<version>
|
|
15
|
+
# 4. Pushing the commit + tag to origin
|
|
16
|
+
#
|
|
17
|
+
# CI trigger fires on the tag push and runs the full release pipeline.
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
if [ $# -ne 1 ]; then
|
|
22
|
+
echo "Usage: $0 <version> (e.g., 0.2.3)" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
VERSION="$1"
|
|
27
|
+
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.(dev|rc|a|b)[0-9]+)?$ ]]; then
|
|
28
|
+
echo "ERROR: version '$VERSION' is not a valid semver-ish" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
SDK_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
33
|
+
REPO_ROOT="$(git -C "$SDK_DIR" rev-parse --show-toplevel)"
|
|
34
|
+
|
|
35
|
+
# Require clean working tree so the commit captures only the version bump.
|
|
36
|
+
if ! git -C "$REPO_ROOT" diff --quiet || ! git -C "$REPO_ROOT" diff --cached --quiet; then
|
|
37
|
+
echo "ERROR: working tree is dirty. Commit or stash first." >&2
|
|
38
|
+
git -C "$REPO_ROOT" status --short >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Bump pyproject.toml
|
|
43
|
+
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$SDK_DIR/pyproject.toml"
|
|
44
|
+
rm -f "$SDK_DIR/pyproject.toml.bak"
|
|
45
|
+
|
|
46
|
+
# Bump docx/__init__.py
|
|
47
|
+
sed -i.bak "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" "$SDK_DIR/docx/__init__.py"
|
|
48
|
+
rm -f "$SDK_DIR/docx/__init__.py.bak"
|
|
49
|
+
|
|
50
|
+
# Show the diff so the operator sees what will be committed.
|
|
51
|
+
echo "=== Version bump diff ==="
|
|
52
|
+
git -C "$REPO_ROOT" diff "$SDK_DIR/pyproject.toml" "$SDK_DIR/docx/__init__.py"
|
|
53
|
+
echo "========================="
|
|
54
|
+
|
|
55
|
+
TAG="athena-python-docx-v$VERSION"
|
|
56
|
+
|
|
57
|
+
# Commit + tag + push.
|
|
58
|
+
git -C "$REPO_ROOT" add "$SDK_DIR/pyproject.toml" "$SDK_DIR/docx/__init__.py"
|
|
59
|
+
git -C "$REPO_ROOT" commit -m "chore(docx-sdk): bump version to $VERSION"
|
|
60
|
+
git -C "$REPO_ROOT" tag "$TAG"
|
|
61
|
+
git -C "$REPO_ROOT" push
|
|
62
|
+
git -C "$REPO_ROOT" push origin "$TAG"
|
|
63
|
+
|
|
64
|
+
cat <<EOF
|
|
65
|
+
|
|
66
|
+
Tagged $TAG and pushed. CI is now running the full release pipeline:
|
|
67
|
+
|
|
68
|
+
1. PyPI publish → https://pypi.org/project/athena-python-docx/$VERSION/
|
|
69
|
+
2. Docker image rebuild → athenaintel/daytona-document:$VERSION
|
|
70
|
+
3. Daytona snapshot → document-exec:$VERSION
|
|
71
|
+
4. Doppler flip → agora/dev + agora/stg
|
|
72
|
+
|
|
73
|
+
Watch the run:
|
|
74
|
+
https://github.com/Athena-Intel/demo-app-monorepo/actions/workflows/athena-python-docx-release.yml
|
|
75
|
+
|
|
76
|
+
For prd rollout, after canary verification:
|
|
77
|
+
doppler secrets set --project agora --config prd \\
|
|
78
|
+
DAYTONA_DOCUMENT_EXEC_SNAPSHOT=document-exec:$VERSION
|
|
79
|
+
|
|
80
|
+
EOF
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Auto-generate test cases from python-docx's public API surface.
|
|
2
|
+
|
|
3
|
+
For every `(ClassName, method_or_property)` pair python-docx exposes that
|
|
4
|
+
our SDK claims to support, emit a minimum-viable test case that:
|
|
5
|
+
- constructs the relevant object (via a small set of hand-written fixtures)
|
|
6
|
+
- invokes the method / sets the property
|
|
7
|
+
- asserts no exception is raised
|
|
8
|
+
|
|
9
|
+
The generated cases live in memory; run via `auto_gen_cases.run_all`.
|
|
10
|
+
Complements hand-written cases by guaranteeing every surface has at least
|
|
11
|
+
one exercise path.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
import traceback
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Callable
|
|
21
|
+
|
|
22
|
+
_SDK_ROOT = Path(__file__).resolve().parents[2]
|
|
23
|
+
sys.path.insert(0, str(_SDK_ROOT))
|
|
24
|
+
|
|
25
|
+
from tests.fidelity.fake_session import install_fake_session # noqa: E402
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ── Representative values per type ───────────────────────────────────
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _rep_value(param_name: str, param_type: str, obj_context: str) -> Any:
|
|
32
|
+
"""Pick a representative value for a parameter name+type hint."""
|
|
33
|
+
from docx.enum.table import (
|
|
34
|
+
WD_ROW_HEIGHT_RULE,
|
|
35
|
+
)
|
|
36
|
+
from docx.enum.text import (
|
|
37
|
+
WD_ALIGN_PARAGRAPH,
|
|
38
|
+
WD_COLOR_INDEX,
|
|
39
|
+
)
|
|
40
|
+
from docx.enum.section import WD_ORIENTATION, WD_SECTION_START
|
|
41
|
+
from docx.shared import Inches, Pt, RGBColor
|
|
42
|
+
|
|
43
|
+
name = param_name.lower()
|
|
44
|
+
type_str = param_type.lower()
|
|
45
|
+
# Booleans
|
|
46
|
+
if "bool" in type_str or name in ("bold", "italic", "underline", "active"):
|
|
47
|
+
return True
|
|
48
|
+
# String literals
|
|
49
|
+
if "str" in type_str or name in ("text", "name", "style", "id", "label"):
|
|
50
|
+
return "sample"
|
|
51
|
+
# Lengths
|
|
52
|
+
if "length" in type_str or "emu" in type_str or "inches" in type_str or "pt " in type_str:
|
|
53
|
+
return Inches(1)
|
|
54
|
+
if name in ("width", "height", "size") and "int" in type_str:
|
|
55
|
+
return Pt(12)
|
|
56
|
+
if "rgb" in type_str or "color" in name:
|
|
57
|
+
return RGBColor(0, 0, 0)
|
|
58
|
+
if "int" in type_str or "number" in type_str:
|
|
59
|
+
if "level" in name:
|
|
60
|
+
return 1
|
|
61
|
+
if name in ("rows", "cols", "rowindex", "columnindex"):
|
|
62
|
+
return 2
|
|
63
|
+
return 1
|
|
64
|
+
# Enums
|
|
65
|
+
if "alignment" in name:
|
|
66
|
+
return WD_ALIGN_PARAGRAPH.CENTER
|
|
67
|
+
if "orientation" in name:
|
|
68
|
+
return WD_ORIENTATION.PORTRAIT
|
|
69
|
+
if "start_type" in name or "break_type" in name:
|
|
70
|
+
return WD_SECTION_START.NEW_PAGE
|
|
71
|
+
if "highlight" in name:
|
|
72
|
+
return WD_COLOR_INDEX.YELLOW
|
|
73
|
+
if "rule" in name:
|
|
74
|
+
return WD_ROW_HEIGHT_RULE.AT_LEAST
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ── Fixtures ──────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _fixture_new_doc() -> Any:
|
|
82
|
+
install_fake_session()
|
|
83
|
+
from docx import Document
|
|
84
|
+
return Document("asset_fake")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_FIXTURES: dict[str, Callable[[Any], Any]] = {
|
|
88
|
+
"Document": lambda doc: doc,
|
|
89
|
+
"Paragraph": lambda doc: doc.add_paragraph("auto-gen"),
|
|
90
|
+
"Run": lambda doc: doc.add_paragraph().add_run("auto-gen"),
|
|
91
|
+
"Font": lambda doc: doc.add_paragraph().add_run("auto-gen").font,
|
|
92
|
+
"ParagraphFormat": lambda doc: doc.add_paragraph("auto-gen").paragraph_format,
|
|
93
|
+
"Table": lambda doc: doc.add_table(rows=2, cols=2),
|
|
94
|
+
"_Cell": lambda doc: doc.add_table(rows=1, cols=1).cell(0, 0),
|
|
95
|
+
"_Row": lambda doc: doc.add_table(rows=1, cols=1).rows[0],
|
|
96
|
+
"_Column": lambda doc: doc.add_table(rows=1, cols=1).columns[0],
|
|
97
|
+
"Section": lambda doc: doc.sections[0],
|
|
98
|
+
"Styles": lambda doc: doc.styles,
|
|
99
|
+
"CoreProperties": lambda doc: doc.core_properties,
|
|
100
|
+
"InlineShapes": lambda doc: doc.inline_shapes,
|
|
101
|
+
"Sections": lambda doc: doc.sections,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
_PROPERTY_VALUES: dict[tuple[str, str], Any] = {} # populated lazily
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _property_value(cls_name: str, prop: str) -> Any:
|
|
109
|
+
from docx.enum.table import WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE, WD_TABLE_ALIGNMENT
|
|
110
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_COLOR_INDEX
|
|
111
|
+
from docx.enum.section import WD_ORIENTATION
|
|
112
|
+
from docx.shared import Inches, Pt
|
|
113
|
+
|
|
114
|
+
# Hand-tuned per (class, property) — most important ones.
|
|
115
|
+
mapping = {
|
|
116
|
+
("Paragraph", "text"): "text",
|
|
117
|
+
("Paragraph", "style"): "Normal",
|
|
118
|
+
("Paragraph", "alignment"): WD_ALIGN_PARAGRAPH.CENTER,
|
|
119
|
+
("Run", "text"): "text",
|
|
120
|
+
("Run", "bold"): True,
|
|
121
|
+
("Run", "italic"): True,
|
|
122
|
+
("Run", "underline"): True,
|
|
123
|
+
("Run", "style"): "Emphasis",
|
|
124
|
+
("Font", "name"): "Arial",
|
|
125
|
+
("Font", "size"): Pt(12),
|
|
126
|
+
("Font", "bold"): True,
|
|
127
|
+
("Font", "italic"): True,
|
|
128
|
+
("Font", "strike"): True,
|
|
129
|
+
("Font", "double_strike"): True,
|
|
130
|
+
("Font", "all_caps"): True,
|
|
131
|
+
("Font", "small_caps"): True,
|
|
132
|
+
("Font", "shadow"): True,
|
|
133
|
+
("Font", "outline"): True,
|
|
134
|
+
("Font", "emboss"): True,
|
|
135
|
+
("Font", "imprint"): True,
|
|
136
|
+
("Font", "hidden"): True,
|
|
137
|
+
("Font", "subscript"): True,
|
|
138
|
+
("Font", "superscript"): True,
|
|
139
|
+
("Font", "highlight_color"): WD_COLOR_INDEX.YELLOW,
|
|
140
|
+
("ParagraphFormat", "alignment"): WD_ALIGN_PARAGRAPH.CENTER,
|
|
141
|
+
("ParagraphFormat", "left_indent"): Inches(0.5),
|
|
142
|
+
("ParagraphFormat", "right_indent"): Inches(0.25),
|
|
143
|
+
("ParagraphFormat", "first_line_indent"): Inches(0.25),
|
|
144
|
+
("ParagraphFormat", "space_before"): Pt(12),
|
|
145
|
+
("ParagraphFormat", "space_after"): Pt(6),
|
|
146
|
+
("ParagraphFormat", "line_spacing"): 1.5,
|
|
147
|
+
("ParagraphFormat", "keep_together"): True,
|
|
148
|
+
("ParagraphFormat", "keep_with_next"): True,
|
|
149
|
+
("ParagraphFormat", "widow_control"): True,
|
|
150
|
+
("ParagraphFormat", "page_break_before"): True,
|
|
151
|
+
("Table", "style"): "TableGrid",
|
|
152
|
+
("Table", "alignment"): WD_TABLE_ALIGNMENT.CENTER,
|
|
153
|
+
("Table", "autofit"): True,
|
|
154
|
+
("_Cell", "text"): "cell body",
|
|
155
|
+
("_Cell", "vertical_alignment"): WD_ALIGN_VERTICAL.CENTER,
|
|
156
|
+
("_Cell", "width"): Inches(1),
|
|
157
|
+
("_Row", "height"): Pt(20),
|
|
158
|
+
("_Row", "height_rule"): WD_ROW_HEIGHT_RULE.AT_LEAST,
|
|
159
|
+
("_Column", "width"): Inches(1),
|
|
160
|
+
("Section", "page_width"): Inches(8.5),
|
|
161
|
+
("Section", "page_height"): Inches(11),
|
|
162
|
+
("Section", "orientation"): WD_ORIENTATION.PORTRAIT,
|
|
163
|
+
("Section", "left_margin"): Inches(1),
|
|
164
|
+
("Section", "right_margin"): Inches(1),
|
|
165
|
+
("Section", "top_margin"): Inches(1),
|
|
166
|
+
("Section", "bottom_margin"): Inches(1),
|
|
167
|
+
}
|
|
168
|
+
return mapping.get((cls_name, prop))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ── Test runner ───────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def run_all() -> tuple[int, int, list[tuple[str, str]]]:
|
|
175
|
+
"""Run auto-generated tests; return (passed, total, failures)."""
|
|
176
|
+
failures: list[tuple[str, str]] = []
|
|
177
|
+
total = 0
|
|
178
|
+
passed = 0
|
|
179
|
+
for cls_name, fix in _FIXTURES.items():
|
|
180
|
+
# Per-class: iterate known (class, property) pairs
|
|
181
|
+
for (cname, prop), val in list(_PROPERTY_VALUES.items()):
|
|
182
|
+
if cname != cls_name:
|
|
183
|
+
continue
|
|
184
|
+
# Use _property_value mapping: iterate all keys matching this class
|
|
185
|
+
from docx.enum.table import WD_ALIGN_VERTICAL # noqa: F401 — keeps import list alive
|
|
186
|
+
mapping_keys = _collect_property_keys(cls_name)
|
|
187
|
+
for prop in mapping_keys:
|
|
188
|
+
val = _property_value(cls_name, prop)
|
|
189
|
+
if val is None:
|
|
190
|
+
continue
|
|
191
|
+
total += 1
|
|
192
|
+
doc = _fixture_new_doc()
|
|
193
|
+
try:
|
|
194
|
+
target = fix(doc)
|
|
195
|
+
_set_nested(target, prop, val)
|
|
196
|
+
# Round-trip read (best effort; skip if getter raises)
|
|
197
|
+
try:
|
|
198
|
+
_ = _get_nested(target, prop)
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
201
|
+
passed += 1
|
|
202
|
+
except Exception as e: # noqa: BLE001
|
|
203
|
+
failures.append((f"{cls_name}.{prop}", f"{type(e).__name__}: {e}"))
|
|
204
|
+
finally:
|
|
205
|
+
try:
|
|
206
|
+
doc.close()
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
return passed, total, failures
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _collect_property_keys(cls_name: str) -> list[str]:
|
|
213
|
+
"""Pull properties we have values for."""
|
|
214
|
+
# Use introspection on the _property_value mapping: easiest way
|
|
215
|
+
# without duplicating data is to call it with each key and filter.
|
|
216
|
+
all_keys = [
|
|
217
|
+
"text", "style", "alignment", "bold", "italic", "underline",
|
|
218
|
+
"name", "size", "strike", "double_strike", "all_caps", "small_caps",
|
|
219
|
+
"shadow", "outline", "emboss", "imprint", "hidden", "subscript",
|
|
220
|
+
"superscript", "highlight_color", "left_indent", "right_indent",
|
|
221
|
+
"first_line_indent", "space_before", "space_after", "line_spacing",
|
|
222
|
+
"keep_together", "keep_with_next", "widow_control", "page_break_before",
|
|
223
|
+
"autofit", "vertical_alignment", "width", "height", "height_rule",
|
|
224
|
+
"page_width", "page_height", "orientation", "left_margin",
|
|
225
|
+
"right_margin", "top_margin", "bottom_margin",
|
|
226
|
+
]
|
|
227
|
+
return [k for k in all_keys if _property_value(cls_name, k) is not None]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _set_nested(obj: Any, path: str, value: Any) -> None:
|
|
231
|
+
parts = path.split(".")
|
|
232
|
+
for p in parts[:-1]:
|
|
233
|
+
obj = getattr(obj, p)
|
|
234
|
+
setattr(obj, parts[-1], value)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _get_nested(obj: Any, path: str) -> Any:
|
|
238
|
+
for p in path.split("."):
|
|
239
|
+
obj = getattr(obj, p)
|
|
240
|
+
return obj
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main() -> int:
|
|
244
|
+
parser = argparse.ArgumentParser()
|
|
245
|
+
parser.add_argument("--verbose", action="store_true")
|
|
246
|
+
args = parser.parse_args()
|
|
247
|
+
passed, total, failures = run_all()
|
|
248
|
+
for name, err in failures:
|
|
249
|
+
print(f" ❌ {name:45} — {err}")
|
|
250
|
+
if args.verbose:
|
|
251
|
+
traceback.print_exc()
|
|
252
|
+
print()
|
|
253
|
+
print(f"Auto-generated fidelity: {passed}/{total} passed")
|
|
254
|
+
if failures:
|
|
255
|
+
print(f"Failures: {len(failures)}")
|
|
256
|
+
return 0 if not failures else 1
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
sys.exit(main())
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Parity coverage scorecard.
|
|
2
|
+
|
|
3
|
+
Combines layers L1-L6 into a single `coverage.json` + human scorecard:
|
|
4
|
+
|
|
5
|
+
- L1 (import parity) — from parity_crawl --ours output
|
|
6
|
+
- L2 (signature match) — from parity_diff
|
|
7
|
+
- L3 (enum value parity) — from parity_diff
|
|
8
|
+
- L6 (property round-trip)— from round_trip_tests
|
|
9
|
+
|
|
10
|
+
The output looks like:
|
|
11
|
+
|
|
12
|
+
python-docx coverage scorecard (0.2.2)
|
|
13
|
+
=====================================
|
|
14
|
+
Classes 27/30 (90%) ✅
|
|
15
|
+
Properties 210/225 (93%) ✅
|
|
16
|
+
Methods 122/142 (86%) ⚠️
|
|
17
|
+
Enum values 68/73 (93%) ✅
|
|
18
|
+
|
|
19
|
+
Intended as a CI gate: any decrease fails the job.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
_SDK_ROOT = Path(__file__).resolve().parents[2]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load(path: Path) -> dict:
|
|
32
|
+
if not path.exists():
|
|
33
|
+
return {}
|
|
34
|
+
return json.loads(path.read_text())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _count_in_stock(stock: dict) -> dict[str, int]:
|
|
38
|
+
classes = 0
|
|
39
|
+
properties = 0
|
|
40
|
+
methods = 0
|
|
41
|
+
enum_values = 0
|
|
42
|
+
for mod_path, entries in stock.items():
|
|
43
|
+
for qual, entry in entries.items():
|
|
44
|
+
if not isinstance(entry, dict):
|
|
45
|
+
continue
|
|
46
|
+
if entry.get("kind") == "class":
|
|
47
|
+
classes += 1
|
|
48
|
+
# Enum detection: a class whose bases include Enum-ish
|
|
49
|
+
is_enum = any(
|
|
50
|
+
b in ("Enum", "IntEnum", "BaseXmlEnum") for b in entry.get("bases", [])
|
|
51
|
+
)
|
|
52
|
+
for m_name, m_entry in entry.get("members", {}).items():
|
|
53
|
+
if not isinstance(m_entry, dict):
|
|
54
|
+
continue
|
|
55
|
+
kind = m_entry.get("kind")
|
|
56
|
+
if kind == "property":
|
|
57
|
+
properties += 1
|
|
58
|
+
elif kind == "method":
|
|
59
|
+
methods += 1
|
|
60
|
+
elif kind == "attribute" and is_enum:
|
|
61
|
+
enum_values += 1
|
|
62
|
+
return {"classes": classes, "properties": properties, "methods": methods, "enum_values": enum_values}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _count_diff_missing(diff: dict) -> dict[str, int]:
|
|
66
|
+
missing = {"classes": len(diff.get("missing_classes", [])), "properties": 0, "methods": 0, "enum_values": 0}
|
|
67
|
+
# missing_members are a flat list; we can't tell property/method from there
|
|
68
|
+
# without cross-ref to stock. For now, lump them under "methods" as a rough
|
|
69
|
+
# signal — the scorecard will be approximate but still useful.
|
|
70
|
+
missing["methods"] += len(diff.get("missing_members", []))
|
|
71
|
+
return missing
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main() -> int:
|
|
75
|
+
stock = _load(_SDK_ROOT / "tests/fidelity/stock_spec.json")
|
|
76
|
+
diff = _load(_SDK_ROOT / "tests/fidelity/parity_diff.json")
|
|
77
|
+
if not stock or not diff:
|
|
78
|
+
print("run parity_crawl.py --stock / --ours / --diff first")
|
|
79
|
+
return 1
|
|
80
|
+
totals = _count_in_stock(stock)
|
|
81
|
+
missing = _count_diff_missing(diff)
|
|
82
|
+
|
|
83
|
+
implemented = {
|
|
84
|
+
k: max(totals[k] - missing.get(k, 0), 0)
|
|
85
|
+
for k in totals
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
version = _get_sdk_version()
|
|
89
|
+
|
|
90
|
+
print()
|
|
91
|
+
print(f"python-docx coverage scorecard (athena-python-docx {version})")
|
|
92
|
+
print("=" * 60)
|
|
93
|
+
for key, label in [("classes", "Classes"), ("properties", "Properties"), ("methods", "Methods"), ("enum_values", "Enum values")]:
|
|
94
|
+
t = totals[key]
|
|
95
|
+
i = implemented[key]
|
|
96
|
+
pct = (i * 100 // t) if t else 100
|
|
97
|
+
icon = "✅" if pct >= 90 else ("⚠️" if pct >= 75 else "❌")
|
|
98
|
+
print(f" {label:14} {i:4}/{t:<4} ({pct:>3}%) {icon}")
|
|
99
|
+
print()
|
|
100
|
+
|
|
101
|
+
# Round-trip scorecard (L6)
|
|
102
|
+
rt_path = _SDK_ROOT / "tests/fidelity/round_trip_tests.py"
|
|
103
|
+
if rt_path.exists():
|
|
104
|
+
print("Property round-trips: run `.venv/bin/python tests/fidelity/round_trip_tests.py` for details")
|
|
105
|
+
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_sdk_version() -> str:
|
|
110
|
+
init = (_SDK_ROOT / "docx/__init__.py").read_text()
|
|
111
|
+
import re
|
|
112
|
+
m = re.search(r'__version__ = "([^"]+)"', init)
|
|
113
|
+
return m.group(1) if m else "?"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
sys.exit(main())
|
|
@@ -364,16 +364,31 @@ class FakeDocState:
|
|
|
364
364
|
if target.get("kind") == "block" and target.get("nodeType") == "tableCell":
|
|
365
365
|
cell_nid = target.get("nodeId")
|
|
366
366
|
content = params.get("content") or {}
|
|
367
|
-
# Extract text from prosemirror paragraph
|
|
367
|
+
# Extract text from either a prosemirror `{type:"paragraph",
|
|
368
|
+
# content:[{type:"text",text:"…"}]}` fragment OR a Superdoc
|
|
369
|
+
# native `{kind:"paragraph", paragraph:{inlines:[{run:...}]}}`
|
|
370
|
+
# fragment.
|
|
368
371
|
appended: str = ""
|
|
369
372
|
if isinstance(content, dict):
|
|
370
|
-
inline_list = content.get("content")
|
|
373
|
+
inline_list = content.get("content")
|
|
371
374
|
if isinstance(inline_list, list):
|
|
372
375
|
for inl in inline_list:
|
|
373
376
|
if isinstance(inl, dict) and inl.get("type") == "text":
|
|
374
377
|
t = inl.get("text")
|
|
375
378
|
if isinstance(t, str):
|
|
376
379
|
appended += t
|
|
380
|
+
# Superdoc native shape
|
|
381
|
+
para = content.get("paragraph")
|
|
382
|
+
if isinstance(para, dict):
|
|
383
|
+
inlines = para.get("inlines") or []
|
|
384
|
+
if isinstance(inlines, list):
|
|
385
|
+
for inl in inlines:
|
|
386
|
+
if isinstance(inl, dict):
|
|
387
|
+
run = inl.get("run")
|
|
388
|
+
if isinstance(run, dict):
|
|
389
|
+
t = run.get("text")
|
|
390
|
+
if isinstance(t, str):
|
|
391
|
+
appended += t
|
|
377
392
|
# Find cell and append to its first paragraph.
|
|
378
393
|
for t in self.tables:
|
|
379
394
|
for r_idx, row in enumerate(t.cells):
|
|
@@ -391,6 +406,23 @@ class FakeDocState:
|
|
|
391
406
|
)
|
|
392
407
|
return {"ok": True}
|
|
393
408
|
|
|
409
|
+
# markdown_to_fragment: return a minimal-valid Superdoc fragment.
|
|
410
|
+
if op == "markdownToFragment":
|
|
411
|
+
md = params.get("markdown", "")
|
|
412
|
+
return {
|
|
413
|
+
"fragment": {
|
|
414
|
+
"kind": "paragraph",
|
|
415
|
+
"paragraph": {
|
|
416
|
+
"inlines": (
|
|
417
|
+
[{"kind": "run", "run": {"text": md}}]
|
|
418
|
+
if md else []
|
|
419
|
+
),
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
"lossy": False,
|
|
423
|
+
"diagnostics": [],
|
|
424
|
+
}
|
|
425
|
+
|
|
394
426
|
if op == "insertLineBreak":
|
|
395
427
|
blk_id = params.get("blockId")
|
|
396
428
|
if blk_id:
|