athena-python-docx 0.20.0__tar.gz → 0.21.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/CLAUDE.md +41 -17
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/DOCX_EXEC_LAB.md +4 -4
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/PKG-INFO +9 -9
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/README.md +6 -7
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/__init__.py +1 -1
- athena_python_docx-0.21.0/docx/_citations.py +74 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_http.py +122 -91
- athena_python_docx-0.21.0/docx/_references.py +91 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/api.py +6 -6
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/client.py +1 -8
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/commands.py +35 -2
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/document.py +68 -26
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/errors.py +3 -3
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/styles/styles.py +43 -13
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/table.py +50 -82
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/paragraph.py +58 -1
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/pyproject.toml +6 -5
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/docx_exec_lab.py +1 -1
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/round_trip_smoke.py +6 -7
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/smoke_test_block_not_found.py +2 -11
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/validate_find_replace_asset.py +1 -1
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/METHODOLOGY.md +9 -9
- athena_python_docx-0.21.0/tests/fidelity/README.md +50 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/fake_session.py +32 -33
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/firm_templates/README.md +1 -1
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/intentional_deviations.json +2 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_athena_extensions_registry.py +5 -0
- athena_python_docx-0.21.0/tests/test_citations.py +151 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_document_create.py +5 -11
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_document_create_from_template.py +167 -52
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_document_factory_validation.py +2 -2
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_docx_exec_lab.py +1 -1
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_smoke_integration.py +4 -5
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/uv.lock +12 -1
- athena_python_docx-0.20.0/tests/fidelity/README.md +0 -71
- athena_python_docx-0.20.0/tests/fidelity/runner.py +0 -486
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/.gitignore +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_http_doc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_postproc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/_timeouts.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/charts.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/comments.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/enum/shape.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/fields.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/math.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/section.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/session.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/settings.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/shape.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/shared.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/toc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/docx/typing.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/dump_wire_fixtures.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/publish.sh +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_image_url_data_uri.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_positional_cell_id.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.20.0 → athena_python_docx-0.21.0}/tests/test_zod_wire_contract.py +0 -0
|
@@ -67,11 +67,7 @@ These standard python-docx members don't apply to a Superdoc-backed SDK:
|
|
|
67
67
|
### Phase-3b upstream-blocked surface
|
|
68
68
|
|
|
69
69
|
These are *shipped at the surface* but partially or fully no-op at
|
|
70
|
-
runtime, pending
|
|
71
|
-
upstream constraints have been verified against
|
|
72
|
-
[SuperDoc's published docs](https://docs.superdoc.dev/document-api/reference/styles/apply)
|
|
73
|
-
and the bundled TypeScript types in
|
|
74
|
-
`node_modules/@superdoc-dev/sdk/dist/generated/client.d.ts`. See
|
|
70
|
+
runtime, pending DOCX Studio backend changes. See
|
|
75
71
|
`docx-studio/PYTHON_DOCX_PARITY_GAPS.md` § *What unblocks the
|
|
76
72
|
remaining work* for the exact wire-op shape needed to unblock each.
|
|
77
73
|
|
|
@@ -132,16 +128,15 @@ not file-backed. Each is documented in the relevant docstring.
|
|
|
132
128
|
|
|
133
129
|
- **`Document.create(name=, base_url=, api_key=, parent_folder_id=, workspace_id=, docx=, agora_base_url=)`**
|
|
134
130
|
classmethod. Stock python-docx returns a blank in-memory document
|
|
135
|
-
for `Document(None)`; we can't fabricate a
|
|
131
|
+
for `Document(None)`; we can't fabricate a Word document asset
|
|
136
132
|
client-side, so net-new asset creation is a separate factory that
|
|
137
133
|
hits `POST {base_url}/docs/empty`. The constructor positional-arg
|
|
138
134
|
shape (`Document(asset_id)`) is preserved for parity.
|
|
139
135
|
- **`docx=path`** mirrors the upstream `Document(docx="template.docx")`
|
|
140
|
-
constructor kwarg: when provided, the
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
default Athena template is applied as today.
|
|
136
|
+
constructor kwarg: when provided, the SDK creates a normal Word
|
|
137
|
+
document asset through Agora's docx-studio bridge, then imports the
|
|
138
|
+
local `.docx` into that asset. Without `docx=`, the default Athena
|
|
139
|
+
template is applied as today.
|
|
145
140
|
- **`agora_base_url=`** is a transport-detail kwarg only required when
|
|
146
141
|
the SDK can't derive the agora host from `base_url` (or from
|
|
147
142
|
`$ATHENA_AGORA_BASE_URL`). The default substitution rule replaces
|
|
@@ -389,6 +384,38 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
389
384
|
cleanup loops, which left horizontal-rule and scratch-content artifacts
|
|
390
385
|
in the document during failed-run recoveries.
|
|
391
386
|
|
|
387
|
+
- **`Paragraph.add_citation(source, *, anchor=None, excerpt=None,
|
|
388
|
+
display_value=None) -> str`** + **`Paragraph.clear_citations()`** +
|
|
389
|
+
**`Document.add_citation(source, *, destination_anchor, anchor=None,
|
|
390
|
+
...)`** + **`Document.remove_citation(citation_id)`** (0.20.0+) —
|
|
391
|
+
SDK-native Athena citations. This is the programmatic equivalent of
|
|
392
|
+
the user selecting DOCX text and choosing "Use selection as citation
|
|
393
|
+
destination/source" in Olympus: it writes the canonical document
|
|
394
|
+
citation record into the document's ``citations`` Y.Array and applies
|
|
395
|
+
the citation-blue highlight to the destination paragraph/range. It is
|
|
396
|
+
not a live link and does not overwrite destination text.
|
|
397
|
+
|
|
398
|
+
Common agent pattern after writing a supported claim:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
from docx import Document
|
|
402
|
+
from docx._references import AssetReference, SheetRangeAnchor
|
|
403
|
+
|
|
404
|
+
doc = Document("asset_word")
|
|
405
|
+
p = doc.add_paragraph("The combined assessment identified 20,049 total vulnerabilities.")
|
|
406
|
+
citation_id = p.add_citation(
|
|
407
|
+
AssetReference(id="asset_spreadsheet"),
|
|
408
|
+
anchor=SheetRangeAnchor(sheet_id=0, range="B2:B8"),
|
|
409
|
+
excerpt="20,049 total vulnerabilities",
|
|
410
|
+
display_value="Vulnerability total",
|
|
411
|
+
)
|
|
412
|
+
doc.save()
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Use ``Document.add_citation(..., destination_anchor=PmNodeAnchor(...))``
|
|
416
|
+
only when you already have a DOCX editor anchor id. For normal writing,
|
|
417
|
+
cite the ``Paragraph`` proxy you just created or found.
|
|
418
|
+
|
|
392
419
|
- **`Document.export_docx(path=None, *, include_revisions=False)
|
|
393
420
|
-> bytes`** (0.11.4+) — export the document's current state as
|
|
394
421
|
``.docx`` bytes, optionally writing to ``path``. Closes the gap
|
|
@@ -397,16 +424,13 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
397
424
|
harnesses and CI workflows need a programmatic .docx export to
|
|
398
425
|
diff against ``python-docx`` output that doesn't require a
|
|
399
426
|
human-driven Olympus UI action. Routes through ``ExportDocx`` →
|
|
400
|
-
apps/api
|
|
401
|
-
recommended backend export path per the SuperDoc team — Document
|
|
402
|
-
API, not the browser-side ``editor.exportDocx`` shim). apps/api
|
|
427
|
+
apps/api's backend export path. apps/api
|
|
403
428
|
writes the session state to a temp ``.docx``, reads bytes back,
|
|
404
429
|
base64-encodes for the wire, and unlinks the temp in a
|
|
405
430
|
``finally`` block. The SDK decodes and writes to ``path``.
|
|
406
431
|
``include_revisions=True`` raises ``DocxError`` pre-flight:
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
through ``doc.save({out})``.
|
|
432
|
+
the current backend export path has no ``includeRevisions`` support,
|
|
433
|
+
so honoring the flag isn't possible yet.
|
|
410
434
|
|
|
411
435
|
Template seeding: ``Document.create()`` (no ``docx=``) already
|
|
412
436
|
produces an exportable asset because ``POST /docs/empty`` seeds
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DocX Execution Lab
|
|
2
2
|
|
|
3
3
|
`scripts/docx_exec_lab.py` is a headless runner for quickly testing
|
|
4
|
-
`athena-python-docx` snippets against Athena
|
|
4
|
+
`athena-python-docx` snippets against Athena Word document assets. It
|
|
5
5
|
shortens the debug loop by running local SDK code directly against a
|
|
6
6
|
chosen DOCX Studio API, capturing stdout/stderr/errors/timings, and
|
|
7
7
|
optionally exporting the result to `.docx` for assertions.
|
|
@@ -85,10 +85,10 @@ uv run python scripts/docx_exec_lab.py \
|
|
|
85
85
|
--export-docx
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
## Local UI With
|
|
88
|
+
## Local UI With DOCX Editor Embed
|
|
89
89
|
|
|
90
90
|
The lab also includes a local-only HTML UI that wraps the same headless
|
|
91
|
-
runner and iframes Olympus's existing
|
|
91
|
+
runner and iframes Olympus's existing DOCX editor embed route.
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
cd docx-studio/python-sdk
|
|
@@ -107,7 +107,7 @@ Defaults:
|
|
|
107
107
|
The UI accepts an `asset_id`, Python code, and optional assertions. The
|
|
108
108
|
right pane can mint an editable embed token through staging Agora and
|
|
109
109
|
iframe the returned `https://staging-app.athenaintel.com/embed/{token}`
|
|
110
|
-
URL, so changes render in the real Olympus
|
|
110
|
+
URL, so changes render in the real Olympus DOCX editor viewer.
|
|
111
111
|
|
|
112
112
|
If the staging Agora URL differs, pass:
|
|
113
113
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Drop-in replacement for python-docx that connects to Athena's
|
|
3
|
+
Version: 0.21.0
|
|
4
|
+
Summary: Drop-in replacement for python-docx that connects to Athena's DOCX Studio collaborative document stack
|
|
5
5
|
Project-URL: Homepage, https://athenaintelligence.ai
|
|
6
6
|
Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
|
|
7
7
|
License-Expression: MIT
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: athena-references>=0.1.0
|
|
14
15
|
Requires-Dist: requests>=2.28
|
|
15
16
|
Requires-Dist: urllib3>=1.26.6
|
|
16
17
|
Provides-Extra: dev
|
|
@@ -27,7 +28,7 @@ Description-Content-Type: text/markdown
|
|
|
27
28
|
|
|
28
29
|
# athena-python-docx
|
|
29
30
|
|
|
30
|
-
Drop-in replacement for [python-docx](https://python-docx.readthedocs.io/) that connects to Athena's
|
|
31
|
+
Drop-in replacement for [python-docx](https://python-docx.readthedocs.io/) that connects to Athena's Keryx-backed Word document stack.
|
|
31
32
|
|
|
32
33
|
## Quick start
|
|
33
34
|
|
|
@@ -35,7 +36,7 @@ Drop-in replacement for [python-docx](https://python-docx.readthedocs.io/) that
|
|
|
35
36
|
from docx import Document
|
|
36
37
|
from docx.shared import Inches, Pt, RGBColor
|
|
37
38
|
|
|
38
|
-
# Open an existing
|
|
39
|
+
# Open an existing Word document asset by its Athena asset_id
|
|
39
40
|
with Document("asset_abc123") as doc:
|
|
40
41
|
doc.add_heading("Market Analysis", level=1)
|
|
41
42
|
|
|
@@ -74,15 +75,14 @@ headless runner in `scripts/docx_exec_lab.py`. It executes an arbitrary
|
|
|
74
75
|
Python snippet with this checkout of `athena-python-docx`, captures
|
|
75
76
|
stdout/stderr/errors/timings, and can export the resulting `.docx` for
|
|
76
77
|
text assertions. `scripts/docx_exec_lab_server.py` adds a local UI with
|
|
77
|
-
an Olympus
|
|
78
|
+
an Olympus Word document embed preview pane. See `DOCX_EXEC_LAB.md`.
|
|
78
79
|
|
|
79
80
|
## Environment variables
|
|
80
81
|
|
|
81
|
-
Required when connecting to
|
|
82
|
+
Required when connecting to DOCX Studio (set by Athena backend when executing in Daytona):
|
|
82
83
|
|
|
83
|
-
- `
|
|
84
|
-
- `
|
|
85
|
-
- `ATHENA_WORKSPACE_ID` — workspace routing segment
|
|
84
|
+
- `ATHENA_DOCX_BASE_URL` — DOCX Studio API base URL
|
|
85
|
+
- `ATHENA_DOCX_API_KEY` — Athena API key or service-issued DOCX Studio token
|
|
86
86
|
|
|
87
87
|
## License
|
|
88
88
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# athena-python-docx
|
|
2
2
|
|
|
3
|
-
Drop-in replacement for [python-docx](https://python-docx.readthedocs.io/) that connects to Athena's
|
|
3
|
+
Drop-in replacement for [python-docx](https://python-docx.readthedocs.io/) that connects to Athena's Keryx-backed Word document stack.
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ Drop-in replacement for [python-docx](https://python-docx.readthedocs.io/) that
|
|
|
8
8
|
from docx import Document
|
|
9
9
|
from docx.shared import Inches, Pt, RGBColor
|
|
10
10
|
|
|
11
|
-
# Open an existing
|
|
11
|
+
# Open an existing Word document asset by its Athena asset_id
|
|
12
12
|
with Document("asset_abc123") as doc:
|
|
13
13
|
doc.add_heading("Market Analysis", level=1)
|
|
14
14
|
|
|
@@ -47,15 +47,14 @@ headless runner in `scripts/docx_exec_lab.py`. It executes an arbitrary
|
|
|
47
47
|
Python snippet with this checkout of `athena-python-docx`, captures
|
|
48
48
|
stdout/stderr/errors/timings, and can export the resulting `.docx` for
|
|
49
49
|
text assertions. `scripts/docx_exec_lab_server.py` adds a local UI with
|
|
50
|
-
an Olympus
|
|
50
|
+
an Olympus Word document embed preview pane. See `DOCX_EXEC_LAB.md`.
|
|
51
51
|
|
|
52
52
|
## Environment variables
|
|
53
53
|
|
|
54
|
-
Required when connecting to
|
|
54
|
+
Required when connecting to DOCX Studio (set by Athena backend when executing in Daytona):
|
|
55
55
|
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
58
|
-
- `ATHENA_WORKSPACE_ID` — workspace routing segment
|
|
56
|
+
- `ATHENA_DOCX_BASE_URL` — DOCX Studio API base URL
|
|
57
|
+
- `ATHENA_DOCX_API_KEY` — Athena API key or service-issued DOCX Studio token
|
|
59
58
|
|
|
60
59
|
## License
|
|
61
60
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Helpers for SDK-native Athena citations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from docx._references import AssetReference
|
|
9
|
+
from docx.commands import AddCitation
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def new_citation_id() -> str:
|
|
13
|
+
return f"citation_{uuid.uuid4().hex[:12]}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def source_ref_to_dict(source: AssetReference) -> dict[str, Any]:
|
|
17
|
+
if not isinstance(source, AssetReference):
|
|
18
|
+
raise TypeError(f"source must be an AssetReference; got {type(source).__name__}")
|
|
19
|
+
return source.to_dict()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def anchor_to_dict(anchor: object | None) -> dict[str, Any] | None:
|
|
23
|
+
if anchor is None:
|
|
24
|
+
return None
|
|
25
|
+
if not hasattr(anchor, "to_dict"):
|
|
26
|
+
raise TypeError(
|
|
27
|
+
"anchor must be an Anchor dataclass (SheetRangeAnchor, "
|
|
28
|
+
f"PmNodeAnchor, etc.) — got {type(anchor).__name__}"
|
|
29
|
+
)
|
|
30
|
+
value = anchor.to_dict()
|
|
31
|
+
if not isinstance(value, dict) or not value.get("type"):
|
|
32
|
+
raise TypeError("anchor.to_dict() must return a dict with a 'type' field")
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def destination_anchor_to_dict(destination_anchor: object) -> dict[str, Any]:
|
|
37
|
+
if hasattr(destination_anchor, "to_dict"):
|
|
38
|
+
destination_anchor = destination_anchor.to_dict()
|
|
39
|
+
if not isinstance(destination_anchor, dict):
|
|
40
|
+
raise TypeError(
|
|
41
|
+
"destination_anchor must be a PmNodeAnchor, PmNodeRangeAnchor, or wire dict"
|
|
42
|
+
)
|
|
43
|
+
anchor_type = destination_anchor.get("type")
|
|
44
|
+
if anchor_type == "pm_node" and destination_anchor.get("nodeId"):
|
|
45
|
+
return dict(destination_anchor)
|
|
46
|
+
if (
|
|
47
|
+
anchor_type == "pm_node_range"
|
|
48
|
+
and destination_anchor.get("startNodeId")
|
|
49
|
+
and destination_anchor.get("endNodeId")
|
|
50
|
+
):
|
|
51
|
+
return dict(destination_anchor)
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"destination_anchor must be {'type':'pm_node','nodeId':...} or "
|
|
54
|
+
"{'type':'pm_node_range','startNodeId':...,'endNodeId':...}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_add_citation_command(
|
|
59
|
+
*,
|
|
60
|
+
destination_anchor: object,
|
|
61
|
+
source: AssetReference,
|
|
62
|
+
anchor: object | None,
|
|
63
|
+
excerpt: str | None,
|
|
64
|
+
display_value: str | None,
|
|
65
|
+
) -> tuple[str, AddCitation]:
|
|
66
|
+
citation_id = new_citation_id()
|
|
67
|
+
return citation_id, AddCitation(
|
|
68
|
+
destination_anchor=destination_anchor_to_dict(destination_anchor),
|
|
69
|
+
source_ref=source_ref_to_dict(source),
|
|
70
|
+
source_anchor=anchor_to_dict(anchor),
|
|
71
|
+
citation_id=citation_id,
|
|
72
|
+
excerpt=excerpt,
|
|
73
|
+
display_value=display_value,
|
|
74
|
+
)
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"""HTTP bootstrap for Document.create().
|
|
2
2
|
|
|
3
|
-
The SDK's
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
provision a new SuperDocument asset and receive a collab bundle to open
|
|
7
|
-
the document with.
|
|
3
|
+
The SDK's transport is HTTP via docx-studio. This module keeps the
|
|
4
|
+
bootstrap and upload calls on urllib so ``Document.create()`` can provision
|
|
5
|
+
Word document assets without pulling in extra runtime dependencies.
|
|
8
6
|
|
|
9
7
|
We use ``urllib`` from stdlib to avoid pulling ``httpx`` / ``requests``
|
|
10
8
|
into the runtime — this code runs in Daytona sandboxes where every MB
|
|
@@ -15,6 +13,7 @@ from __future__ import annotations
|
|
|
15
13
|
|
|
16
14
|
import json
|
|
17
15
|
import os
|
|
16
|
+
import urllib.parse
|
|
18
17
|
import urllib.error
|
|
19
18
|
import urllib.request
|
|
20
19
|
from typing import TypedDict
|
|
@@ -26,26 +25,12 @@ from docx.errors import (
|
|
|
26
25
|
)
|
|
27
26
|
|
|
28
27
|
|
|
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
28
|
class CreateAssetResult(TypedDict):
|
|
43
29
|
"""Parsed response from ``POST /docs/empty``."""
|
|
44
30
|
|
|
45
31
|
asset_id: str
|
|
46
32
|
name: str
|
|
47
33
|
workspace_id: str
|
|
48
|
-
collab: _CollabBundle
|
|
49
34
|
|
|
50
35
|
|
|
51
36
|
_BASE_URL_ENV = "ATHENA_DOCX_BASE_URL"
|
|
@@ -67,6 +52,37 @@ def _derive_agora_base_url(docx_base_url: str) -> str | None:
|
|
|
67
52
|
return docx_base_url.replace("docx-studio", "agora")
|
|
68
53
|
|
|
69
54
|
|
|
55
|
+
def _delete_created_asset_best_effort(
|
|
56
|
+
*,
|
|
57
|
+
agora_base_url: str,
|
|
58
|
+
asset_id: str,
|
|
59
|
+
api_key: str,
|
|
60
|
+
timeout: float,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Best-effort rollback for create-then-import failures."""
|
|
63
|
+
from docx import __version__
|
|
64
|
+
|
|
65
|
+
delete_url = (
|
|
66
|
+
agora_base_url.rstrip().rstrip("/")
|
|
67
|
+
+ "/api/docx-studio/assets/"
|
|
68
|
+
+ urllib.parse.quote(asset_id, safe="")
|
|
69
|
+
)
|
|
70
|
+
req = urllib.request.Request( # noqa: S310
|
|
71
|
+
delete_url,
|
|
72
|
+
method="DELETE",
|
|
73
|
+
headers={
|
|
74
|
+
"Authorization": f"Bearer {api_key}",
|
|
75
|
+
"Accept": "application/json",
|
|
76
|
+
"User-Agent": f"athena-python-docx/{__version__}",
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310
|
|
81
|
+
resp.read()
|
|
82
|
+
except Exception: # noqa: BLE001
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
70
86
|
def create_empty_document(
|
|
71
87
|
*,
|
|
72
88
|
base_url: str | None = None,
|
|
@@ -91,8 +107,7 @@ def create_empty_document(
|
|
|
91
107
|
timeout: Request timeout in seconds.
|
|
92
108
|
|
|
93
109
|
Returns:
|
|
94
|
-
Parsed response dict with the new asset_id
|
|
95
|
-
ready to feed into ``Session(..., bundle=...)``.
|
|
110
|
+
Parsed response dict with the new asset_id, display name, and workspace.
|
|
96
111
|
|
|
97
112
|
Raises:
|
|
98
113
|
SessionError: missing base_url or unparseable response.
|
|
@@ -182,42 +197,22 @@ def create_empty_document(
|
|
|
182
197
|
asset_id_obj = parsed.get("assetId")
|
|
183
198
|
name_obj = parsed.get("name")
|
|
184
199
|
ws_obj = parsed.get("workspaceId")
|
|
185
|
-
collab_obj = parsed.get("collab")
|
|
186
200
|
if not (
|
|
187
|
-
isinstance(asset_id_obj, str)
|
|
188
|
-
and isinstance(name_obj, str)
|
|
189
|
-
and isinstance(ws_obj, str)
|
|
190
|
-
and isinstance(collab_obj, dict)
|
|
201
|
+
isinstance(asset_id_obj, str) and isinstance(name_obj, str) and isinstance(ws_obj, str)
|
|
191
202
|
):
|
|
192
203
|
raise SessionError(
|
|
193
204
|
f"docx-studio response is missing required fields: {parsed!r}",
|
|
194
205
|
)
|
|
195
|
-
token_obj = collab_obj.get("token")
|
|
196
|
-
ws_url_obj = collab_obj.get("wsUrl")
|
|
197
|
-
bundle_ws_obj = collab_obj.get("workspaceId")
|
|
198
|
-
if not (
|
|
199
|
-
isinstance(token_obj, str)
|
|
200
|
-
and isinstance(ws_url_obj, str)
|
|
201
|
-
and isinstance(bundle_ws_obj, str)
|
|
202
|
-
):
|
|
203
|
-
raise SessionError(
|
|
204
|
-
f"docx-studio collab bundle is malformed: {collab_obj!r}",
|
|
205
|
-
)
|
|
206
206
|
|
|
207
207
|
return CreateAssetResult(
|
|
208
208
|
asset_id=asset_id_obj,
|
|
209
209
|
name=name_obj,
|
|
210
210
|
workspace_id=ws_obj,
|
|
211
|
-
collab=_CollabBundle(
|
|
212
|
-
SUPERDOC_COLLAB_TOKEN=token_obj,
|
|
213
|
-
KERYX_WS_URL=ws_url_obj,
|
|
214
|
-
ATHENA_WORKSPACE_ID=bundle_ws_obj,
|
|
215
|
-
),
|
|
216
211
|
)
|
|
217
212
|
|
|
218
213
|
|
|
219
214
|
class UploadAssetResult(TypedDict):
|
|
220
|
-
"""Parsed response from
|
|
215
|
+
"""Parsed response from the create-and-import upload flow."""
|
|
221
216
|
|
|
222
217
|
asset_id: str
|
|
223
218
|
name: str
|
|
@@ -231,14 +226,13 @@ def upload_document(
|
|
|
231
226
|
api_key: str | None = None,
|
|
232
227
|
name: str | None = None,
|
|
233
228
|
parent_folder_id: str | None = None,
|
|
229
|
+
workspace_id: str | None = None,
|
|
234
230
|
timeout: float = 120.0,
|
|
235
231
|
) -> UploadAssetResult:
|
|
236
|
-
"""Upload a local ``.docx`` to agora and create a new
|
|
232
|
+
"""Upload a local ``.docx`` to agora and create a new Word document asset.
|
|
237
233
|
|
|
238
|
-
The
|
|
239
|
-
|
|
240
|
-
server-side in agora. The SDK still talks to docx-studio for command
|
|
241
|
-
application; this function only handles the initial create.
|
|
234
|
+
The upload path creates a normal Word document asset through agora's
|
|
235
|
+
docx-studio bridge, then imports the local ``.docx`` into that asset.
|
|
242
236
|
|
|
243
237
|
Args:
|
|
244
238
|
docx_path: Filesystem path to the ``.docx`` to upload.
|
|
@@ -251,6 +245,8 @@ def upload_document(
|
|
|
251
245
|
api_key: Athena API key. Falls back to ``$ATHENA_DOCX_API_KEY``.
|
|
252
246
|
name: Optional asset title (defaults to the file's stem).
|
|
253
247
|
parent_folder_id: Optional parent folder for the new asset.
|
|
248
|
+
workspace_id: Optional workspace UUID; defaults to the caller's
|
|
249
|
+
current workspace as resolved by Agora.
|
|
254
250
|
timeout: Request timeout in seconds (uploads can be slow for big
|
|
255
251
|
decks/templates).
|
|
256
252
|
|
|
@@ -262,10 +258,8 @@ def upload_document(
|
|
|
262
258
|
AuthenticationError: missing api_key, or 401/403 from server.
|
|
263
259
|
DocxError: any other 4xx/5xx, or a malformed response.
|
|
264
260
|
"""
|
|
265
|
-
import
|
|
266
|
-
import mimetypes
|
|
261
|
+
import base64
|
|
267
262
|
import os
|
|
268
|
-
import uuid
|
|
269
263
|
|
|
270
264
|
resolved_docx_base: str | None = docx_base_url or os.environ.get(_BASE_URL_ENV)
|
|
271
265
|
# Create must authenticate as the REAL user so the new asset's ``created_by``
|
|
@@ -311,49 +305,25 @@ def upload_document(
|
|
|
311
305
|
base = os.path.basename(docx_path)
|
|
312
306
|
title = base[:-5] if base.lower().endswith(".docx") else base
|
|
313
307
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
from urllib.parse import urlencode
|
|
308
|
+
with open(docx_path, "rb") as f:
|
|
309
|
+
file_bytes: bytes = f.read()
|
|
317
310
|
|
|
318
|
-
|
|
311
|
+
create_body: dict[str, str] = {"name": title}
|
|
319
312
|
if parent_folder_id is not None:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
resolved_agora_base.rstrip("/") + "/api/super-docs/create-from-upload?" + urlencode(qs)
|
|
324
|
-
)
|
|
313
|
+
create_body["parent_folder_id"] = parent_folder_id
|
|
314
|
+
if workspace_id is not None:
|
|
315
|
+
create_body["workspace_id"] = workspace_id
|
|
325
316
|
|
|
326
|
-
|
|
327
|
-
# `requests` — keep the urllib-only dep profile.
|
|
328
|
-
with open(docx_path, "rb") as f:
|
|
329
|
-
file_bytes: bytes = f.read()
|
|
330
|
-
content_type: str = (
|
|
331
|
-
mimetypes.guess_type(docx_path)[0]
|
|
332
|
-
or "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
333
|
-
)
|
|
334
|
-
boundary: str = f"----athenaDocxBoundary{uuid.uuid4().hex}"
|
|
335
|
-
filename = os.path.basename(docx_path)
|
|
336
|
-
body_parts: list[bytes] = []
|
|
337
|
-
body_parts.append(f"--{boundary}\r\n".encode("utf-8"))
|
|
338
|
-
body_parts.append(
|
|
339
|
-
(
|
|
340
|
-
'Content-Disposition: form-data; name="file"; '
|
|
341
|
-
f'filename="{filename}"\r\n'
|
|
342
|
-
f"Content-Type: {content_type}\r\n\r\n"
|
|
343
|
-
).encode("utf-8"),
|
|
344
|
-
)
|
|
345
|
-
body_parts.append(file_bytes)
|
|
346
|
-
body_parts.append(f"\r\n--{boundary}--\r\n".encode("utf-8"))
|
|
347
|
-
body: bytes = b"".join(body_parts)
|
|
317
|
+
create_url: str = resolved_agora_base.rstrip("/") + "/api/docx-studio/assets"
|
|
348
318
|
|
|
349
319
|
from docx import __version__
|
|
350
320
|
|
|
351
321
|
req = urllib.request.Request( # noqa: S310
|
|
352
|
-
|
|
353
|
-
data=
|
|
322
|
+
create_url,
|
|
323
|
+
data=json.dumps(create_body).encode("utf-8"),
|
|
354
324
|
method="POST",
|
|
355
325
|
headers={
|
|
356
|
-
"Content-Type":
|
|
326
|
+
"Content-Type": "application/json",
|
|
357
327
|
"Authorization": f"Bearer {resolved_key}",
|
|
358
328
|
"Accept": "application/json",
|
|
359
329
|
"User-Agent": f"athena-python-docx/{__version__}",
|
|
@@ -374,15 +344,13 @@ def upload_document(
|
|
|
374
344
|
f"agora rejected the API key (HTTP {e.code}): {err_body}",
|
|
375
345
|
) from e
|
|
376
346
|
raise DocxError(
|
|
377
|
-
f"agora /api/
|
|
347
|
+
f"agora /api/docx-studio/assets returned HTTP {e.code}: {err_body}",
|
|
378
348
|
) from e
|
|
379
349
|
except urllib.error.URLError as e:
|
|
380
350
|
raise SessionError(
|
|
381
|
-
f"Unable to reach agora at {
|
|
351
|
+
f"Unable to reach agora at {create_url}: {e.reason}",
|
|
382
352
|
) from e
|
|
383
353
|
|
|
384
|
-
# Avoid unused import warning when io isn't otherwise referenced.
|
|
385
|
-
_ = io
|
|
386
354
|
try:
|
|
387
355
|
parsed: dict = json.loads(raw.decode("utf-8"))
|
|
388
356
|
except (UnicodeDecodeError, json.JSONDecodeError) as e:
|
|
@@ -390,11 +358,74 @@ def upload_document(
|
|
|
390
358
|
f"agora returned non-JSON response: {raw[:200]!r}",
|
|
391
359
|
) from e
|
|
392
360
|
|
|
393
|
-
asset_id_obj = parsed.get("
|
|
394
|
-
name_obj = parsed.get("
|
|
361
|
+
asset_id_obj = parsed.get("asset_id")
|
|
362
|
+
name_obj = parsed.get("name")
|
|
395
363
|
if not (isinstance(asset_id_obj, str) and isinstance(name_obj, str)):
|
|
396
364
|
raise SessionError(
|
|
397
365
|
f"agora response is missing required fields: {parsed!r}",
|
|
398
366
|
)
|
|
399
367
|
|
|
368
|
+
import_url: str = (
|
|
369
|
+
resolved_agora_base.rstrip("/")
|
|
370
|
+
+ "/api/docx-studio/assets/"
|
|
371
|
+
+ urllib.parse.quote(asset_id_obj, safe="")
|
|
372
|
+
+ "/import"
|
|
373
|
+
)
|
|
374
|
+
import_body = {
|
|
375
|
+
"docx_base64": base64.b64encode(file_bytes).decode("ascii"),
|
|
376
|
+
"overwrite": True,
|
|
377
|
+
}
|
|
378
|
+
import_req = urllib.request.Request( # noqa: S310
|
|
379
|
+
import_url,
|
|
380
|
+
data=json.dumps(import_body).encode("utf-8"),
|
|
381
|
+
method="POST",
|
|
382
|
+
headers={
|
|
383
|
+
"Content-Type": "application/json",
|
|
384
|
+
"Authorization": f"Bearer {resolved_key}",
|
|
385
|
+
"Accept": "application/json",
|
|
386
|
+
"User-Agent": f"athena-python-docx/{__version__}",
|
|
387
|
+
},
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
try:
|
|
392
|
+
with urllib.request.urlopen(import_req, timeout=timeout) as resp: # noqa: S310
|
|
393
|
+
import_raw: bytes = resp.read()
|
|
394
|
+
except urllib.error.HTTPError as e:
|
|
395
|
+
err_body = ""
|
|
396
|
+
try:
|
|
397
|
+
err_body = e.read().decode("utf-8", errors="replace")
|
|
398
|
+
except Exception: # noqa: BLE001
|
|
399
|
+
pass
|
|
400
|
+
if e.code in (401, 403):
|
|
401
|
+
raise AuthenticationError(
|
|
402
|
+
f"agora rejected the API key while importing DOCX (HTTP {e.code}): {err_body}",
|
|
403
|
+
) from e
|
|
404
|
+
raise DocxError(
|
|
405
|
+
f"agora /api/docx-studio/assets/{asset_id_obj}/import returned HTTP {e.code}: {err_body}",
|
|
406
|
+
) from e
|
|
407
|
+
except urllib.error.URLError as e:
|
|
408
|
+
raise SessionError(
|
|
409
|
+
f"Unable to reach agora at {import_url}: {e.reason}",
|
|
410
|
+
) from e
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
import_parsed: dict = json.loads(import_raw.decode("utf-8"))
|
|
414
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as e:
|
|
415
|
+
raise SessionError(
|
|
416
|
+
f"agora import returned non-JSON response: {import_raw[:200]!r}",
|
|
417
|
+
) from e
|
|
418
|
+
if import_parsed.get("asset_id") != asset_id_obj or import_parsed.get("seeded") is not True:
|
|
419
|
+
raise SessionError(
|
|
420
|
+
f"agora import response is missing required fields: {import_parsed!r}",
|
|
421
|
+
)
|
|
422
|
+
except Exception:
|
|
423
|
+
_delete_created_asset_best_effort(
|
|
424
|
+
agora_base_url=resolved_agora_base,
|
|
425
|
+
asset_id=asset_id_obj,
|
|
426
|
+
api_key=resolved_key,
|
|
427
|
+
timeout=timeout,
|
|
428
|
+
)
|
|
429
|
+
raise
|
|
430
|
+
|
|
400
431
|
return UploadAssetResult(asset_id=asset_id_obj, name=name_obj)
|