athena-python-docx 0.43.0__tar.gz → 0.45.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.43.0 → athena_python_docx-0.45.0}/PKG-INFO +8 -1
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/README.md +7 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/__init__.py +1 -1
- athena_python_docx-0.45.0/docx/_asset_token.py +379 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_http.py +114 -2
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_http_doc.py +139 -33
- athena_python_docx-0.45.0/docx/_public_create.py +270 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/client.py +12 -9
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/document.py +5 -1
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/pyproject.toml +1 -1
- athena_python_docx-0.45.0/tests/test_asset_tokens.py +483 -0
- athena_python_docx-0.45.0/tests/test_public_create.py +473 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/uv.lock +1 -1
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/.gitignore +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/AGENTS.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/CLAUDE.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/DOCX_EXEC_LAB.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_capabilities.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_citations.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_postproc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_proxy.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_references.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_run_order.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_timeouts.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_tracked.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/_xml_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/api.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/blkcntnr.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/charts.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/commands.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/comments.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/drawing/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/base.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/dml.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/errors.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/fields.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/image/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/image/exceptions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/image/image.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/math.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/session.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/settings.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/shared.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/styles/_table.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/styles/latent.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/table.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/toc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/docx/typing.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/dump_wire_fixtures.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/agent_documents.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/default_style_run_formatting.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/header_footer_pictures.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/less_used_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/merged_cell_identity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/paragraph_text_setter.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/picture_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/proxy_equality.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/return_types.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/xml_incompatible_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/zero_and_negative_indents.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw16_header_footer_tables.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw17_header_letterhead_filled.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw18_header_table_properties.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw19_header_table_rows_columns.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw20_header_table_merges.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fixtures/field_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fixtures/note_reference_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fixtures/ruby_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/fixtures/symbol_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/baseline.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/fixture_scripts.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/reader.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/sdk_driver.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/stock_driver.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/live/test_live_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_add_section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_alignment_values.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_athena_extension_creates_session_open.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_athena_extensions_registry.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_batch_chunking.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_battery_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_borders_spec.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_fastpath_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_merge.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_nested_tables.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_paragraphs_listed.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_citations.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_color_format_theme.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_comment_paragraphs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_comments_first_read.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_content_control_value.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_core_properties.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_create_workspace.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_document_settings_session_open.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_drawing_image.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_empty_hyperlinks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_enum_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_export_docx_clean.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_export_rescue_anchors.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_field_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_field_refresh.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_field_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_flushed_write_failures.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_image_url_data_uri.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_inline_page_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_inline_shape_alt_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_inline_shape_size.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_inline_shape_type.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_inline_shapes_floating.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_latent_style_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_line_clear_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_listed_paragraph_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_literal_no_break_hyphens.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_literal_soft_hyphens.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_merged_cell_identity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_missing_modules.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_nested_table_refused.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_note_reference_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_page_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_paragraph_delete.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_paragraph_indent_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_paragraph_runs_hyperlinks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_paragraph_text_empty_run.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_parfmt_clears.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_pdf_export_and_page_renders.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_positional_cell_id.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_proxy_equality.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_read_run_ranges.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_rendered_page_breaks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_repeated_node_read.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_restart_numbering.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_round_trip_smoke_script.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_ruby_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_add_picture_anchor.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_break_inner_content.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_edges.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_formatting_wire.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_identity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_later_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_mark_inheritance.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_page_number_blocks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_table_style_objects.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_text_python_docx.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_text_refreshes_paragraph.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_run_unmodeled_toggles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_sdk_reads_served.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_section_read_lengths.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_sections_empty_list.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_delete.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_formatting_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_metadata_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_names.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_resolution.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_tab_stops.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_style_tab_stops_read.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_symbol_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_tab_stop_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_add_row_grid_row.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_anchors.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_clears.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_direction.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_length_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_listing_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_read_reuse.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_text_control_characters.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_tracked_revisions_offsets.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_unmapped_alignment_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_uv_lock_version.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_versioned_read_reuse.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_xml_incompatible_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.45.0}/tests/test_zod_wire_contract.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.45.0
|
|
4
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>
|
|
@@ -81,6 +81,13 @@ Required when connecting to DOCX Studio (set by the Athena backend in the `execu
|
|
|
81
81
|
- `ATHENA_DOCX_BASE_URL` — DOCX Studio API base URL
|
|
82
82
|
- `ATHENA_DOCX_API_KEY` — Athena API key or service-issued DOCX Studio token
|
|
83
83
|
|
|
84
|
+
Without `ATHENA_DOCX_API_KEY` (and without `api_key=`), the SDK uses
|
|
85
|
+
`ATHENA_API_KEY`. When `ATHENA_API_URL` is set too (the bare Athena URL or one
|
|
86
|
+
ending in `/api/v0`), an opened document's calls carry a 15-minute token Athena
|
|
87
|
+
mints for that document alone (`POST /api/v0/assets/{asset_id}/studio-token`),
|
|
88
|
+
read-only for reads; `Document.create()` and its upload still send the key
|
|
89
|
+
(`docx/_asset_token.py`).
|
|
90
|
+
|
|
84
91
|
## License
|
|
85
92
|
|
|
86
93
|
MIT
|
|
@@ -56,6 +56,13 @@ Required when connecting to DOCX Studio (set by the Athena backend in the `execu
|
|
|
56
56
|
- `ATHENA_DOCX_BASE_URL` — DOCX Studio API base URL
|
|
57
57
|
- `ATHENA_DOCX_API_KEY` — Athena API key or service-issued DOCX Studio token
|
|
58
58
|
|
|
59
|
+
Without `ATHENA_DOCX_API_KEY` (and without `api_key=`), the SDK uses
|
|
60
|
+
`ATHENA_API_KEY`. When `ATHENA_API_URL` is set too (the bare Athena URL or one
|
|
61
|
+
ending in `/api/v0`), an opened document's calls carry a 15-minute token Athena
|
|
62
|
+
mints for that document alone (`POST /api/v0/assets/{asset_id}/studio-token`),
|
|
63
|
+
read-only for reads; `Document.create()` and its upload still send the key
|
|
64
|
+
(`docx/_asset_token.py`).
|
|
65
|
+
|
|
59
66
|
## License
|
|
60
67
|
|
|
61
68
|
MIT
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Per-document DOCX Studio tokens, minted from Athena's public API.
|
|
2
|
+
|
|
3
|
+
A call on an existing Word document reaches DOCX Studio with, in this order:
|
|
4
|
+
|
|
5
|
+
1. the ``api_key`` the caller passed;
|
|
6
|
+
2. the studio's own key, ``ATHENA_DOCX_API_KEY``;
|
|
7
|
+
3. when ``ATHENA_API_KEY`` and ``ATHENA_API_URL`` are both set, a token for that
|
|
8
|
+
document alone, minted by
|
|
9
|
+
``POST {ATHENA_API_URL}/api/v0/assets/{asset_id}/studio-token`` with
|
|
10
|
+
``X-API-KEY: $ATHENA_API_KEY`` and sent as ``Authorization: Bearer``. It names
|
|
11
|
+
the caller, the document, one access level and DOCX Studio, and lasts 15
|
|
12
|
+
minutes;
|
|
13
|
+
4. otherwise ``ATHENA_API_KEY`` itself, as before.
|
|
14
|
+
|
|
15
|
+
Creating a document (``Document.create``, its upload) keeps the key of 1, 2 or
|
|
16
|
+
4: a document that does not exist yet has no token. In case 3 Athena's public
|
|
17
|
+
API creates it instead (``docx._public_create``). A token goes only to its own
|
|
18
|
+
document's routes.
|
|
19
|
+
|
|
20
|
+
A token is minted once per (agora, key, asset, access) for the whole process,
|
|
21
|
+
again 90 seconds before it expires, and once more when the studio answers 401.
|
|
22
|
+
A read mints a read-only token (``{"access": "view"}``: the version check, the
|
|
23
|
+
layout, page renders, the clean export), a write a read-write one
|
|
24
|
+
(``{"access": "edit"}``, which Athena grants only to a caller who may edit).
|
|
25
|
+
Every command batch is a write to DOCX Studio, whose ``/commands`` route needs
|
|
26
|
+
edit access even for a batch that only queries. ``ATHENA_API_URL`` ends in
|
|
27
|
+
``/api/v0`` inside an Athena computer and is the bare agora URL in a session VM
|
|
28
|
+
or locally; both work.
|
|
29
|
+
|
|
30
|
+
Athena's refusals (400, 401, 403, 404, 422) raise at once, without a retry. A
|
|
31
|
+
deployment that signs no asset tokens answers 503: the client then uses the
|
|
32
|
+
studio key when one is set, and otherwise raises an error that names it. An
|
|
33
|
+
Athena older than the route answers FastAPI's bare 404 (``{"detail": "Not
|
|
34
|
+
Found"}``): the client then sends ``ATHENA_API_KEY``, as before the route.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import hashlib
|
|
40
|
+
import inspect
|
|
41
|
+
import os
|
|
42
|
+
import threading
|
|
43
|
+
import time
|
|
44
|
+
from collections import OrderedDict
|
|
45
|
+
from dataclasses import dataclass, field
|
|
46
|
+
from typing import Any
|
|
47
|
+
from urllib.parse import quote
|
|
48
|
+
|
|
49
|
+
import requests
|
|
50
|
+
from requests.adapters import HTTPAdapter
|
|
51
|
+
from urllib3.util.retry import Retry
|
|
52
|
+
|
|
53
|
+
from docx.errors import (
|
|
54
|
+
AuthenticationError,
|
|
55
|
+
DocxError,
|
|
56
|
+
DocxStudioTemporarilyUnavailable,
|
|
57
|
+
NotFoundError,
|
|
58
|
+
SessionError,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
API_KEY_ENV = "ATHENA_API_KEY"
|
|
62
|
+
API_URL_ENV = "ATHENA_API_URL"
|
|
63
|
+
STUDIO_KEY_ENV = "ATHENA_DOCX_API_KEY"
|
|
64
|
+
#: The token audience DOCX Studio verifies.
|
|
65
|
+
AUDIENCE = "docx-studio"
|
|
66
|
+
TOKEN_PREFIX = "aat_"
|
|
67
|
+
PUBLIC_API_PREFIX = "/api/v0"
|
|
68
|
+
|
|
69
|
+
#: The access a token is minted for.
|
|
70
|
+
VIEW = "view"
|
|
71
|
+
EDIT = "edit"
|
|
72
|
+
|
|
73
|
+
#: Minted again this long before Athena's ``expires_at_ms``.
|
|
74
|
+
REFRESH_BEFORE_EXPIRY_SECONDS = 90.0
|
|
75
|
+
#: The longest a token is held, whatever the local clock says (Athena's lifetime).
|
|
76
|
+
MAX_LIFETIME_SECONDS = 15 * 60.0
|
|
77
|
+
#: The shortest, so a clock running ahead of Athena's cannot mint per call.
|
|
78
|
+
MIN_LIFETIME_SECONDS = 30.0
|
|
79
|
+
MINT_TIMEOUT_SECONDS = 30.0
|
|
80
|
+
_MAX_CACHED_TOKENS = 256
|
|
81
|
+
_LOCK_STRIPES = 16
|
|
82
|
+
|
|
83
|
+
_KIND_BY_AUDIENCE = {
|
|
84
|
+
AUDIENCE: "a Word document",
|
|
85
|
+
"pptx-studio": "a presentation",
|
|
86
|
+
"xlsx-studio": "a spreadsheet",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def public_api_base(url: str) -> str:
|
|
91
|
+
"""The public API base for ``url``, whether or not it already ends in it."""
|
|
92
|
+
base = url.strip().rstrip("/")
|
|
93
|
+
if base.endswith(PUBLIC_API_PREFIX):
|
|
94
|
+
return base
|
|
95
|
+
return base + PUBLIC_API_PREFIX
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class StudioToken:
|
|
100
|
+
"""One minted token and what Athena said about it."""
|
|
101
|
+
|
|
102
|
+
token: str = field(repr=False)
|
|
103
|
+
asset_id: str
|
|
104
|
+
audience: str
|
|
105
|
+
#: ``"r"`` (reads only) or ``"rw"``.
|
|
106
|
+
access_type: str
|
|
107
|
+
#: ``time.monotonic()`` after which the token is minted again.
|
|
108
|
+
refresh_at: float
|
|
109
|
+
|
|
110
|
+
def is_fresh(self) -> bool:
|
|
111
|
+
return time.monotonic() < self.refresh_at
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class SigningUnavailable(Exception):
|
|
115
|
+
"""Athena's 503: this deployment signs no asset tokens."""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class MintRouteMissing(Exception):
|
|
119
|
+
"""A 404 with FastAPI's bare route-not-found body: the Athena at
|
|
120
|
+
``ATHENA_API_URL`` predates the studio-token route (a self-hosted release
|
|
121
|
+
older than the SDK). The client then sends ``ATHENA_API_KEY``, as before."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
#: What FastAPI answers for a path no route matches. Athena's own 404 from the
|
|
125
|
+
#: studio-token route names the asset ("Asset not found."), so the two never mix.
|
|
126
|
+
_ROUTE_NOT_FOUND_BODY = {"detail": "Not Found"}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _is_route_missing(response: requests.Response) -> bool:
|
|
130
|
+
if response.status_code != 404:
|
|
131
|
+
return False
|
|
132
|
+
try:
|
|
133
|
+
return bool(response.json() == _ROUTE_NOT_FOUND_BODY)
|
|
134
|
+
except ValueError:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class _TokenCache:
|
|
139
|
+
"""Process-wide tokens by (agora, key, asset, access), least recently used first."""
|
|
140
|
+
|
|
141
|
+
def __init__(self, max_entries: int) -> None:
|
|
142
|
+
self._entries: OrderedDict[tuple[str, str, str, str], StudioToken] = OrderedDict()
|
|
143
|
+
self._max_entries = max_entries
|
|
144
|
+
self._lock = threading.Lock()
|
|
145
|
+
# Minting holds one stripe, so threads wanting the same token wait for
|
|
146
|
+
# one mint instead of each minting their own.
|
|
147
|
+
self._mint_locks = tuple(threading.Lock() for _ in range(_LOCK_STRIPES))
|
|
148
|
+
|
|
149
|
+
def get(self, key: tuple[str, str, str, str]) -> StudioToken | None:
|
|
150
|
+
with self._lock:
|
|
151
|
+
token = self._entries.get(key)
|
|
152
|
+
if token is not None:
|
|
153
|
+
self._entries.move_to_end(key)
|
|
154
|
+
return token
|
|
155
|
+
|
|
156
|
+
def put(self, key: tuple[str, str, str, str], token: StudioToken) -> None:
|
|
157
|
+
with self._lock:
|
|
158
|
+
self._entries[key] = token
|
|
159
|
+
self._entries.move_to_end(key)
|
|
160
|
+
while len(self._entries) > self._max_entries:
|
|
161
|
+
self._entries.popitem(last=False)
|
|
162
|
+
|
|
163
|
+
def discard(self, key: tuple[str, str, str, str], token: StudioToken) -> None:
|
|
164
|
+
"""Forget ``token`` unless another thread already replaced it."""
|
|
165
|
+
with self._lock:
|
|
166
|
+
if self._entries.get(key) is token:
|
|
167
|
+
del self._entries[key]
|
|
168
|
+
|
|
169
|
+
def mint_lock(self, key: tuple[str, str, str, str]) -> threading.Lock:
|
|
170
|
+
return self._mint_locks[hash(key) % _LOCK_STRIPES]
|
|
171
|
+
|
|
172
|
+
def clear(self) -> None:
|
|
173
|
+
with self._lock:
|
|
174
|
+
self._entries.clear()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
_CACHE = _TokenCache(_MAX_CACHED_TOKENS)
|
|
178
|
+
|
|
179
|
+
_SESSION: requests.Session | None = None
|
|
180
|
+
_SESSION_LOCK = threading.Lock()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _mint_session() -> requests.Session:
|
|
184
|
+
"""One session for every mint: connection retries, and status retries for
|
|
185
|
+
429/502/504. A 503 is Athena's answer, not a transient fault, so it is not
|
|
186
|
+
retried; minting is safe to resend (each mint only signs a new token)."""
|
|
187
|
+
global _SESSION
|
|
188
|
+
with _SESSION_LOCK:
|
|
189
|
+
if _SESSION is None:
|
|
190
|
+
retry_kwargs: dict[str, Any] = {
|
|
191
|
+
"total": None,
|
|
192
|
+
"status": 2,
|
|
193
|
+
"connect": 2,
|
|
194
|
+
"read": 0,
|
|
195
|
+
"other": 0,
|
|
196
|
+
"backoff_factor": 0.5,
|
|
197
|
+
"status_forcelist": (429, 502, 504),
|
|
198
|
+
"allowed_methods": frozenset(["POST"]),
|
|
199
|
+
"raise_on_status": False,
|
|
200
|
+
"respect_retry_after_header": True,
|
|
201
|
+
}
|
|
202
|
+
if "backoff_max" in inspect.signature(Retry).parameters:
|
|
203
|
+
retry_kwargs["backoff_max"] = 5.0
|
|
204
|
+
session = requests.Session()
|
|
205
|
+
adapter = HTTPAdapter(max_retries=Retry(**retry_kwargs))
|
|
206
|
+
session.mount("http://", adapter)
|
|
207
|
+
session.mount("https://", adapter)
|
|
208
|
+
_SESSION = session
|
|
209
|
+
return _SESSION
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _detail(response: requests.Response) -> str:
|
|
213
|
+
"""Athena's reason (``{"detail": ...}``), else a short excerpt of the body."""
|
|
214
|
+
try:
|
|
215
|
+
body = response.json()
|
|
216
|
+
except ValueError:
|
|
217
|
+
return " ".join((response.text or "").split())[:200]
|
|
218
|
+
detail = body.get("detail") if isinstance(body, dict) else None
|
|
219
|
+
if isinstance(detail, dict):
|
|
220
|
+
detail = detail.get("message")
|
|
221
|
+
if isinstance(detail, str):
|
|
222
|
+
return detail.strip()
|
|
223
|
+
return ""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _lifetime_seconds(expires_at_ms: int) -> float:
|
|
227
|
+
"""How long to hold a token, from Athena's expiry and the local clock."""
|
|
228
|
+
remaining = expires_at_ms / 1000.0 - time.time()
|
|
229
|
+
return min(max(remaining, MIN_LIFETIME_SECONDS), MAX_LIFETIME_SECONDS)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TokenMinter:
|
|
233
|
+
"""Mints the studio tokens one ``ATHENA_API_KEY`` holds, through the cache."""
|
|
234
|
+
|
|
235
|
+
def __init__(self, *, api_url: str, api_key: str) -> None:
|
|
236
|
+
self.base = public_api_base(api_url)
|
|
237
|
+
self._api_key = api_key
|
|
238
|
+
self._fingerprint = hashlib.sha256(api_key.encode("utf-8")).hexdigest()
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def from_environment(cls) -> TokenMinter | None:
|
|
242
|
+
"""The minter for ``ATHENA_API_KEY`` at ``ATHENA_API_URL``; None when either is unset."""
|
|
243
|
+
api_key = os.environ.get(API_KEY_ENV)
|
|
244
|
+
api_url = os.environ.get(API_URL_ENV)
|
|
245
|
+
if not api_key or not api_url or not api_url.strip():
|
|
246
|
+
return None
|
|
247
|
+
return cls(api_url=api_url, api_key=api_key)
|
|
248
|
+
|
|
249
|
+
def _key(self, *, asset_id: str, access: str) -> tuple[str, str, str, str]:
|
|
250
|
+
return (self.base, self._fingerprint, asset_id, access)
|
|
251
|
+
|
|
252
|
+
def token(self, *, asset_id: str, access: str) -> StudioToken:
|
|
253
|
+
"""The token for ``asset_id`` at ``access``: the cached one while fresh,
|
|
254
|
+
else a new one. Raises when Athena refuses it, when it is for another
|
|
255
|
+
studio, and :class:`SigningUnavailable` on a 503."""
|
|
256
|
+
key = self._key(asset_id=asset_id, access=access)
|
|
257
|
+
token = _CACHE.get(key)
|
|
258
|
+
if token is None or not token.is_fresh():
|
|
259
|
+
with _CACHE.mint_lock(key):
|
|
260
|
+
token = _CACHE.get(key)
|
|
261
|
+
if token is None or not token.is_fresh():
|
|
262
|
+
token = self._mint(asset_id=asset_id, access=access)
|
|
263
|
+
_CACHE.put(key, token)
|
|
264
|
+
if token.audience != AUDIENCE:
|
|
265
|
+
raise DocxError(
|
|
266
|
+
f"Asset {asset_id} is not a Word document: Athena edits it through "
|
|
267
|
+
f"{token.audience}. Open it with the Athena SDK for "
|
|
268
|
+
f"{_KIND_BY_AUDIENCE.get(token.audience, 'that kind of asset')}."
|
|
269
|
+
)
|
|
270
|
+
return token
|
|
271
|
+
|
|
272
|
+
def invalidate(self, *, token: StudioToken, access: str) -> None:
|
|
273
|
+
"""Forget ``token`` (the studio answered 401 to it), so the next call mints."""
|
|
274
|
+
_CACHE.discard(self._key(asset_id=token.asset_id, access=access), token)
|
|
275
|
+
|
|
276
|
+
def _mint(self, *, asset_id: str, access: str) -> StudioToken:
|
|
277
|
+
url = f"{self.base}/assets/{quote(asset_id, safe='')}/studio-token"
|
|
278
|
+
try:
|
|
279
|
+
response = _mint_session().post(
|
|
280
|
+
url,
|
|
281
|
+
json={"access": access},
|
|
282
|
+
headers={"X-API-KEY": self._api_key, "Accept": "application/json"},
|
|
283
|
+
timeout=MINT_TIMEOUT_SECONDS,
|
|
284
|
+
)
|
|
285
|
+
except requests.RequestException as exc:
|
|
286
|
+
raise SessionError(
|
|
287
|
+
f"Unable to reach Athena at {url} to mint a DOCX Studio token for "
|
|
288
|
+
f"{asset_id}: {exc}"
|
|
289
|
+
) from exc
|
|
290
|
+
try:
|
|
291
|
+
return self._read(response, asset_id=asset_id)
|
|
292
|
+
finally:
|
|
293
|
+
response.close()
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _read(response: requests.Response, *, asset_id: str) -> StudioToken:
|
|
297
|
+
status = response.status_code
|
|
298
|
+
if status == 503:
|
|
299
|
+
raise SigningUnavailable(_detail(response) or "This deployment signs no asset tokens.")
|
|
300
|
+
if _is_route_missing(response):
|
|
301
|
+
raise MintRouteMissing(f"no studio-token route at {response.url}")
|
|
302
|
+
detail = _detail(response)
|
|
303
|
+
reason = f"(HTTP {status}{': ' + detail if detail else ''})"
|
|
304
|
+
if status == 401:
|
|
305
|
+
raise AuthenticationError(
|
|
306
|
+
f"Athena refused ATHENA_API_KEY when asked for a DOCX Studio token for "
|
|
307
|
+
f"{asset_id} {reason}. The key is invalid, expired or revoked; retrying "
|
|
308
|
+
"with it will not help."
|
|
309
|
+
)
|
|
310
|
+
if status == 403:
|
|
311
|
+
raise AuthenticationError(f"Athena refused a DOCX Studio token for {asset_id} {reason}.")
|
|
312
|
+
if status == 404:
|
|
313
|
+
raise NotFoundError(
|
|
314
|
+
f"Athena refused a DOCX Studio token for {asset_id} {reason}: the document "
|
|
315
|
+
"does not exist, or this key may not read it.",
|
|
316
|
+
payload={"status": 404, "body": detail},
|
|
317
|
+
)
|
|
318
|
+
if status in (400, 422):
|
|
319
|
+
raise DocxError(f"Athena refused a DOCX Studio token for {asset_id} {reason}.")
|
|
320
|
+
if not response.ok:
|
|
321
|
+
raise DocxStudioTemporarilyUnavailable(
|
|
322
|
+
f"Athena could not mint a DOCX Studio token for {asset_id} {reason}.",
|
|
323
|
+
http_status=status,
|
|
324
|
+
)
|
|
325
|
+
try:
|
|
326
|
+
body = response.json()
|
|
327
|
+
except ValueError:
|
|
328
|
+
body = None
|
|
329
|
+
if not isinstance(body, dict):
|
|
330
|
+
body = {}
|
|
331
|
+
token = body.get("token")
|
|
332
|
+
audience = body.get("audience")
|
|
333
|
+
access_type = body.get("access_type")
|
|
334
|
+
expires_at_ms = body.get("expires_at_ms")
|
|
335
|
+
if (
|
|
336
|
+
not isinstance(token, str)
|
|
337
|
+
or not token.startswith(TOKEN_PREFIX)
|
|
338
|
+
or not isinstance(audience, str)
|
|
339
|
+
or access_type not in ("r", "rw")
|
|
340
|
+
or not isinstance(expires_at_ms, int)
|
|
341
|
+
or isinstance(expires_at_ms, bool)
|
|
342
|
+
or body.get("asset_id") != asset_id
|
|
343
|
+
):
|
|
344
|
+
raise SessionError(
|
|
345
|
+
f"Athena's studio token answer for {asset_id} is not one this SDK can use "
|
|
346
|
+
"(expected token, audience, access_type, expires_at_ms and this asset id)."
|
|
347
|
+
)
|
|
348
|
+
return StudioToken(
|
|
349
|
+
token=token,
|
|
350
|
+
asset_id=asset_id,
|
|
351
|
+
audience=audience,
|
|
352
|
+
access_type=access_type,
|
|
353
|
+
refresh_at=time.monotonic()
|
|
354
|
+
+ max(
|
|
355
|
+
_lifetime_seconds(expires_at_ms) - REFRESH_BEFORE_EXPIRY_SECONDS,
|
|
356
|
+
MIN_LIFETIME_SECONDS,
|
|
357
|
+
),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def studio_credentials(*, api_key: str | None) -> tuple[str | None, TokenMinter | None]:
|
|
362
|
+
"""What a client authenticates with: ``(key, minter)``.
|
|
363
|
+
|
|
364
|
+
``key`` serves every route the minter does not: ``api_key`` when the caller
|
|
365
|
+
passed one, else ``ATHENA_DOCX_API_KEY``, else ``ATHENA_API_KEY``. ``minter``
|
|
366
|
+
mints per-document tokens when neither of the first two is set and
|
|
367
|
+
``ATHENA_API_KEY`` and ``ATHENA_API_URL`` are.
|
|
368
|
+
"""
|
|
369
|
+
if api_key:
|
|
370
|
+
return api_key, None
|
|
371
|
+
studio_key = os.environ.get(STUDIO_KEY_ENV)
|
|
372
|
+
if studio_key:
|
|
373
|
+
return studio_key, None
|
|
374
|
+
return os.environ.get(API_KEY_ENV), TokenMinter.from_environment()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _clear_cache() -> None:
|
|
378
|
+
"""Forget every cached token (tests)."""
|
|
379
|
+
_CACHE.clear()
|
|
@@ -45,8 +45,10 @@ def _resolve_api_key(api_key: str | None) -> str | None:
|
|
|
45
45
|
makes. Both create paths land outside Athena's public API (docx-studio's
|
|
46
46
|
``/docs/empty``, agora's ``/api/docx-studio/assets``), and inside an Athena
|
|
47
47
|
session VM or computer ``ATHENA_API_KEY`` is a credential only that public
|
|
48
|
-
API (``/api/v0``) accepts
|
|
49
|
-
|
|
48
|
+
API (``/api/v0``) accepts: there (neither key set, ``ATHENA_API_URL`` set)
|
|
49
|
+
Athena's public API creates the document instead (``docx._public_create``).
|
|
50
|
+
``ATHENA_API_KEY`` stays the fallback for an environment that sets no
|
|
51
|
+
studio key and no ``ATHENA_API_URL`` (local use with one personal key).
|
|
50
52
|
The new document is owned by the user the key belongs to.
|
|
51
53
|
"""
|
|
52
54
|
return api_key or os.environ.get(_API_KEY_ENV) or os.environ.get("ATHENA_API_KEY")
|
|
@@ -137,6 +139,14 @@ def create_empty_document(
|
|
|
137
139
|
"Pass base_url= to Document.create() or set the env var "
|
|
138
140
|
'(e.g. ATHENA_DOCX_BASE_URL="https://docx-studio.prd.athenaintel.com").',
|
|
139
141
|
)
|
|
142
|
+
created = _create_through_athena(
|
|
143
|
+
api_key=api_key,
|
|
144
|
+
name=name,
|
|
145
|
+
parent_folder_id=parent_folder_id,
|
|
146
|
+
workspace_id=workspace_id,
|
|
147
|
+
)
|
|
148
|
+
if created is not None:
|
|
149
|
+
return created
|
|
140
150
|
if not resolved_key:
|
|
141
151
|
raise AuthenticationError(
|
|
142
152
|
f"Missing api_key and {_API_KEY_ENV} env var. "
|
|
@@ -221,6 +231,44 @@ def create_empty_document(
|
|
|
221
231
|
)
|
|
222
232
|
|
|
223
233
|
|
|
234
|
+
def _create_through_athena(
|
|
235
|
+
*,
|
|
236
|
+
api_key: str | None,
|
|
237
|
+
name: str | None,
|
|
238
|
+
parent_folder_id: str | None,
|
|
239
|
+
workspace_id: str | None,
|
|
240
|
+
) -> CreateAssetResult | None:
|
|
241
|
+
"""A new document Athena's public API made, or None for docx-studio's path.
|
|
242
|
+
|
|
243
|
+
Athena creates it when no ``api_key`` and no ``ATHENA_DOCX_API_KEY`` are set
|
|
244
|
+
and ``ATHENA_API_KEY`` and ``ATHENA_API_URL`` are (``docx._public_create``).
|
|
245
|
+
None otherwise, and when that Athena predates the public create.
|
|
246
|
+
"""
|
|
247
|
+
from docx._public_create import (
|
|
248
|
+
PublicApi,
|
|
249
|
+
PublicCreateUnavailable,
|
|
250
|
+
create_word_document,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
api = PublicApi.for_creation(api_key=api_key)
|
|
254
|
+
if api is None:
|
|
255
|
+
return None
|
|
256
|
+
try:
|
|
257
|
+
created = create_word_document(
|
|
258
|
+
api=api,
|
|
259
|
+
name=name,
|
|
260
|
+
parent_folder_id=parent_folder_id,
|
|
261
|
+
workspace_id=workspace_id or os.environ.get("ATHENA_WORKSPACE_ID"),
|
|
262
|
+
)
|
|
263
|
+
except PublicCreateUnavailable:
|
|
264
|
+
return None
|
|
265
|
+
return CreateAssetResult(
|
|
266
|
+
asset_id=created["asset_id"],
|
|
267
|
+
name=created["title"],
|
|
268
|
+
workspace_id=created["workspace_id"] or "",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
224
272
|
class UploadAssetResult(TypedDict):
|
|
225
273
|
"""Parsed response from the create-and-import upload flow."""
|
|
226
274
|
|
|
@@ -228,6 +276,58 @@ class UploadAssetResult(TypedDict):
|
|
|
228
276
|
name: str
|
|
229
277
|
|
|
230
278
|
|
|
279
|
+
def _upload_through_athena(
|
|
280
|
+
*,
|
|
281
|
+
docx_path: str,
|
|
282
|
+
api_key: str | None,
|
|
283
|
+
name: str | None,
|
|
284
|
+
parent_folder_id: str | None,
|
|
285
|
+
workspace_id: str | None,
|
|
286
|
+
source_asset_id: str | None,
|
|
287
|
+
timeout: float,
|
|
288
|
+
) -> UploadAssetResult | None:
|
|
289
|
+
"""A document Athena made from the ``.docx`` at ``docx_path``, or None.
|
|
290
|
+
|
|
291
|
+
Athena's ``POST /api/v0/assets/create-from-file`` makes it in one request
|
|
292
|
+
when no ``api_key`` and no ``ATHENA_DOCX_API_KEY`` are set and
|
|
293
|
+
``ATHENA_API_KEY`` and ``ATHENA_API_URL`` are (``docx._public_create``).
|
|
294
|
+
None otherwise, and when that Athena predates the route: the upload then
|
|
295
|
+
goes through agora's docx-studio bridge, as before.
|
|
296
|
+
"""
|
|
297
|
+
from docx._public_create import (
|
|
298
|
+
PublicApi,
|
|
299
|
+
PublicCreateUnavailable,
|
|
300
|
+
create_word_document_from_file,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
api = PublicApi.for_creation(api_key=api_key)
|
|
304
|
+
if api is None:
|
|
305
|
+
return None
|
|
306
|
+
if not os.path.exists(docx_path):
|
|
307
|
+
raise SessionError(f"DOCX file not found: {docx_path}")
|
|
308
|
+
if not docx_path.lower().endswith(".docx"):
|
|
309
|
+
raise SessionError(
|
|
310
|
+
f"Only .docx files are accepted by the upload endpoint (got {docx_path})",
|
|
311
|
+
)
|
|
312
|
+
title = name
|
|
313
|
+
if title is None:
|
|
314
|
+
base = os.path.basename(docx_path)
|
|
315
|
+
title = base[:-5] if base.lower().endswith(".docx") else base
|
|
316
|
+
try:
|
|
317
|
+
created = create_word_document_from_file(
|
|
318
|
+
api=api,
|
|
319
|
+
docx_path=docx_path,
|
|
320
|
+
title=title,
|
|
321
|
+
parent_folder_id=parent_folder_id,
|
|
322
|
+
workspace_id=workspace_id or os.environ.get("ATHENA_WORKSPACE_ID"),
|
|
323
|
+
source_asset_id=source_asset_id,
|
|
324
|
+
timeout=timeout,
|
|
325
|
+
)
|
|
326
|
+
except PublicCreateUnavailable:
|
|
327
|
+
return None
|
|
328
|
+
return UploadAssetResult(asset_id=created["asset_id"], name=created["title"])
|
|
329
|
+
|
|
330
|
+
|
|
231
331
|
def upload_document(
|
|
232
332
|
*,
|
|
233
333
|
docx_path: str,
|
|
@@ -273,6 +373,18 @@ def upload_document(
|
|
|
273
373
|
import base64
|
|
274
374
|
import os
|
|
275
375
|
|
|
376
|
+
uploaded = _upload_through_athena(
|
|
377
|
+
docx_path=docx_path,
|
|
378
|
+
api_key=api_key,
|
|
379
|
+
name=name,
|
|
380
|
+
parent_folder_id=parent_folder_id,
|
|
381
|
+
workspace_id=workspace_id,
|
|
382
|
+
source_asset_id=source_asset_id,
|
|
383
|
+
timeout=timeout,
|
|
384
|
+
)
|
|
385
|
+
if uploaded is not None:
|
|
386
|
+
return uploaded
|
|
387
|
+
|
|
276
388
|
resolved_docx_base: str | None = docx_base_url or os.environ.get(_BASE_URL_ENV)
|
|
277
389
|
resolved_key: str | None = _resolve_api_key(api_key)
|
|
278
390
|
resolved_agora_base: str | None = (
|