athena-python-docx 0.2.1__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.1 → athena_python_docx-0.2.3}/PKG-INFO +1 -1
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/__init__.py +11 -1
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/document.py +31 -9
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/text.py +18 -1
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/section.py +1 -1
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/shape.py +1 -1
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/table.py +76 -98
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/paragraph.py +23 -14
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/parfmt.py +1 -1
- {athena_python_docx-0.2.1 → 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/METHODOLOGY.md +107 -0
- athena_python_docx-0.2.3/tests/fidelity/auto_gen_cases.py +260 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/cases.py +2 -3
- athena_python_docx-0.2.3/tests/fidelity/coverage_report.py +117 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/fake_session.py +65 -1
- 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.3/tests/fidelity/ours_spec.json +4495 -0
- athena_python_docx-0.2.3/tests/fidelity/parity_crawl.py +266 -0
- athena_python_docx-0.2.3/tests/fidelity/parity_diff.json +517 -0
- athena_python_docx-0.2.3/tests/fidelity/round_trip_tests.py +214 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/runner.py +0 -1
- athena_python_docx-0.2.3/tests/fidelity/stock_spec.json +10189 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/.gitignore +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/CLAUDE.md +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/README.md +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/_batching.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/api.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/client.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/section.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/style.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/enum/table.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/errors.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/settings.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/shared.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/styles/style.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/text/run.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/docx/typing.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/scripts/publish.sh +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/__init__.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/conftest.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/test_commands.py +0 -0
- {athena_python_docx-0.2.1 → athena_python_docx-0.2.3}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.2.1 → 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>
|
|
@@ -6,11 +6,21 @@ See CLAUDE.md for the API parity contract.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
__version__ = "0.2.
|
|
9
|
+
__version__ = "0.2.3"
|
|
10
10
|
|
|
11
11
|
from docx.api import Document
|
|
12
|
+
# Re-exports python-docx ships at docx top-level for convenience.
|
|
13
|
+
from docx.shared import Emu, Inches, Pt, Cm, Mm, Twips, Length, RGBColor
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
16
|
"Document",
|
|
17
|
+
"Emu",
|
|
18
|
+
"Inches",
|
|
19
|
+
"Pt",
|
|
20
|
+
"Cm",
|
|
21
|
+
"Mm",
|
|
22
|
+
"Twips",
|
|
23
|
+
"Length",
|
|
24
|
+
"RGBColor",
|
|
15
25
|
"__version__",
|
|
16
26
|
]
|
|
@@ -22,9 +22,15 @@ from typing import TYPE_CHECKING, BinaryIO
|
|
|
22
22
|
from docx._batching import run_sync
|
|
23
23
|
from docx.client import Session
|
|
24
24
|
from docx.errors import DocumentClosedError, ValidationError
|
|
25
|
+
# python-docx re-exports a subset of symbols at docx.document; mirror those
|
|
26
|
+
# so `from docx.document import Emu` etc. works.
|
|
27
|
+
from docx.enum.section import WD_SECTION, WD_SECTION_START # noqa: F401
|
|
28
|
+
from docx.enum.text import WD_BREAK # noqa: F401
|
|
29
|
+
from docx.section import Section, Sections # noqa: F401
|
|
30
|
+
from docx.shared import Cm, Emu, Inches, Length, Mm, Pt, RGBColor, Twips # noqa: F401
|
|
31
|
+
from docx.text.run import Run # noqa: F401
|
|
25
32
|
|
|
26
33
|
if TYPE_CHECKING:
|
|
27
|
-
from docx.shared import Emu
|
|
28
34
|
from docx.table import Table
|
|
29
35
|
from docx.text.paragraph import Paragraph
|
|
30
36
|
|
|
@@ -80,7 +86,15 @@ class Document:
|
|
|
80
86
|
continue
|
|
81
87
|
node_id: str = str(b.get("nodeId", ""))
|
|
82
88
|
if node_id:
|
|
83
|
-
|
|
89
|
+
nt_raw = b.get("nodeType")
|
|
90
|
+
nt: str = nt_raw if isinstance(nt_raw, str) and nt_raw else "paragraph"
|
|
91
|
+
out.append(
|
|
92
|
+
Paragraph(
|
|
93
|
+
session=self._session,
|
|
94
|
+
node_id=node_id,
|
|
95
|
+
node_type=nt,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
84
98
|
return out
|
|
85
99
|
|
|
86
100
|
@property
|
|
@@ -101,7 +115,6 @@ class Document:
|
|
|
101
115
|
@property
|
|
102
116
|
def sections(self):
|
|
103
117
|
"""Return the document's sections collection."""
|
|
104
|
-
from docx.section import Sections
|
|
105
118
|
|
|
106
119
|
self._ensure_open()
|
|
107
120
|
return Sections(session=self._session)
|
|
@@ -204,7 +217,9 @@ class Document:
|
|
|
204
217
|
raise RuntimeError(
|
|
205
218
|
f"Superdoc did not return a nodeId for add_paragraph: {result!r}",
|
|
206
219
|
)
|
|
207
|
-
return Paragraph(
|
|
220
|
+
return Paragraph(
|
|
221
|
+
session=self._session, node_id=node_id, node_type="paragraph",
|
|
222
|
+
)
|
|
208
223
|
|
|
209
224
|
def add_heading(
|
|
210
225
|
self,
|
|
@@ -239,7 +254,11 @@ class Document:
|
|
|
239
254
|
raise RuntimeError(
|
|
240
255
|
f"Superdoc did not return a nodeId for add_heading(level=0): {result!r}",
|
|
241
256
|
)
|
|
242
|
-
paragraph = Paragraph(
|
|
257
|
+
paragraph = Paragraph(
|
|
258
|
+
session=self._session,
|
|
259
|
+
node_id=node_id,
|
|
260
|
+
node_type="paragraph",
|
|
261
|
+
)
|
|
243
262
|
paragraph.style = "Title"
|
|
244
263
|
return paragraph
|
|
245
264
|
|
|
@@ -251,12 +270,17 @@ class Document:
|
|
|
251
270
|
result = run_sync(
|
|
252
271
|
self._session.doc.create.heading(params),
|
|
253
272
|
)
|
|
254
|
-
|
|
273
|
+
# Bug fix: was passing expected_type="paragraph" here (wrong); the
|
|
274
|
+
# fallback loop recovered but the code of intent was wrong. Fixed to
|
|
275
|
+
# expected_type="heading" so we extract from the correct response key.
|
|
276
|
+
node_id = _extract_inserted_node_id(result, expected_type="heading")
|
|
255
277
|
if not node_id:
|
|
256
278
|
raise RuntimeError(
|
|
257
279
|
f"Superdoc did not return a nodeId for add_heading: {result!r}",
|
|
258
280
|
)
|
|
259
|
-
return Paragraph(
|
|
281
|
+
return Paragraph(
|
|
282
|
+
session=self._session, node_id=node_id, node_type="heading",
|
|
283
|
+
)
|
|
260
284
|
|
|
261
285
|
def add_table(
|
|
262
286
|
self,
|
|
@@ -372,8 +396,6 @@ class Document:
|
|
|
372
396
|
Mirrors python-docx: creating a section adds a trailing empty
|
|
373
397
|
paragraph that marks the section boundary.
|
|
374
398
|
"""
|
|
375
|
-
from docx.enum.section import WD_SECTION_START
|
|
376
|
-
from docx.section import Section
|
|
377
399
|
|
|
378
400
|
self._ensure_open()
|
|
379
401
|
# python-docx always inserts an anchor paragraph at the section break
|
|
@@ -113,6 +113,15 @@ class WD_BREAK(Enum):
|
|
|
113
113
|
LINE_CLEAR_RIGHT = "lineClearRight"
|
|
114
114
|
LINE_CLEAR_ALL = "lineClearAll"
|
|
115
115
|
TEXT_WRAPPING = "textWrapping"
|
|
116
|
+
# python-docx 1.x also exposes section breaks via WD_BREAK
|
|
117
|
+
SECTION_CONTINUOUS = "sectionContinuous"
|
|
118
|
+
SECTION_EVEN_PAGE = "sectionEvenPage"
|
|
119
|
+
SECTION_NEXT_PAGE = "sectionNextPage"
|
|
120
|
+
SECTION_ODD_PAGE = "sectionOddPage"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# python-docx internal alias
|
|
124
|
+
WD_BREAK_TYPE = WD_BREAK
|
|
116
125
|
|
|
117
126
|
|
|
118
127
|
class WD_UNDERLINE(Enum):
|
|
@@ -137,6 +146,7 @@ class WD_UNDERLINE(Enum):
|
|
|
137
146
|
|
|
138
147
|
|
|
139
148
|
class WD_COLOR_INDEX(Enum):
|
|
149
|
+
INHERITED = "inherit"
|
|
140
150
|
AUTO = "default"
|
|
141
151
|
BLACK = "black"
|
|
142
152
|
BLUE = "blue"
|
|
@@ -156,5 +166,12 @@ class WD_COLOR_INDEX(Enum):
|
|
|
156
166
|
YELLOW = "yellow"
|
|
157
167
|
|
|
158
168
|
|
|
159
|
-
#
|
|
169
|
+
# Aliases used by python-docx as well
|
|
160
170
|
WD_COLOR = WD_COLOR_INDEX
|
|
171
|
+
WD_PARAGRAPH_ALIGNMENT = WD_ALIGN_PARAGRAPH
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# python-docx 1.x base class that WD_* enums inherit from — we don't need
|
|
175
|
+
# the real base, just a name users can subclass-check against.
|
|
176
|
+
class BaseXmlEnum(Enum):
|
|
177
|
+
pass
|
|
@@ -60,7 +60,11 @@ def _find_first_paragraph_id(obj: object) -> str:
|
|
|
60
60
|
def _collect_paragraph_ids(obj: object, out: list[str]) -> None:
|
|
61
61
|
"""Walk a node tree and collect all paragraph/heading nodeIds in order.
|
|
62
62
|
|
|
63
|
-
Tolerates
|
|
63
|
+
Tolerates multiple shapes Superdoc emits:
|
|
64
|
+
- cell getNodeById: {"node": {"kind": "paragraph", "id": "UUID",
|
|
65
|
+
"paragraph": {"inlines": [...]}}}
|
|
66
|
+
(the cell's inner paragraph — server reports `id` as a bare UUID
|
|
67
|
+
that the addressing layer expects as `paragraph:UUID`)
|
|
64
68
|
- prosemirror-style: {"type": "paragraph", "attrs": {"nodeId": ...}}
|
|
65
69
|
- typed-wrapper: {"paragraph": {...}, "nodeId": "..."}
|
|
66
70
|
- flat-address: {"kind": "block", "nodeType": "paragraph", "nodeId": ...}
|
|
@@ -69,11 +73,22 @@ def _collect_paragraph_ids(obj: object, out: list[str]) -> None:
|
|
|
69
73
|
seen: set[str] = set(out)
|
|
70
74
|
|
|
71
75
|
def _add(nid: object) -> None:
|
|
72
|
-
if isinstance(nid, str)
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
if not isinstance(nid, str) or not nid:
|
|
77
|
+
return
|
|
78
|
+
# Superdoc uses bare UUIDs (or short hashes) — no `paragraph:`
|
|
79
|
+
# prefix. Pass the value through verbatim.
|
|
80
|
+
if nid in seen:
|
|
81
|
+
return
|
|
82
|
+
seen.add(nid)
|
|
83
|
+
out.append(nid)
|
|
75
84
|
|
|
76
85
|
if isinstance(obj, dict):
|
|
86
|
+
# Cell getNodeById shape: {kind: "paragraph", id: "<UUID>", paragraph: {...}}
|
|
87
|
+
kind: object = obj.get("kind")
|
|
88
|
+
if kind == "paragraph" and isinstance(obj.get("id"), str):
|
|
89
|
+
_add(obj.get("id"))
|
|
90
|
+
# Some responses also put the wrapper's id at nodeId.
|
|
91
|
+
_add(obj.get("nodeId"))
|
|
77
92
|
# Prosemirror-style
|
|
78
93
|
t: object = obj.get("type")
|
|
79
94
|
if isinstance(t, str) and t in ("paragraph", "heading"):
|
|
@@ -611,118 +626,73 @@ class _Cell:
|
|
|
611
626
|
def text(self, value: str) -> None:
|
|
612
627
|
"""Set the cell's text content.
|
|
613
628
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
prosemirror paragraph fragment as last resort.
|
|
621
|
-
"""
|
|
622
|
-
from docx.text.paragraph import _node_text
|
|
629
|
+
The cell's single inner paragraph is addressed indirectly — Superdoc
|
|
630
|
+
doesn't expose a paragraph ref that's usable as a `blockId` for text
|
|
631
|
+
selections. Instead we use `doc.insert` with a structural paragraph
|
|
632
|
+
fragment at `placement=insideEnd`, which APPENDS inline runs to the
|
|
633
|
+
cell's existing paragraph. For a freshly-created (empty) cell this
|
|
634
|
+
produces `cell.text == value` on read-back.
|
|
623
635
|
|
|
636
|
+
For cells that already contain text, callers who truly want "replace"
|
|
637
|
+
semantics should first resolve the cell's paragraph via `doc.find`
|
|
638
|
+
and delete it — see _Cell.clear() (Phase 3).
|
|
639
|
+
"""
|
|
624
640
|
cell_id = self._cell_id()
|
|
625
641
|
session = self._table._session
|
|
626
|
-
|
|
627
|
-
# --- Strategy 1: inner paragraph + text-range replace ---
|
|
628
|
-
ids = self._inner_paragraph_ids()
|
|
629
|
-
if ids:
|
|
630
|
-
first = ids[0]
|
|
631
|
-
current = _node_text(session, first)
|
|
632
|
-
try:
|
|
633
|
-
run_sync(
|
|
634
|
-
session.doc.replace(
|
|
635
|
-
{
|
|
636
|
-
"target": {
|
|
637
|
-
"kind": "selection",
|
|
638
|
-
"start": {
|
|
639
|
-
"kind": "text",
|
|
640
|
-
"blockId": first,
|
|
641
|
-
"offset": 0,
|
|
642
|
-
},
|
|
643
|
-
"end": {
|
|
644
|
-
"kind": "text",
|
|
645
|
-
"blockId": first,
|
|
646
|
-
"offset": len(current),
|
|
647
|
-
},
|
|
648
|
-
},
|
|
649
|
-
"text": value,
|
|
650
|
-
},
|
|
651
|
-
),
|
|
652
|
-
)
|
|
653
|
-
for extra in ids[1:]:
|
|
654
|
-
existing = _node_text(session, extra)
|
|
655
|
-
if existing:
|
|
656
|
-
run_sync(
|
|
657
|
-
session.doc.replace(
|
|
658
|
-
{
|
|
659
|
-
"target": {
|
|
660
|
-
"kind": "selection",
|
|
661
|
-
"start": {
|
|
662
|
-
"kind": "text",
|
|
663
|
-
"blockId": extra,
|
|
664
|
-
"offset": 0,
|
|
665
|
-
},
|
|
666
|
-
"end": {
|
|
667
|
-
"kind": "text",
|
|
668
|
-
"blockId": extra,
|
|
669
|
-
"offset": len(existing),
|
|
670
|
-
},
|
|
671
|
-
},
|
|
672
|
-
"text": "",
|
|
673
|
-
},
|
|
674
|
-
),
|
|
675
|
-
)
|
|
676
|
-
return
|
|
677
|
-
except Exception as e:
|
|
678
|
-
_log_warn(
|
|
679
|
-
f"_Cell.text text-range replace failed on paragraph "
|
|
680
|
-
f"{first}: {e!r}; falling back to structural replace.",
|
|
681
|
-
)
|
|
682
|
-
|
|
683
|
-
# --- Strategy 2: markdownToFragment + structural replace ---
|
|
684
642
|
cell_target: dict = {
|
|
685
643
|
"kind": "block",
|
|
686
644
|
"nodeType": "tableCell",
|
|
687
645
|
"nodeId": cell_id,
|
|
688
646
|
}
|
|
647
|
+
# Superdoc only accepts block-typed fragments at the top level
|
|
648
|
+
# (paragraph/heading/table/image/list/sectionBreak/sdt/tableOfContents).
|
|
649
|
+
# We convert the plain-text value through `doc.markdownToFragment`
|
|
650
|
+
# to get a guaranteed-valid `{kind:"paragraph", paragraph:{inlines:[...]}}`
|
|
651
|
+
# shape, then doc.insert appends its inline runs into the cell's
|
|
652
|
+
# existing paragraph (rather than adding a sibling paragraph).
|
|
653
|
+
#
|
|
654
|
+
# Confirmed against real staging Superdoc. This is the ONLY shape
|
|
655
|
+
# that actually lands text inside a tableCell:
|
|
656
|
+
# - text mode + placement → rejected ("placement only valid
|
|
657
|
+
# with structural content")
|
|
658
|
+
# - doc.replace + tableCell target → replaces the cell itself,
|
|
659
|
+
# destroying the table structure
|
|
689
660
|
try:
|
|
690
661
|
frag_result: object = run_sync(
|
|
691
662
|
session.doc.markdown_to_fragment({"markdown": value or ""}),
|
|
692
663
|
)
|
|
693
|
-
fragment: object =
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
run_sync(
|
|
698
|
-
session.doc.replace(
|
|
699
|
-
{"target": cell_target, "content": fragment},
|
|
700
|
-
),
|
|
701
|
-
)
|
|
702
|
-
return
|
|
703
|
-
except Exception as e:
|
|
704
|
-
_log_warn(
|
|
705
|
-
f"_Cell.text markdownToFragment/replace failed: {e!r}; "
|
|
706
|
-
f"falling back to prosemirror fragment.",
|
|
664
|
+
fragment: object = (
|
|
665
|
+
frag_result.get("fragment")
|
|
666
|
+
if isinstance(frag_result, dict)
|
|
667
|
+
else None
|
|
707
668
|
)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
669
|
+
# Fall back to a hand-built fragment with Superdoc's native
|
|
670
|
+
# shape if markdownToFragment is unavailable.
|
|
671
|
+
if not isinstance(fragment, dict):
|
|
672
|
+
fragment = {
|
|
673
|
+
"kind": "paragraph",
|
|
674
|
+
"paragraph": {
|
|
675
|
+
"inlines": (
|
|
676
|
+
[{"kind": "run", "run": {"text": value}}]
|
|
677
|
+
if value
|
|
678
|
+
else []
|
|
679
|
+
),
|
|
680
|
+
},
|
|
681
|
+
}
|
|
715
682
|
run_sync(
|
|
716
|
-
session.doc.
|
|
717
|
-
{
|
|
683
|
+
session.doc.insert(
|
|
684
|
+
{
|
|
685
|
+
"target": cell_target,
|
|
686
|
+
"placement": "insideEnd",
|
|
687
|
+
"content": fragment,
|
|
688
|
+
},
|
|
718
689
|
),
|
|
719
690
|
)
|
|
720
691
|
return
|
|
721
692
|
except Exception as e:
|
|
722
693
|
raise RuntimeError(
|
|
723
694
|
f"Failed to set _Cell.text on cell ({self._row}, {self._col}) "
|
|
724
|
-
f"of table {self._table._fresh_node_id()}:
|
|
725
|
-
f"failed. Last error: {e!r}",
|
|
695
|
+
f"of table {self._table._fresh_node_id()}: {e!r}",
|
|
726
696
|
) from e
|
|
727
697
|
|
|
728
698
|
@property
|
|
@@ -730,7 +700,11 @@ class _Cell:
|
|
|
730
700
|
from docx.text.paragraph import Paragraph
|
|
731
701
|
|
|
732
702
|
return [
|
|
733
|
-
Paragraph(
|
|
703
|
+
Paragraph(
|
|
704
|
+
session=self._table._session,
|
|
705
|
+
node_id=pid,
|
|
706
|
+
node_type="paragraph",
|
|
707
|
+
)
|
|
734
708
|
for pid in self._inner_paragraph_ids()
|
|
735
709
|
]
|
|
736
710
|
|
|
@@ -803,7 +777,11 @@ class _Cell:
|
|
|
803
777
|
raise RuntimeError(
|
|
804
778
|
f"Superdoc did not return nodeId for _Cell.add_paragraph: {result!r}",
|
|
805
779
|
)
|
|
806
|
-
para = Paragraph(
|
|
780
|
+
para = Paragraph(
|
|
781
|
+
session=self._table._session,
|
|
782
|
+
node_id=node_id,
|
|
783
|
+
node_type="paragraph",
|
|
784
|
+
)
|
|
807
785
|
if style:
|
|
808
786
|
para.style = style
|
|
809
787
|
return para
|
|
@@ -76,9 +76,20 @@ def _walk_inlines(info: object) -> list[dict]:
|
|
|
76
76
|
class Paragraph:
|
|
77
77
|
"""A paragraph block in a Word document."""
|
|
78
78
|
|
|
79
|
-
def __init__(
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
*,
|
|
82
|
+
session: "Session",
|
|
83
|
+
node_id: str,
|
|
84
|
+
node_type: str = "paragraph",
|
|
85
|
+
) -> None:
|
|
80
86
|
self._session: "Session" = session
|
|
81
87
|
self._node_id: str = node_id
|
|
88
|
+
# Track node_type at creation so paragraph-level ops can skip a
|
|
89
|
+
# getNodeById round-trip (which raced against Superdoc mutation
|
|
90
|
+
# commits and raised "Block X was not found" for freshly-created
|
|
91
|
+
# blocks). "paragraph" | "heading" | "listItem".
|
|
92
|
+
self._node_type: str = node_type
|
|
82
93
|
|
|
83
94
|
@property
|
|
84
95
|
def text(self) -> str:
|
|
@@ -290,20 +301,16 @@ class Paragraph:
|
|
|
290
301
|
return "\f" in self.text
|
|
291
302
|
|
|
292
303
|
def _block_target(self) -> dict:
|
|
293
|
-
"""Build a {kind, nodeType, nodeId} target for paragraph-level ops.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if isinstance(node_obj, dict):
|
|
301
|
-
raw: object = node_obj.get("nodeType")
|
|
302
|
-
if isinstance(raw, str) and raw:
|
|
303
|
-
node_type = raw
|
|
304
|
+
"""Build a {kind, nodeType, nodeId} target for paragraph-level ops.
|
|
305
|
+
|
|
306
|
+
Uses the node_type cached at construction. This avoids an extra
|
|
307
|
+
getNodeById round-trip that previously raced with Superdoc's
|
|
308
|
+
mutation-commit pipeline and raised "Block X was not found" for
|
|
309
|
+
blocks that had just been created by the same session.
|
|
310
|
+
"""
|
|
304
311
|
return {
|
|
305
312
|
"kind": "block",
|
|
306
|
-
"nodeType":
|
|
313
|
+
"nodeType": self._node_type,
|
|
307
314
|
"nodeId": self._node_id,
|
|
308
315
|
}
|
|
309
316
|
|
|
@@ -381,7 +388,9 @@ class Paragraph:
|
|
|
381
388
|
raise RuntimeError(
|
|
382
389
|
f"Superdoc did not return nodeId for insert_paragraph_before: {result!r}",
|
|
383
390
|
)
|
|
384
|
-
new_para = Paragraph(
|
|
391
|
+
new_para = Paragraph(
|
|
392
|
+
session=self._session, node_id=node_id, node_type="paragraph",
|
|
393
|
+
)
|
|
385
394
|
if style:
|
|
386
395
|
new_para.style = style
|
|
387
396
|
return new_para
|
|
@@ -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
|