athena-python-docx 0.10.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.10.0 → athena_python_docx-0.10.1}/CLAUDE.md +35 -1
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/PKG-INFO +1 -1
- {athena_python_docx-0.10.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.10.1/docx/_table_styles.py +92 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/document.py +59 -15
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/table.py +4 -1
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/pyproject.toml +1 -1
- athena_python_docx-0.10.1/tests/test_document_create_from_template.py +312 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_style_acceptance.py +5 -2
- athena_python_docx-0.10.1/tests/test_table_style_id_resolution.py +173 -0
- athena_python_docx-0.10.0/docx/_http.py +0 -189
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/.gitignore +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/README.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/_batching.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/_buffer.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/_http_doc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/_ptc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/api.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/client.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/commands.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/comments.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/enum/section.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/enum/style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/enum/table.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/enum/text.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/errors.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/exceptions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/revisions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/section.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/settings.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/shape.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/shared.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/styles/style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/font.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/run.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/docx/typing.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/scripts/publish.sh +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/scripts/release.sh +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/conftest.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.10.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.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/README.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_commands.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_comments.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.10.0 → athena_python_docx-0.10.1}/uv.lock +0 -0
|
@@ -81,12 +81,46 @@ remaining work* for the exact wire-op shape needed to unblock each.
|
|
|
81
81
|
These differ from stock python-docx because the SDK is asset-backed,
|
|
82
82
|
not file-backed. Each is documented in the relevant docstring.
|
|
83
83
|
|
|
84
|
-
-
|
|
84
|
+
- **Built-in table style friendly names resolve to OOXML styleIds.**
|
|
85
|
+
Word's ``styles.xml`` ships built-in table styles whose styleIds
|
|
86
|
+
(``LightGrid-Accent1``, ``MediumShading1-Accent2``, …) differ from
|
|
87
|
+
their display names (``"Light Grid Accent 1"``, …). Stock
|
|
88
|
+
python-docx resolves the friendly form against the loaded
|
|
89
|
+
document's styles collection at write time. We can't read
|
|
90
|
+
``styles.xml`` over HTTP, so ``Document.add_table(style=...)`` /
|
|
91
|
+
``Table.style`` setter consult an in-process map
|
|
92
|
+
(``docx._table_styles.BUILTIN_TABLE_STYLE_IDS``) covering every
|
|
93
|
+
Word 2007 and Word 2013+ built-in table style (204 entries) before
|
|
94
|
+
the value reaches ``<w:tblStyle w:val="..."/>``. Without this
|
|
95
|
+
mapping Word can't resolve the friendly name in ``styles.xml``
|
|
96
|
+
and silently falls back to ``Normal Table`` (no borders, no
|
|
97
|
+
header fill, no banding). Custom user-defined style names pass
|
|
98
|
+
through unchanged. Whether Word actually renders a Word 2013+
|
|
99
|
+
style after resolution also depends on SuperDoc's exported
|
|
100
|
+
``styles.xml`` containing the matching ``<w:style w:type="table">``
|
|
101
|
+
definition — at SuperDoc 1.8.1 the exported ``styles.xml`` only
|
|
102
|
+
carries the Word 2007 set, so Word 2013+ styleIds resolve in the
|
|
103
|
+
``<w:tblStyle>`` element but still fall back to ``Normal Table``
|
|
104
|
+
until SuperDoc upstream ships an expanded ``styles.xml``.
|
|
105
|
+
|
|
106
|
+
- **`Document.create(name=, base_url=, api_key=, parent_folder_id=, workspace_id=, docx=, agora_base_url=)`**
|
|
85
107
|
classmethod. Stock python-docx returns a blank in-memory document
|
|
86
108
|
for `Document(None)`; we can't fabricate a SuperDocument asset
|
|
87
109
|
client-side, so net-new asset creation is a separate factory that
|
|
88
110
|
hits `POST {base_url}/docs/empty`. The constructor positional-arg
|
|
89
111
|
shape (`Document(asset_id)`) is preserved for parity.
|
|
112
|
+
- **`docx=path`** mirrors the upstream `Document(docx="template.docx")`
|
|
113
|
+
constructor kwarg: when provided, the local `.docx` is uploaded to
|
|
114
|
+
agora's `/api/super-docs/create-from-upload` and used as the seed
|
|
115
|
+
for the new SuperDocument. The bytes are mirrored to S3 server-side
|
|
116
|
+
so subsequent template-asset reuse is fast. Without `docx=`, the
|
|
117
|
+
default Athena template is applied as today.
|
|
118
|
+
- **`agora_base_url=`** is a transport-detail kwarg only required when
|
|
119
|
+
the SDK can't derive the agora host from `base_url` (or from
|
|
120
|
+
`$ATHENA_AGORA_BASE_URL`). The default substitution rule replaces
|
|
121
|
+
`docx-studio` with `agora` in the docx-studio host; explicit
|
|
122
|
+
`agora_base_url` is required for local development or unusual
|
|
123
|
+
deployment topologies.
|
|
90
124
|
|
|
91
125
|
- **`Document.asset_id: str`** read-only property — the Athena asset
|
|
92
126
|
id this Document is bound to (`asset_<uuid>`). Agent-callable so
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.10.
|
|
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)
|
|
@@ -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)
|
|
@@ -174,12 +174,21 @@ class Document:
|
|
|
174
174
|
user_name: str | None = None,
|
|
175
175
|
user_email: str | None = None,
|
|
176
176
|
track_revisions: bool = False,
|
|
177
|
+
docx: str | None = None,
|
|
178
|
+
agora_base_url: str | None = None,
|
|
177
179
|
) -> "Document":
|
|
178
180
|
"""Create a new SuperDocument asset and return an opened Document.
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
provided Athena API key, then returns a Document bound to
|
|
182
|
-
new asset.
|
|
182
|
+
By default this hits ``POST {base_url}/docs/empty`` on docx-studio
|
|
183
|
+
with the provided Athena API key, then returns a Document bound to
|
|
184
|
+
the new asset.
|
|
185
|
+
|
|
186
|
+
When ``docx`` is provided, the local ``.docx`` file is uploaded as
|
|
187
|
+
the seed instead of starting from the bundled default template.
|
|
188
|
+
The upload goes through agora's ``/api/super-docs/create-from-upload``
|
|
189
|
+
endpoint (which seeds the new SuperDoc Y.Doc via SuperDoc SDK).
|
|
190
|
+
Mirrors stock python-docx's ``Document(docx=...)`` constructor
|
|
191
|
+
kwarg.
|
|
183
192
|
|
|
184
193
|
Args:
|
|
185
194
|
name: Display title for the new document. Defaults to the
|
|
@@ -192,6 +201,14 @@ class Document:
|
|
|
192
201
|
workspace root.
|
|
193
202
|
workspace_id: Optional workspace UUID; defaults to the
|
|
194
203
|
caller's current workspace as resolved by Agora.
|
|
204
|
+
docx: Optional path to a local ``.docx`` file. When provided,
|
|
205
|
+
the file is uploaded and used as the seed for the new
|
|
206
|
+
document. Mirrors python-docx's
|
|
207
|
+
``Document(docx="template.docx")`` pattern.
|
|
208
|
+
agora_base_url: Agora base URL used by the upload path. Falls
|
|
209
|
+
back to ``$ATHENA_AGORA_BASE_URL`` and finally to a
|
|
210
|
+
``docx-studio`` → ``agora`` substitution against
|
|
211
|
+
``base_url``. Ignored when ``docx`` is not set.
|
|
195
212
|
|
|
196
213
|
Returns:
|
|
197
214
|
A Document bound to the newly-created asset.
|
|
@@ -204,10 +221,12 @@ class Document:
|
|
|
204
221
|
DEVIATION FROM python-docx: stock python-docx returns a blank
|
|
205
222
|
in-memory document for ``Document(None)``. Our SDK is backed by
|
|
206
223
|
an Athena asset, so we expose a separate ``create()`` factory
|
|
207
|
-
rather than overloading the constructor.
|
|
224
|
+
rather than overloading the constructor. The ``docx=`` kwarg
|
|
225
|
+
mirrors the upstream constructor kwarg on this factory instead.
|
|
208
226
|
"""
|
|
209
227
|
from docx import _ptc
|
|
210
|
-
from docx._http import create_empty_document
|
|
228
|
+
from docx._http import create_empty_document, upload_document
|
|
229
|
+
from docx.errors import DocxError
|
|
211
230
|
|
|
212
231
|
# PTC: surface asset creation as its own top-level sub-tool-card.
|
|
213
232
|
# The asset_id isn't known until create_empty_document returns,
|
|
@@ -215,13 +234,37 @@ class Document:
|
|
|
215
234
|
# asset_id into the end event's result.
|
|
216
235
|
call_id = _ptc.emit_begin("CreateDocument", {"name": name})
|
|
217
236
|
try:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
workspace_id
|
|
224
|
-
|
|
237
|
+
if docx is not None:
|
|
238
|
+
# Fail-fast: the agora upload endpoint that backs the
|
|
239
|
+
# ``docx=`` path doesn't accept a workspace_id parameter
|
|
240
|
+
# (it always lands in the caller's current workspace).
|
|
241
|
+
# Silently dropping it would be a contract violation
|
|
242
|
+
# vs. the non-``docx`` path which honors workspace_id.
|
|
243
|
+
if workspace_id is not None:
|
|
244
|
+
raise DocxError(
|
|
245
|
+
"workspace_id is not supported when docx= is provided. "
|
|
246
|
+
"Omit workspace_id (the uploaded template lands in the "
|
|
247
|
+
"caller's current workspace), or use Document.create() "
|
|
248
|
+
"without docx= and add content via the SDK afterwards.",
|
|
249
|
+
)
|
|
250
|
+
upload_result = upload_document(
|
|
251
|
+
docx_path=docx,
|
|
252
|
+
docx_base_url=base_url,
|
|
253
|
+
agora_base_url=agora_base_url,
|
|
254
|
+
api_key=api_key,
|
|
255
|
+
name=name,
|
|
256
|
+
parent_folder_id=parent_folder_id,
|
|
257
|
+
)
|
|
258
|
+
asset_id = upload_result["asset_id"]
|
|
259
|
+
else:
|
|
260
|
+
result = create_empty_document(
|
|
261
|
+
base_url=base_url,
|
|
262
|
+
api_key=api_key,
|
|
263
|
+
name=name,
|
|
264
|
+
parent_folder_id=parent_folder_id,
|
|
265
|
+
workspace_id=workspace_id,
|
|
266
|
+
)
|
|
267
|
+
asset_id = result["asset_id"]
|
|
225
268
|
except Exception as exc:
|
|
226
269
|
_ptc.emit_end(
|
|
227
270
|
call_id=call_id,
|
|
@@ -233,11 +276,11 @@ class Document:
|
|
|
233
276
|
_ptc.emit_end(
|
|
234
277
|
call_id=call_id,
|
|
235
278
|
tool_name="CreateDocument",
|
|
236
|
-
result={"ok": True, "assetId":
|
|
279
|
+
result={"ok": True, "assetId": asset_id},
|
|
237
280
|
is_error=False,
|
|
238
281
|
)
|
|
239
282
|
return cls(
|
|
240
|
-
asset_id=
|
|
283
|
+
asset_id=asset_id,
|
|
241
284
|
http_base_url=base_url,
|
|
242
285
|
http_api_key=api_key,
|
|
243
286
|
user_name=user_name,
|
|
@@ -933,12 +976,13 @@ class Document:
|
|
|
933
976
|
node_id: str = client_node_id
|
|
934
977
|
|
|
935
978
|
if style:
|
|
979
|
+
from docx._table_styles import resolve_table_style_id
|
|
936
980
|
from docx.styles.style import BaseStyle
|
|
937
981
|
|
|
938
982
|
style_id = style.style_id if isinstance(style, BaseStyle) else str(style)
|
|
939
983
|
run_sync(
|
|
940
984
|
self._session.doc.tables.set_style(
|
|
941
|
-
{"nodeId": node_id, "styleId": style_id},
|
|
985
|
+
{"nodeId": node_id, "styleId": resolve_table_style_id(style_id)},
|
|
942
986
|
),
|
|
943
987
|
)
|
|
944
988
|
|
|
@@ -487,10 +487,13 @@ class Table:
|
|
|
487
487
|
|
|
488
488
|
@style.setter
|
|
489
489
|
def style(self, value: str | None) -> None:
|
|
490
|
+
from docx._table_styles import resolve_table_style_id
|
|
491
|
+
|
|
490
492
|
nid: str = self._fresh_node_id()
|
|
493
|
+
style_id = resolve_table_style_id(value) if value else "TableGrid"
|
|
491
494
|
run_sync(
|
|
492
495
|
self._session.doc.tables.set_style(
|
|
493
|
-
{"nodeId": nid, "styleId":
|
|
496
|
+
{"nodeId": nid, "styleId": style_id},
|
|
494
497
|
),
|
|
495
498
|
)
|
|
496
499
|
|