athena-python-docx 0.9.0__tar.gz → 0.10.1__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.9.0 → athena_python_docx-0.10.1}/CLAUDE.md +54 -5
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/PKG-INFO +1 -1
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/__init__.py +1 -1
- athena_python_docx-0.10.1/docx/_http.py +379 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/_http_doc.py +6 -0
- athena_python_docx-0.10.1/docx/_table_styles.py +92 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/api.py +56 -8
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/commands.py +28 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/document.py +104 -54
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/errors.py +37 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/table.py +94 -49
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/pyproject.toml +1 -1
- athena_python_docx-0.10.1/scripts/smoke_test_block_not_found.py +175 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/fake_session.py +68 -0
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +12 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -1
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/18_table_cell_text.json +9 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -3
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +1 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/23_nested_table.json +1 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/24_table_add_row_column.json +1 -3
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -2
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/35_full_report.json +9 -33
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/40_large_table_10x10.json +103 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +1 -5
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/49_resume_layout.json +3 -12
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +1 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/56_everything_in_one.json +12 -48
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +1 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -1
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +793 -1993
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/68_invoice.json +62 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/69_newsletter.json +0 -1
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/72_legal_contract.json +6 -24
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +20 -80
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +9 -27
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +24 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -1
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/91_many_small_tables.json +153 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +1 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +3 -12
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/ex04_50x50_table.json +53 -0
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +5 -0
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/mega10_api_documentation.json → athena_python_docx-0.10.1/tests/fidelity/op_snapshots/ex10_complex_bom.json +126 -113
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +80 -320
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -1
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/rw10_colored_grid_table.json → athena_python_docx-0.10.1/tests/fidelity/op_snapshots/ex14_styled_report_table.json +36 -69
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +84 -336
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega01_book_chapter.json +13 -52
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega02_research_proposal.json +18 -72
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega03_financial_statement.json +21 -98
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega05_user_manual.json +20 -88
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/ex14_styled_report_table.json → athena_python_docx-0.10.1/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +44 -75
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega09_signed_contract.json +8 -36
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json → athena_python_docx-0.10.1/tests/fidelity/op_snapshots/mega10_api_documentation.json +104 -97
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +12 -36
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +12 -36
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/rw08_table_merged_header.json +13 -0
- athena_python_docx-0.10.1/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +148 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +1 -4
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/reports/gap_report.json +1 -1
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/snapshots/athena_latest.json +0 -50
- athena_python_docx-0.10.1/tests/test_document_asset_id_property.py +52 -0
- athena_python_docx-0.10.1/tests/test_document_create_from_template.py +312 -0
- athena_python_docx-0.10.1/tests/test_document_factory_validation.py +52 -0
- athena_python_docx-0.10.1/tests/test_e2e_partial_failure_cascade.py +278 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_parity_misc.py +4 -2
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_document_audit.py +55 -61
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_style_acceptance.py +5 -2
- athena_python_docx-0.10.1/tests/test_table_set_cell_perf.py +127 -0
- athena_python_docx-0.10.1/tests/test_table_style_id_resolution.py +173 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_wire_contract.py +9 -0
- athena_python_docx-0.9.0/docx/_http.py +0 -189
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -24
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -27
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -403
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/68_invoice.json +0 -114
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -54
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -453
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -203
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -8
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -596
- athena_python_docx-0.9.0/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -35
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/.gitignore +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/README.md +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/_batching.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/_buffer.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/_ptc.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/client.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/comments.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/enum/section.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/enum/style.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/enum/table.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/enum/text.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/exceptions.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/revisions.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/section.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/settings.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/shape.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/shared.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/styles/style.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/font.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/run.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/docx/typing.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/scripts/publish.sh +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/scripts/release.sh +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/conftest.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/README.md +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_commands.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_comments.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.9.0 → athena_python_docx-0.10.1}/uv.lock +0 -0
|
@@ -81,20 +81,69 @@ 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.
|
|
124
|
+
|
|
125
|
+
- **`Document.asset_id: str`** read-only property — the Athena asset
|
|
126
|
+
id this Document is bound to (`asset_<uuid>`). Agent-callable so
|
|
127
|
+
the typical pattern `doc = Document.create(...); print(doc.asset_id)`
|
|
128
|
+
works without reaching into `doc._session._asset_id`. Stock
|
|
129
|
+
python-docx has no analogue (it operates on local files), so this
|
|
130
|
+
is an Athena-only addition. Mirrors the parallel public accessors
|
|
131
|
+
on the other studios: `Workbook.workbook_id` (xlsx) and
|
|
132
|
+
`Presentation.deck_id` (pptx).
|
|
90
133
|
|
|
91
|
-
- **`Document.save(path_or_stream=None)`** — argument is optional
|
|
92
|
-
|
|
134
|
+
- **`Document.save(path_or_stream=None)`** — argument is optional and,
|
|
135
|
+
when supplied, raises
|
|
136
|
+
:class:`docx.errors.LocalSaveTargetNotSupportedError`. Stock
|
|
137
|
+
python-docx requires it and `TypeError`s on no-arg, but in an
|
|
93
138
|
asset-backed SDK there is no local file to write — writes are always
|
|
94
139
|
in-place against the Y.Doc. Forcing the upstream signature broke
|
|
95
140
|
every agent invocation that reflexively called `doc.save()` (a
|
|
96
|
-
near-universal Python pattern), so
|
|
97
|
-
|
|
141
|
+
near-universal Python pattern), so the no-arg form is supported.
|
|
142
|
+
When a path or stream IS supplied, the SDK cannot fulfill the
|
|
143
|
+
implied "write bytes to this target" contract, so the call raises
|
|
144
|
+
loudly rather than silently flushing — agents must use Olympus's
|
|
145
|
+
Export DOCX (which goes through the SuperDoc session that already
|
|
146
|
+
has the bytes) instead of expecting a local file.
|
|
98
147
|
|
|
99
148
|
- **`Document.track_revisions: bool`** — when `True`, all subsequent
|
|
100
149
|
mutations are recorded as tracked revisions instead of direct edits.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
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,379 @@
|
|
|
1
|
+
"""HTTP bootstrap for Document.create().
|
|
2
|
+
|
|
3
|
+
The SDK's primary transport is y-websocket via Superdoc; this module is
|
|
4
|
+
the *only* HTTP client in the package. It exists solely so that
|
|
5
|
+
``Document.create()`` can hit ``POST /docs/empty`` on docx-studio to
|
|
6
|
+
provision a new SuperDocument asset and receive a collab bundle to open
|
|
7
|
+
the document with.
|
|
8
|
+
|
|
9
|
+
We use ``urllib`` from stdlib to avoid pulling ``httpx`` / ``requests``
|
|
10
|
+
into the runtime — this code runs in Daytona sandboxes where every MB
|
|
11
|
+
matters, and the existing SDK already keeps its dep tree tight.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.request
|
|
20
|
+
from typing import TypedDict
|
|
21
|
+
|
|
22
|
+
from docx.errors import (
|
|
23
|
+
AuthenticationError,
|
|
24
|
+
DocxError,
|
|
25
|
+
SessionError,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _CollabBundle(TypedDict):
|
|
30
|
+
"""The collab credentials returned alongside a freshly-created asset.
|
|
31
|
+
|
|
32
|
+
Note: env-var-shape (matches what ``Session`` reads), even though
|
|
33
|
+
the wire format from docx-studio uses different key names. The
|
|
34
|
+
keys are renamed at parse time.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
SUPERDOC_COLLAB_TOKEN: str
|
|
38
|
+
KERYX_WS_URL: str
|
|
39
|
+
ATHENA_WORKSPACE_ID: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CreateAssetResult(TypedDict):
|
|
43
|
+
"""Parsed response from ``POST /docs/empty``."""
|
|
44
|
+
|
|
45
|
+
asset_id: str
|
|
46
|
+
name: str
|
|
47
|
+
workspace_id: str
|
|
48
|
+
collab: _CollabBundle
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_BASE_URL_ENV = "ATHENA_DOCX_BASE_URL"
|
|
52
|
+
_API_KEY_ENV = "ATHENA_DOCX_API_KEY" # noqa: S105
|
|
53
|
+
_AGORA_BASE_URL_ENV = "ATHENA_AGORA_BASE_URL"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _derive_agora_base_url(docx_base_url: str) -> str | None:
|
|
57
|
+
"""Best-effort derive an Agora base URL from a docx-studio base URL.
|
|
58
|
+
|
|
59
|
+
The Athena hosts follow a parallel ``<service>.<env>.athenaintel.com``
|
|
60
|
+
convention, so substituting ``docx-studio`` → ``agora`` lands on the
|
|
61
|
+
matching Agora service. Returns ``None`` when the substitution
|
|
62
|
+
doesn't apply (e.g. ``http://localhost:4100``), in which case the
|
|
63
|
+
caller must provide ``agora_base_url`` explicitly.
|
|
64
|
+
"""
|
|
65
|
+
if "docx-studio" not in docx_base_url:
|
|
66
|
+
return None
|
|
67
|
+
return docx_base_url.replace("docx-studio", "agora")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_empty_document(
|
|
71
|
+
*,
|
|
72
|
+
base_url: str | None = None,
|
|
73
|
+
api_key: str | None = None,
|
|
74
|
+
name: str | None = None,
|
|
75
|
+
parent_folder_id: str | None = None,
|
|
76
|
+
workspace_id: str | None = None,
|
|
77
|
+
timeout: float = 30.0,
|
|
78
|
+
) -> CreateAssetResult:
|
|
79
|
+
"""Call ``POST {base_url}/docs/empty`` and parse the response.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
base_url: docx-studio API base, e.g. ``https://docx-studio.stg.athenaintel.com``.
|
|
83
|
+
Falls back to ``$ATHENA_DOCX_BASE_URL``.
|
|
84
|
+
api_key: Athena API key (PropelAuth user API key) or PropelAuth
|
|
85
|
+
access token. Sent as ``Authorization: Bearer <api_key>``.
|
|
86
|
+
Falls back to ``$ATHENA_DOCX_API_KEY``.
|
|
87
|
+
name: Optional display title.
|
|
88
|
+
parent_folder_id: Optional parent folder; defaults to workspace root.
|
|
89
|
+
workspace_id: Optional workspace UUID; defaults to caller's
|
|
90
|
+
current workspace.
|
|
91
|
+
timeout: Request timeout in seconds.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Parsed response dict with the new asset_id and a collab bundle
|
|
95
|
+
ready to feed into ``Session(..., bundle=...)``.
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
SessionError: missing base_url or unparseable response.
|
|
99
|
+
AuthenticationError: missing api_key, or the server returned 401/403.
|
|
100
|
+
DocxError: any other 4xx/5xx from the server.
|
|
101
|
+
"""
|
|
102
|
+
resolved_base: str | None = base_url or os.environ.get(_BASE_URL_ENV)
|
|
103
|
+
resolved_key: str | None = api_key or os.environ.get(_API_KEY_ENV)
|
|
104
|
+
|
|
105
|
+
if not resolved_base:
|
|
106
|
+
raise SessionError(
|
|
107
|
+
f"Missing base_url and {_BASE_URL_ENV} env var. "
|
|
108
|
+
"Pass base_url= to Document.create() or set the env var.",
|
|
109
|
+
)
|
|
110
|
+
if not resolved_key:
|
|
111
|
+
raise AuthenticationError(
|
|
112
|
+
f"Missing api_key and {_API_KEY_ENV} env var. "
|
|
113
|
+
"Pass api_key= to Document.create() or set the env var.",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
url: str = resolved_base.rstrip("/") + "/docs/empty"
|
|
117
|
+
body: dict[str, str] = {}
|
|
118
|
+
if name is not None:
|
|
119
|
+
body["name"] = name
|
|
120
|
+
if parent_folder_id is not None:
|
|
121
|
+
body["parentFolderId"] = parent_folder_id
|
|
122
|
+
if workspace_id is not None:
|
|
123
|
+
body["workspaceId"] = workspace_id
|
|
124
|
+
|
|
125
|
+
# Lazy import — _http is loaded lazily by Document.create(), so the
|
|
126
|
+
# package is fully initialized by the time we reach this code.
|
|
127
|
+
from docx import __version__
|
|
128
|
+
|
|
129
|
+
payload: bytes = json.dumps(body).encode("utf-8")
|
|
130
|
+
req = urllib.request.Request( # noqa: S310
|
|
131
|
+
url,
|
|
132
|
+
data=payload,
|
|
133
|
+
method="POST",
|
|
134
|
+
headers={
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
"Authorization": f"Bearer {resolved_key}",
|
|
137
|
+
"Accept": "application/json",
|
|
138
|
+
"User-Agent": f"athena-python-docx/{__version__}",
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310
|
|
144
|
+
raw: bytes = resp.read()
|
|
145
|
+
except urllib.error.HTTPError as e:
|
|
146
|
+
err_body: str = ""
|
|
147
|
+
try:
|
|
148
|
+
err_body = e.read().decode("utf-8", errors="replace")
|
|
149
|
+
except Exception: # noqa: BLE001
|
|
150
|
+
pass
|
|
151
|
+
if e.code in (401, 403):
|
|
152
|
+
raise AuthenticationError(
|
|
153
|
+
f"docx-studio rejected the API key (HTTP {e.code}): {err_body}",
|
|
154
|
+
) from e
|
|
155
|
+
raise DocxError(
|
|
156
|
+
f"docx-studio /docs/empty returned HTTP {e.code}: {err_body}",
|
|
157
|
+
) from e
|
|
158
|
+
except urllib.error.URLError as e:
|
|
159
|
+
raise SessionError(
|
|
160
|
+
f"Unable to reach docx-studio at {url}: {e.reason}",
|
|
161
|
+
) from e
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
parsed: dict = json.loads(raw.decode("utf-8"))
|
|
165
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as e:
|
|
166
|
+
raise SessionError(
|
|
167
|
+
f"docx-studio returned non-JSON response: {raw[:200]!r}",
|
|
168
|
+
) from e
|
|
169
|
+
|
|
170
|
+
asset_id_obj = parsed.get("assetId")
|
|
171
|
+
name_obj = parsed.get("name")
|
|
172
|
+
ws_obj = parsed.get("workspaceId")
|
|
173
|
+
collab_obj = parsed.get("collab")
|
|
174
|
+
if not (
|
|
175
|
+
isinstance(asset_id_obj, str)
|
|
176
|
+
and isinstance(name_obj, str)
|
|
177
|
+
and isinstance(ws_obj, str)
|
|
178
|
+
and isinstance(collab_obj, dict)
|
|
179
|
+
):
|
|
180
|
+
raise SessionError(
|
|
181
|
+
f"docx-studio response is missing required fields: {parsed!r}",
|
|
182
|
+
)
|
|
183
|
+
token_obj = collab_obj.get("token")
|
|
184
|
+
ws_url_obj = collab_obj.get("wsUrl")
|
|
185
|
+
bundle_ws_obj = collab_obj.get("workspaceId")
|
|
186
|
+
if not (
|
|
187
|
+
isinstance(token_obj, str)
|
|
188
|
+
and isinstance(ws_url_obj, str)
|
|
189
|
+
and isinstance(bundle_ws_obj, str)
|
|
190
|
+
):
|
|
191
|
+
raise SessionError(
|
|
192
|
+
f"docx-studio collab bundle is malformed: {collab_obj!r}",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return CreateAssetResult(
|
|
196
|
+
asset_id=asset_id_obj,
|
|
197
|
+
name=name_obj,
|
|
198
|
+
workspace_id=ws_obj,
|
|
199
|
+
collab=_CollabBundle(
|
|
200
|
+
SUPERDOC_COLLAB_TOKEN=token_obj,
|
|
201
|
+
KERYX_WS_URL=ws_url_obj,
|
|
202
|
+
ATHENA_WORKSPACE_ID=bundle_ws_obj,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class UploadAssetResult(TypedDict):
|
|
208
|
+
"""Parsed response from agora's ``POST /api/super-docs/create-from-upload``."""
|
|
209
|
+
|
|
210
|
+
asset_id: str
|
|
211
|
+
name: str
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def upload_document(
|
|
215
|
+
*,
|
|
216
|
+
docx_path: str,
|
|
217
|
+
docx_base_url: str | None = None,
|
|
218
|
+
agora_base_url: str | None = None,
|
|
219
|
+
api_key: str | None = None,
|
|
220
|
+
name: str | None = None,
|
|
221
|
+
parent_folder_id: str | None = None,
|
|
222
|
+
timeout: float = 120.0,
|
|
223
|
+
) -> UploadAssetResult:
|
|
224
|
+
"""Upload a local ``.docx`` to agora and create a new SuperDocument asset.
|
|
225
|
+
|
|
226
|
+
The endpoint lives on agora (``POST /api/super-docs/create-from-upload``),
|
|
227
|
+
not docx-studio, because the upload + Y.Doc-seed path is implemented
|
|
228
|
+
server-side in agora. The SDK still talks to docx-studio for command
|
|
229
|
+
application; this function only handles the initial create.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
docx_path: Filesystem path to the ``.docx`` to upload.
|
|
233
|
+
docx_base_url: docx-studio base URL — used as a fallback to derive
|
|
234
|
+
the agora URL when ``agora_base_url`` is not supplied. Falls
|
|
235
|
+
back to ``$ATHENA_DOCX_BASE_URL``.
|
|
236
|
+
agora_base_url: Explicit Agora base URL. Falls back to
|
|
237
|
+
``$ATHENA_AGORA_BASE_URL`` and finally to a host substitution
|
|
238
|
+
of ``docx-studio`` → ``agora`` against ``docx_base_url``.
|
|
239
|
+
api_key: Athena API key. Falls back to ``$ATHENA_DOCX_API_KEY``.
|
|
240
|
+
name: Optional asset title (defaults to the file's stem).
|
|
241
|
+
parent_folder_id: Optional parent folder for the new asset.
|
|
242
|
+
timeout: Request timeout in seconds (uploads can be slow for big
|
|
243
|
+
decks/templates).
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dict with the new asset_id and the resolved title.
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
SessionError: missing base URLs / unreachable agora.
|
|
250
|
+
AuthenticationError: missing api_key, or 401/403 from server.
|
|
251
|
+
DocxError: any other 4xx/5xx, or a malformed response.
|
|
252
|
+
"""
|
|
253
|
+
import io
|
|
254
|
+
import mimetypes
|
|
255
|
+
import os
|
|
256
|
+
import uuid
|
|
257
|
+
|
|
258
|
+
resolved_docx_base: str | None = docx_base_url or os.environ.get(_BASE_URL_ENV)
|
|
259
|
+
resolved_key: str | None = api_key or os.environ.get(_API_KEY_ENV)
|
|
260
|
+
resolved_agora_base: str | None = (
|
|
261
|
+
agora_base_url
|
|
262
|
+
or os.environ.get(_AGORA_BASE_URL_ENV)
|
|
263
|
+
or (_derive_agora_base_url(resolved_docx_base) if resolved_docx_base else None)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if not resolved_agora_base:
|
|
267
|
+
raise SessionError(
|
|
268
|
+
f"Missing agora_base_url, {_AGORA_BASE_URL_ENV}, and {_BASE_URL_ENV} "
|
|
269
|
+
"(or the docx-studio URL doesn't follow the docx-studio↔agora "
|
|
270
|
+
"host pattern). Pass agora_base_url= to Document.create() or set "
|
|
271
|
+
f"the {_AGORA_BASE_URL_ENV} env var.",
|
|
272
|
+
)
|
|
273
|
+
if not resolved_key:
|
|
274
|
+
raise AuthenticationError(
|
|
275
|
+
f"Missing api_key and {_API_KEY_ENV} env var. "
|
|
276
|
+
"Pass api_key= to Document.create() or set the env var.",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if not os.path.exists(docx_path):
|
|
280
|
+
raise SessionError(f"DOCX file not found: {docx_path}")
|
|
281
|
+
if not docx_path.lower().endswith(".docx"):
|
|
282
|
+
raise SessionError(
|
|
283
|
+
f"Only .docx files are accepted by the upload endpoint (got {docx_path})",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
title = name
|
|
287
|
+
if title is None:
|
|
288
|
+
base = os.path.basename(docx_path)
|
|
289
|
+
title = base[:-5] if base.lower().endswith(".docx") else base
|
|
290
|
+
|
|
291
|
+
# Build the agora URL with query-string params (FastAPI binds non-body
|
|
292
|
+
# args from the query string for multipart routes).
|
|
293
|
+
from urllib.parse import urlencode
|
|
294
|
+
|
|
295
|
+
qs: dict[str, str] = {"is_template": "false", "title": title}
|
|
296
|
+
if parent_folder_id is not None:
|
|
297
|
+
qs["parent_folder_id"] = parent_folder_id
|
|
298
|
+
|
|
299
|
+
url: str = (
|
|
300
|
+
resolved_agora_base.rstrip("/")
|
|
301
|
+
+ "/api/super-docs/create-from-upload?"
|
|
302
|
+
+ urlencode(qs)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Build a minimal multipart/form-data body without pulling in
|
|
306
|
+
# `requests` — keep the urllib-only dep profile.
|
|
307
|
+
with open(docx_path, "rb") as f:
|
|
308
|
+
file_bytes: bytes = f.read()
|
|
309
|
+
content_type: str = (
|
|
310
|
+
mimetypes.guess_type(docx_path)[0]
|
|
311
|
+
or "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
312
|
+
)
|
|
313
|
+
boundary: str = f"----athenaDocxBoundary{uuid.uuid4().hex}"
|
|
314
|
+
filename = os.path.basename(docx_path)
|
|
315
|
+
body_parts: list[bytes] = []
|
|
316
|
+
body_parts.append(f"--{boundary}\r\n".encode("utf-8"))
|
|
317
|
+
body_parts.append(
|
|
318
|
+
(
|
|
319
|
+
'Content-Disposition: form-data; name="file"; '
|
|
320
|
+
f'filename="{filename}"\r\n'
|
|
321
|
+
f"Content-Type: {content_type}\r\n\r\n"
|
|
322
|
+
).encode("utf-8"),
|
|
323
|
+
)
|
|
324
|
+
body_parts.append(file_bytes)
|
|
325
|
+
body_parts.append(f"\r\n--{boundary}--\r\n".encode("utf-8"))
|
|
326
|
+
body: bytes = b"".join(body_parts)
|
|
327
|
+
|
|
328
|
+
from docx import __version__
|
|
329
|
+
|
|
330
|
+
req = urllib.request.Request( # noqa: S310
|
|
331
|
+
url,
|
|
332
|
+
data=body,
|
|
333
|
+
method="POST",
|
|
334
|
+
headers={
|
|
335
|
+
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
|
336
|
+
"Authorization": f"Bearer {resolved_key}",
|
|
337
|
+
"Accept": "application/json",
|
|
338
|
+
"User-Agent": f"athena-python-docx/{__version__}",
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310
|
|
344
|
+
raw: bytes = resp.read()
|
|
345
|
+
except urllib.error.HTTPError as e:
|
|
346
|
+
err_body: str = ""
|
|
347
|
+
try:
|
|
348
|
+
err_body = e.read().decode("utf-8", errors="replace")
|
|
349
|
+
except Exception: # noqa: BLE001
|
|
350
|
+
pass
|
|
351
|
+
if e.code in (401, 403):
|
|
352
|
+
raise AuthenticationError(
|
|
353
|
+
f"agora rejected the API key (HTTP {e.code}): {err_body}",
|
|
354
|
+
) from e
|
|
355
|
+
raise DocxError(
|
|
356
|
+
f"agora /api/super-docs/create-from-upload returned HTTP {e.code}: {err_body}",
|
|
357
|
+
) from e
|
|
358
|
+
except urllib.error.URLError as e:
|
|
359
|
+
raise SessionError(
|
|
360
|
+
f"Unable to reach agora at {url}: {e.reason}",
|
|
361
|
+
) from e
|
|
362
|
+
|
|
363
|
+
# Avoid unused import warning when io isn't otherwise referenced.
|
|
364
|
+
_ = io
|
|
365
|
+
try:
|
|
366
|
+
parsed: dict = json.loads(raw.decode("utf-8"))
|
|
367
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as e:
|
|
368
|
+
raise SessionError(
|
|
369
|
+
f"agora returned non-JSON response: {raw[:200]!r}",
|
|
370
|
+
) from e
|
|
371
|
+
|
|
372
|
+
asset_id_obj = parsed.get("id")
|
|
373
|
+
name_obj = parsed.get("title")
|
|
374
|
+
if not (isinstance(asset_id_obj, str) and isinstance(name_obj, str)):
|
|
375
|
+
raise SessionError(
|
|
376
|
+
f"agora response is missing required fields: {parsed!r}",
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return UploadAssetResult(asset_id=asset_id_obj, name=name_obj)
|
|
@@ -30,6 +30,7 @@ format is now typed end-to-end either way.
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import json
|
|
33
|
+
import os
|
|
33
34
|
from typing import Any
|
|
34
35
|
|
|
35
36
|
import requests
|
|
@@ -106,6 +107,7 @@ from docx.commands import (
|
|
|
106
107
|
TablesSetRowHeight,
|
|
107
108
|
TablesSetStyle,
|
|
108
109
|
TablesSetTableOptions,
|
|
110
|
+
TableSetCell,
|
|
109
111
|
TrackChangesDecide,
|
|
110
112
|
TrackChangesGet,
|
|
111
113
|
TrackChangesList,
|
|
@@ -372,6 +374,9 @@ def _http_post_json(
|
|
|
372
374
|
"Accept": "application/json",
|
|
373
375
|
"User-Agent": _user_agent(),
|
|
374
376
|
}
|
|
377
|
+
custom_attr = os.environ.get("ATHENA_DOCX_CUSTOM_ATTRIBUTIONS")
|
|
378
|
+
if custom_attr:
|
|
379
|
+
headers["X-Custom-Attributions"] = custom_attr
|
|
375
380
|
try:
|
|
376
381
|
resp = session.post(
|
|
377
382
|
url,
|
|
@@ -647,6 +652,7 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
647
652
|
"tables.set_cell_properties": TablesSetCellProperties,
|
|
648
653
|
"tables.set_column_width": TablesSetColumnWidth,
|
|
649
654
|
"tables.set_row_height": TablesSetRowHeight,
|
|
655
|
+
"tables.set_cell": TableSetCell,
|
|
650
656
|
# Images (mutations)
|
|
651
657
|
"images.set_size": SetImageSize,
|
|
652
658
|
"images.set_alt_text": SetImageAltText,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Friendly-name → OOXML styleId resolution for Word's built-in table styles.
|
|
2
|
+
|
|
3
|
+
Word stores built-in table styles in ``word/styles.xml`` with internal
|
|
4
|
+
styleIds like ``LightGrid-Accent1`` and friendly display names like
|
|
5
|
+
``"Light Grid Accent 1"``. python-docx accepts the friendly name and
|
|
6
|
+
resolves it against the loaded document's styles collection at write
|
|
7
|
+
time. Our SDK ships commands over HTTP and never sees ``styles.xml``,
|
|
8
|
+
so we keep an in-process map and resolve the friendly form to the
|
|
9
|
+
canonical styleId before the value reaches the
|
|
10
|
+
``<w:tblStyle w:val="..."/>`` element in the exported ``.docx``.
|
|
11
|
+
|
|
12
|
+
Without this resolution Word can't find the style definition in
|
|
13
|
+
``styles.xml`` and silently falls back to ``Normal Table`` — i.e.
|
|
14
|
+
no borders, no header fill, no banding.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build_builtin_table_style_map() -> dict[str, str]:
|
|
21
|
+
"""Build the friendly-name → styleId map for all Word built-in table styles.
|
|
22
|
+
|
|
23
|
+
Mirrors every ``<w:lsdException w:name="...">`` table-style
|
|
24
|
+
declaration in Word's default ``styles.xml`` for Word 2007 and
|
|
25
|
+
Word 2013+. Pattern: friendly names use spaces; styleIds collapse
|
|
26
|
+
spaces in the base and join the optional ``Accent N`` suffix with
|
|
27
|
+
a hyphen.
|
|
28
|
+
"""
|
|
29
|
+
table: dict[str, str] = {
|
|
30
|
+
"Normal Table": "TableNormal",
|
|
31
|
+
"Table Grid": "TableGrid",
|
|
32
|
+
# Word 2013+ added a single un-numbered "Grid Table Light"
|
|
33
|
+
# alongside the numbered 1–7 series.
|
|
34
|
+
"Grid Table Light": "GridTableLight",
|
|
35
|
+
}
|
|
36
|
+
# Word 2007 era — 14 bases × (base + 6 accents) = 98 entries.
|
|
37
|
+
bases_2007: tuple[tuple[str, str], ...] = (
|
|
38
|
+
("Light Shading", "LightShading"),
|
|
39
|
+
("Light List", "LightList"),
|
|
40
|
+
("Light Grid", "LightGrid"),
|
|
41
|
+
("Medium Shading 1", "MediumShading1"),
|
|
42
|
+
("Medium Shading 2", "MediumShading2"),
|
|
43
|
+
("Medium List 1", "MediumList1"),
|
|
44
|
+
("Medium List 2", "MediumList2"),
|
|
45
|
+
("Medium Grid 1", "MediumGrid1"),
|
|
46
|
+
("Medium Grid 2", "MediumGrid2"),
|
|
47
|
+
("Medium Grid 3", "MediumGrid3"),
|
|
48
|
+
("Dark List", "DarkList"),
|
|
49
|
+
("Colorful Shading", "ColorfulShading"),
|
|
50
|
+
("Colorful List", "ColorfulList"),
|
|
51
|
+
("Colorful Grid", "ColorfulGrid"),
|
|
52
|
+
)
|
|
53
|
+
# Word 2013+ Plain Table — 5 entries, no accents.
|
|
54
|
+
for n in range(1, 6):
|
|
55
|
+
table[f"Plain Table {n}"] = f"PlainTable{n}"
|
|
56
|
+
# Word 2013+ Grid Table / List Table — 7 bases × (base + 6 accents) = 49
|
|
57
|
+
# entries per series. The "Light"/"Dark"/"Colorful" suffix on some
|
|
58
|
+
# bases is part of the styleId, not a separate "Accent N" axis.
|
|
59
|
+
bases_2013: tuple[tuple[str, str], ...] = (
|
|
60
|
+
("Grid Table 1 Light", "GridTable1Light"),
|
|
61
|
+
("Grid Table 2", "GridTable2"),
|
|
62
|
+
("Grid Table 3", "GridTable3"),
|
|
63
|
+
("Grid Table 4", "GridTable4"),
|
|
64
|
+
("Grid Table 5 Dark", "GridTable5Dark"),
|
|
65
|
+
("Grid Table 6 Colorful", "GridTable6Colorful"),
|
|
66
|
+
("Grid Table 7 Colorful", "GridTable7Colorful"),
|
|
67
|
+
("List Table 1 Light", "ListTable1Light"),
|
|
68
|
+
("List Table 2", "ListTable2"),
|
|
69
|
+
("List Table 3", "ListTable3"),
|
|
70
|
+
("List Table 4", "ListTable4"),
|
|
71
|
+
("List Table 5 Dark", "ListTable5Dark"),
|
|
72
|
+
("List Table 6 Colorful", "ListTable6Colorful"),
|
|
73
|
+
("List Table 7 Colorful", "ListTable7Colorful"),
|
|
74
|
+
)
|
|
75
|
+
for friendly, style_id in (*bases_2007, *bases_2013):
|
|
76
|
+
table[friendly] = style_id
|
|
77
|
+
for n in range(1, 7):
|
|
78
|
+
table[f"{friendly} Accent {n}"] = f"{style_id}-Accent{n}"
|
|
79
|
+
return table
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
BUILTIN_TABLE_STYLE_IDS: dict[str, str] = _build_builtin_table_style_map()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def resolve_table_style_id(style: str) -> str:
|
|
86
|
+
"""Return the OOXML styleId for a Word built-in table style friendly name.
|
|
87
|
+
|
|
88
|
+
Custom user-defined style names pass through unchanged so that
|
|
89
|
+
callers that already supply canonical styleIds (or non-built-in
|
|
90
|
+
style names defined elsewhere) keep working.
|
|
91
|
+
"""
|
|
92
|
+
return BUILTIN_TABLE_STYLE_IDS.get(style, style)
|
|
@@ -6,7 +6,10 @@ python-docx signature:
|
|
|
6
6
|
Our signature deviates from the path-based one because we don't open
|
|
7
7
|
.docx files from disk — we open Y.Doc assets from Keryx. The parameter
|
|
8
8
|
is reused: pass an asset_id string where python-docx would take a file
|
|
9
|
-
path.
|
|
9
|
+
path. The id MUST be ``asset_<uuid>``; anything else (a title, a file
|
|
10
|
+
path, etc.) raises ``ValueError`` before any HTTP request. To mint a
|
|
11
|
+
new asset, call ``Document.create(name=...)`` and reuse the returned
|
|
12
|
+
``doc.asset_id`` on subsequent ``Document(...)`` opens.
|
|
10
13
|
|
|
11
14
|
For net-new asset creation, use the classmethod ``Document.create()``
|
|
12
15
|
(see ``docx.document``). Stock python-docx returns a blank document
|
|
@@ -18,8 +21,18 @@ write that has to happen first. ``Document.create()`` handles that via
|
|
|
18
21
|
|
|
19
22
|
from __future__ import annotations
|
|
20
23
|
|
|
24
|
+
import re
|
|
25
|
+
|
|
21
26
|
from docx.document import Document as _Document
|
|
22
27
|
|
|
28
|
+
# Asset-id shape validation. Athena SuperDocument assets are
|
|
29
|
+
# ``asset_<uuid>``. Rejecting anything else up front prevents the
|
|
30
|
+
# common agent failure mode of passing a document TITLE
|
|
31
|
+
# (``Document("Q4 Report Draft")``) instead of its id, which would
|
|
32
|
+
# otherwise round-trip through the docx-studio session and fail with
|
|
33
|
+
# an opaque server error.
|
|
34
|
+
_ASSET_ID_PATTERN = re.compile(r"^asset_[A-Za-z0-9_-]+$")
|
|
35
|
+
|
|
23
36
|
|
|
24
37
|
def Document(
|
|
25
38
|
docx: str | None = None,
|
|
@@ -67,14 +80,49 @@ def Document(
|
|
|
67
80
|
"To create a brand-new document, call "
|
|
68
81
|
"Document.create(name=..., base_url=..., api_key=...).",
|
|
69
82
|
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
if not _ASSET_ID_PATTERN.match(docx):
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Document({docx!r}) is not a valid SuperDocument asset id. "
|
|
86
|
+
f"Asset ids look like 'asset_<uuid>' "
|
|
87
|
+
f"(e.g., 'asset_3a9328bc-9c1c-4498-be8f-bda3883276f5'). To "
|
|
88
|
+
f"open an existing document, pass its asset id — NOT its "
|
|
89
|
+
f"title. To create a new document titled {docx!r}, call "
|
|
90
|
+
f"Document.create(name={docx!r}) and reuse the returned "
|
|
91
|
+
f"document's asset id on subsequent calls in the same "
|
|
92
|
+
f"conversation."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# PTC: surface document open as its own sub-tool-card so the
|
|
96
|
+
# frontend can react (e.g., auto-open the asset tab). The paired
|
|
97
|
+
# ``Document.create()`` path emits ``CreateDocument``; this open
|
|
98
|
+
# path emits ``OpenDocument`` with the asset id.
|
|
99
|
+
from docx import _ptc
|
|
100
|
+
|
|
101
|
+
call_id = _ptc.emit_begin("OpenDocument", {"assetId": docx})
|
|
102
|
+
try:
|
|
103
|
+
doc = _Document(
|
|
104
|
+
asset_id=docx,
|
|
105
|
+
http_base_url=base_url,
|
|
106
|
+
http_api_key=api_key,
|
|
107
|
+
user_name=user_name,
|
|
108
|
+
user_email=user_email,
|
|
109
|
+
track_revisions=track_revisions,
|
|
110
|
+
)
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
_ptc.emit_end(
|
|
113
|
+
call_id=call_id,
|
|
114
|
+
tool_name="OpenDocument",
|
|
115
|
+
result={"ok": False, "error": str(exc)},
|
|
116
|
+
is_error=True,
|
|
117
|
+
)
|
|
118
|
+
raise
|
|
119
|
+
_ptc.emit_end(
|
|
120
|
+
call_id=call_id,
|
|
121
|
+
tool_name="OpenDocument",
|
|
122
|
+
result={"ok": True, "assetId": docx},
|
|
123
|
+
is_error=False,
|
|
77
124
|
)
|
|
125
|
+
return doc
|
|
78
126
|
|
|
79
127
|
|
|
80
128
|
# Re-export the classmethod factories at module level so callers can
|