athena-python-docx 0.43.0__tar.gz → 0.44.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.44.0}/PKG-INFO +8 -1
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/README.md +7 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/__init__.py +1 -1
- athena_python_docx-0.44.0/docx/_asset_token.py +354 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_http_doc.py +134 -33
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/client.py +12 -9
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/pyproject.toml +1 -1
- athena_python_docx-0.44.0/tests/test_asset_tokens.py +468 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/uv.lock +1 -1
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/.gitignore +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/AGENTS.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/CLAUDE.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/DOCX_EXEC_LAB.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_capabilities.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_citations.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_http.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_postproc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_proxy.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_references.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_run_order.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_timeouts.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_tracked.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/_xml_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/api.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/blkcntnr.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/charts.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/commands.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/comments.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/document.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/drawing/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/base.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/dml.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/errors.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/fields.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/image/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/image/exceptions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/image/image.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/math.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/session.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/settings.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/shared.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/styles/_table.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/styles/latent.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/table.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/toc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/docx/typing.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/dump_wire_fixtures.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/agent_documents.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/default_style_run_formatting.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/header_footer_pictures.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/less_used_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/merged_cell_identity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/paragraph_text_setter.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/picture_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/proxy_equality.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/return_types.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/xml_incompatible_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe/zero_and_negative_indents.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.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.44.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw16_header_footer_tables.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw17_header_letterhead_filled.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw18_header_table_properties.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw19_header_table_rows_columns.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/op_snapshots/rw20_header_table_merges.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fixtures/field_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fixtures/note_reference_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fixtures/ruby_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/fixtures/symbol_reads.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/baseline.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/fixture_scripts.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/reader.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/sdk_driver.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/stock_driver.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/live/test_live_runner.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_add_section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_alignment_values.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_athena_extension_creates_session_open.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_athena_extensions_registry.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_batch_chunking.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_battery_cases.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_borders_spec.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_fastpath_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_merge.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_nested_tables.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_paragraphs_listed.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_citations.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_color_format_theme.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_comment_paragraphs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_comments_first_read.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_content_control_value.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_core_properties.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_create_workspace.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_document_settings_session_open.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_drawing_image.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_empty_hyperlinks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_enum_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_export_docx_clean.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_export_rescue_anchors.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_field_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_field_refresh.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_field_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_flushed_write_failures.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_image_url_data_uri.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_inline_page_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_inline_shape_alt_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_inline_shape_size.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_inline_shape_type.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_inline_shapes_floating.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_latent_style_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_line_clear_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_listed_paragraph_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_literal_no_break_hyphens.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_literal_soft_hyphens.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_merged_cell_identity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_missing_modules.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_nested_table_refused.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_note_reference_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_page_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_paragraph_delete.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_paragraph_indent_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_paragraph_runs_hyperlinks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_paragraph_text_empty_run.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_parfmt_clears.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_pdf_export_and_page_renders.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_positional_cell_id.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_proxy_equality.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_read_run_ranges.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_rendered_page_breaks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_repeated_node_read.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_restart_numbering.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_round_trip_smoke_script.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_ruby_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_add_picture_anchor.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_break_inner_content.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_edges.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_formatting_wire.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_identity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_later_runs.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_mark_inheritance.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_page_number_blocks.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_table_style_objects.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_text_python_docx.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_text_refreshes_paragraph.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_run_unmodeled_toggles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_sdk_reads_served.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_section_read_lengths.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_sections_empty_list.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_delete.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_formatting_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_metadata_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_names.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_resolution.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_tab_stops.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_style_tab_stops_read.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_symbol_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_tab_stop_writes.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_add_row_grid_row.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_anchors.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_clears.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_direction.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_length_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_listing_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_read_reuse.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_text_control_characters.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_tracked_revisions_offsets.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_unmapped_alignment_reads.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_uv_lock_version.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_versioned_read_reuse.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.0}/tests/test_xml_incompatible_text.py +0 -0
- {athena_python_docx-0.43.0 → athena_python_docx-0.44.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.44.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,354 @@
|
|
|
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. A token goes only to its
|
|
17
|
+
own document's routes.
|
|
18
|
+
|
|
19
|
+
A token is minted once per (agora, key, asset, access) for the whole process,
|
|
20
|
+
again 90 seconds before it expires, and once more when the studio answers 401.
|
|
21
|
+
A read mints a read-only token (``{"access": "view"}``: the version check, the
|
|
22
|
+
layout, page renders, the clean export), a write a read-write one
|
|
23
|
+
(``{"access": "edit"}``, which Athena grants only to a caller who may edit).
|
|
24
|
+
Every command batch is a write to DOCX Studio, whose ``/commands`` route needs
|
|
25
|
+
edit access even for a batch that only queries. ``ATHENA_API_URL`` ends in
|
|
26
|
+
``/api/v0`` inside an Athena computer and is the bare agora URL in a session VM
|
|
27
|
+
or locally; both work.
|
|
28
|
+
|
|
29
|
+
Athena's refusals (400, 401, 403, 404, 422) raise at once, without a retry. A
|
|
30
|
+
deployment that signs no asset tokens answers 503: the client then uses the
|
|
31
|
+
studio key when one is set, and otherwise raises an error that names it.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import hashlib
|
|
37
|
+
import inspect
|
|
38
|
+
import os
|
|
39
|
+
import threading
|
|
40
|
+
import time
|
|
41
|
+
from collections import OrderedDict
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
from typing import Any
|
|
44
|
+
from urllib.parse import quote
|
|
45
|
+
|
|
46
|
+
import requests
|
|
47
|
+
from requests.adapters import HTTPAdapter
|
|
48
|
+
from urllib3.util.retry import Retry
|
|
49
|
+
|
|
50
|
+
from docx.errors import (
|
|
51
|
+
AuthenticationError,
|
|
52
|
+
DocxError,
|
|
53
|
+
DocxStudioTemporarilyUnavailable,
|
|
54
|
+
NotFoundError,
|
|
55
|
+
SessionError,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
API_KEY_ENV = "ATHENA_API_KEY"
|
|
59
|
+
API_URL_ENV = "ATHENA_API_URL"
|
|
60
|
+
STUDIO_KEY_ENV = "ATHENA_DOCX_API_KEY"
|
|
61
|
+
#: The token audience DOCX Studio verifies.
|
|
62
|
+
AUDIENCE = "docx-studio"
|
|
63
|
+
TOKEN_PREFIX = "aat_"
|
|
64
|
+
PUBLIC_API_PREFIX = "/api/v0"
|
|
65
|
+
|
|
66
|
+
#: The access a token is minted for.
|
|
67
|
+
VIEW = "view"
|
|
68
|
+
EDIT = "edit"
|
|
69
|
+
|
|
70
|
+
#: Minted again this long before Athena's ``expires_at_ms``.
|
|
71
|
+
REFRESH_BEFORE_EXPIRY_SECONDS = 90.0
|
|
72
|
+
#: The longest a token is held, whatever the local clock says (Athena's lifetime).
|
|
73
|
+
MAX_LIFETIME_SECONDS = 15 * 60.0
|
|
74
|
+
#: The shortest, so a clock running ahead of Athena's cannot mint per call.
|
|
75
|
+
MIN_LIFETIME_SECONDS = 30.0
|
|
76
|
+
MINT_TIMEOUT_SECONDS = 30.0
|
|
77
|
+
_MAX_CACHED_TOKENS = 256
|
|
78
|
+
_LOCK_STRIPES = 16
|
|
79
|
+
|
|
80
|
+
_KIND_BY_AUDIENCE = {
|
|
81
|
+
AUDIENCE: "a Word document",
|
|
82
|
+
"pptx-studio": "a presentation",
|
|
83
|
+
"xlsx-studio": "a spreadsheet",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def public_api_base(url: str) -> str:
|
|
88
|
+
"""The public API base for ``url``, whether or not it already ends in it."""
|
|
89
|
+
base = url.strip().rstrip("/")
|
|
90
|
+
if base.endswith(PUBLIC_API_PREFIX):
|
|
91
|
+
return base
|
|
92
|
+
return base + PUBLIC_API_PREFIX
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class StudioToken:
|
|
97
|
+
"""One minted token and what Athena said about it."""
|
|
98
|
+
|
|
99
|
+
token: str = field(repr=False)
|
|
100
|
+
asset_id: str
|
|
101
|
+
audience: str
|
|
102
|
+
#: ``"r"`` (reads only) or ``"rw"``.
|
|
103
|
+
access_type: str
|
|
104
|
+
#: ``time.monotonic()`` after which the token is minted again.
|
|
105
|
+
refresh_at: float
|
|
106
|
+
|
|
107
|
+
def is_fresh(self) -> bool:
|
|
108
|
+
return time.monotonic() < self.refresh_at
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SigningUnavailable(Exception):
|
|
112
|
+
"""Athena's 503: this deployment signs no asset tokens."""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class _TokenCache:
|
|
116
|
+
"""Process-wide tokens by (agora, key, asset, access), least recently used first."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, max_entries: int) -> None:
|
|
119
|
+
self._entries: OrderedDict[tuple[str, str, str, str], StudioToken] = OrderedDict()
|
|
120
|
+
self._max_entries = max_entries
|
|
121
|
+
self._lock = threading.Lock()
|
|
122
|
+
# Minting holds one stripe, so threads wanting the same token wait for
|
|
123
|
+
# one mint instead of each minting their own.
|
|
124
|
+
self._mint_locks = tuple(threading.Lock() for _ in range(_LOCK_STRIPES))
|
|
125
|
+
|
|
126
|
+
def get(self, key: tuple[str, str, str, str]) -> StudioToken | None:
|
|
127
|
+
with self._lock:
|
|
128
|
+
token = self._entries.get(key)
|
|
129
|
+
if token is not None:
|
|
130
|
+
self._entries.move_to_end(key)
|
|
131
|
+
return token
|
|
132
|
+
|
|
133
|
+
def put(self, key: tuple[str, str, str, str], token: StudioToken) -> None:
|
|
134
|
+
with self._lock:
|
|
135
|
+
self._entries[key] = token
|
|
136
|
+
self._entries.move_to_end(key)
|
|
137
|
+
while len(self._entries) > self._max_entries:
|
|
138
|
+
self._entries.popitem(last=False)
|
|
139
|
+
|
|
140
|
+
def discard(self, key: tuple[str, str, str, str], token: StudioToken) -> None:
|
|
141
|
+
"""Forget ``token`` unless another thread already replaced it."""
|
|
142
|
+
with self._lock:
|
|
143
|
+
if self._entries.get(key) is token:
|
|
144
|
+
del self._entries[key]
|
|
145
|
+
|
|
146
|
+
def mint_lock(self, key: tuple[str, str, str, str]) -> threading.Lock:
|
|
147
|
+
return self._mint_locks[hash(key) % _LOCK_STRIPES]
|
|
148
|
+
|
|
149
|
+
def clear(self) -> None:
|
|
150
|
+
with self._lock:
|
|
151
|
+
self._entries.clear()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
_CACHE = _TokenCache(_MAX_CACHED_TOKENS)
|
|
155
|
+
|
|
156
|
+
_SESSION: requests.Session | None = None
|
|
157
|
+
_SESSION_LOCK = threading.Lock()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _mint_session() -> requests.Session:
|
|
161
|
+
"""One session for every mint: connection retries, and status retries for
|
|
162
|
+
429/502/504. A 503 is Athena's answer, not a transient fault, so it is not
|
|
163
|
+
retried; minting is safe to resend (each mint only signs a new token)."""
|
|
164
|
+
global _SESSION
|
|
165
|
+
with _SESSION_LOCK:
|
|
166
|
+
if _SESSION is None:
|
|
167
|
+
retry_kwargs: dict[str, Any] = {
|
|
168
|
+
"total": None,
|
|
169
|
+
"status": 2,
|
|
170
|
+
"connect": 2,
|
|
171
|
+
"read": 0,
|
|
172
|
+
"other": 0,
|
|
173
|
+
"backoff_factor": 0.5,
|
|
174
|
+
"status_forcelist": (429, 502, 504),
|
|
175
|
+
"allowed_methods": frozenset(["POST"]),
|
|
176
|
+
"raise_on_status": False,
|
|
177
|
+
"respect_retry_after_header": True,
|
|
178
|
+
}
|
|
179
|
+
if "backoff_max" in inspect.signature(Retry).parameters:
|
|
180
|
+
retry_kwargs["backoff_max"] = 5.0
|
|
181
|
+
session = requests.Session()
|
|
182
|
+
adapter = HTTPAdapter(max_retries=Retry(**retry_kwargs))
|
|
183
|
+
session.mount("http://", adapter)
|
|
184
|
+
session.mount("https://", adapter)
|
|
185
|
+
_SESSION = session
|
|
186
|
+
return _SESSION
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _detail(response: requests.Response) -> str:
|
|
190
|
+
"""Athena's reason (``{"detail": ...}``), else a short excerpt of the body."""
|
|
191
|
+
try:
|
|
192
|
+
body = response.json()
|
|
193
|
+
except ValueError:
|
|
194
|
+
return " ".join((response.text or "").split())[:200]
|
|
195
|
+
detail = body.get("detail") if isinstance(body, dict) else None
|
|
196
|
+
if isinstance(detail, dict):
|
|
197
|
+
detail = detail.get("message")
|
|
198
|
+
if isinstance(detail, str):
|
|
199
|
+
return detail.strip()
|
|
200
|
+
return ""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _lifetime_seconds(expires_at_ms: int) -> float:
|
|
204
|
+
"""How long to hold a token, from Athena's expiry and the local clock."""
|
|
205
|
+
remaining = expires_at_ms / 1000.0 - time.time()
|
|
206
|
+
return min(max(remaining, MIN_LIFETIME_SECONDS), MAX_LIFETIME_SECONDS)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TokenMinter:
|
|
210
|
+
"""Mints the studio tokens one ``ATHENA_API_KEY`` holds, through the cache."""
|
|
211
|
+
|
|
212
|
+
def __init__(self, *, api_url: str, api_key: str) -> None:
|
|
213
|
+
self.base = public_api_base(api_url)
|
|
214
|
+
self._api_key = api_key
|
|
215
|
+
self._fingerprint = hashlib.sha256(api_key.encode("utf-8")).hexdigest()
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def from_environment(cls) -> TokenMinter | None:
|
|
219
|
+
"""The minter for ``ATHENA_API_KEY`` at ``ATHENA_API_URL``; None when either is unset."""
|
|
220
|
+
api_key = os.environ.get(API_KEY_ENV)
|
|
221
|
+
api_url = os.environ.get(API_URL_ENV)
|
|
222
|
+
if not api_key or not api_url or not api_url.strip():
|
|
223
|
+
return None
|
|
224
|
+
return cls(api_url=api_url, api_key=api_key)
|
|
225
|
+
|
|
226
|
+
def _key(self, *, asset_id: str, access: str) -> tuple[str, str, str, str]:
|
|
227
|
+
return (self.base, self._fingerprint, asset_id, access)
|
|
228
|
+
|
|
229
|
+
def token(self, *, asset_id: str, access: str) -> StudioToken:
|
|
230
|
+
"""The token for ``asset_id`` at ``access``: the cached one while fresh,
|
|
231
|
+
else a new one. Raises when Athena refuses it, when it is for another
|
|
232
|
+
studio, and :class:`SigningUnavailable` on a 503."""
|
|
233
|
+
key = self._key(asset_id=asset_id, access=access)
|
|
234
|
+
token = _CACHE.get(key)
|
|
235
|
+
if token is None or not token.is_fresh():
|
|
236
|
+
with _CACHE.mint_lock(key):
|
|
237
|
+
token = _CACHE.get(key)
|
|
238
|
+
if token is None or not token.is_fresh():
|
|
239
|
+
token = self._mint(asset_id=asset_id, access=access)
|
|
240
|
+
_CACHE.put(key, token)
|
|
241
|
+
if token.audience != AUDIENCE:
|
|
242
|
+
raise DocxError(
|
|
243
|
+
f"Asset {asset_id} is not a Word document: Athena edits it through "
|
|
244
|
+
f"{token.audience}. Open it with the Athena SDK for "
|
|
245
|
+
f"{_KIND_BY_AUDIENCE.get(token.audience, 'that kind of asset')}."
|
|
246
|
+
)
|
|
247
|
+
return token
|
|
248
|
+
|
|
249
|
+
def invalidate(self, *, token: StudioToken, access: str) -> None:
|
|
250
|
+
"""Forget ``token`` (the studio answered 401 to it), so the next call mints."""
|
|
251
|
+
_CACHE.discard(self._key(asset_id=token.asset_id, access=access), token)
|
|
252
|
+
|
|
253
|
+
def _mint(self, *, asset_id: str, access: str) -> StudioToken:
|
|
254
|
+
url = f"{self.base}/assets/{quote(asset_id, safe='')}/studio-token"
|
|
255
|
+
try:
|
|
256
|
+
response = _mint_session().post(
|
|
257
|
+
url,
|
|
258
|
+
json={"access": access},
|
|
259
|
+
headers={"X-API-KEY": self._api_key, "Accept": "application/json"},
|
|
260
|
+
timeout=MINT_TIMEOUT_SECONDS,
|
|
261
|
+
)
|
|
262
|
+
except requests.RequestException as exc:
|
|
263
|
+
raise SessionError(
|
|
264
|
+
f"Unable to reach Athena at {url} to mint a DOCX Studio token for "
|
|
265
|
+
f"{asset_id}: {exc}"
|
|
266
|
+
) from exc
|
|
267
|
+
try:
|
|
268
|
+
return self._read(response, asset_id=asset_id)
|
|
269
|
+
finally:
|
|
270
|
+
response.close()
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def _read(response: requests.Response, *, asset_id: str) -> StudioToken:
|
|
274
|
+
status = response.status_code
|
|
275
|
+
if status == 503:
|
|
276
|
+
raise SigningUnavailable(_detail(response) or "This deployment signs no asset tokens.")
|
|
277
|
+
detail = _detail(response)
|
|
278
|
+
reason = f"(HTTP {status}{': ' + detail if detail else ''})"
|
|
279
|
+
if status == 401:
|
|
280
|
+
raise AuthenticationError(
|
|
281
|
+
f"Athena refused ATHENA_API_KEY when asked for a DOCX Studio token for "
|
|
282
|
+
f"{asset_id} {reason}. The key is invalid, expired or revoked; retrying "
|
|
283
|
+
"with it will not help."
|
|
284
|
+
)
|
|
285
|
+
if status == 403:
|
|
286
|
+
raise AuthenticationError(f"Athena refused a DOCX Studio token for {asset_id} {reason}.")
|
|
287
|
+
if status == 404:
|
|
288
|
+
raise NotFoundError(
|
|
289
|
+
f"Athena refused a DOCX Studio token for {asset_id} {reason}: the document "
|
|
290
|
+
"does not exist, or this key may not read it.",
|
|
291
|
+
payload={"status": 404, "body": detail},
|
|
292
|
+
)
|
|
293
|
+
if status in (400, 422):
|
|
294
|
+
raise DocxError(f"Athena refused a DOCX Studio token for {asset_id} {reason}.")
|
|
295
|
+
if not response.ok:
|
|
296
|
+
raise DocxStudioTemporarilyUnavailable(
|
|
297
|
+
f"Athena could not mint a DOCX Studio token for {asset_id} {reason}.",
|
|
298
|
+
http_status=status,
|
|
299
|
+
)
|
|
300
|
+
try:
|
|
301
|
+
body = response.json()
|
|
302
|
+
except ValueError:
|
|
303
|
+
body = None
|
|
304
|
+
if not isinstance(body, dict):
|
|
305
|
+
body = {}
|
|
306
|
+
token = body.get("token")
|
|
307
|
+
audience = body.get("audience")
|
|
308
|
+
access_type = body.get("access_type")
|
|
309
|
+
expires_at_ms = body.get("expires_at_ms")
|
|
310
|
+
if (
|
|
311
|
+
not isinstance(token, str)
|
|
312
|
+
or not token.startswith(TOKEN_PREFIX)
|
|
313
|
+
or not isinstance(audience, str)
|
|
314
|
+
or access_type not in ("r", "rw")
|
|
315
|
+
or not isinstance(expires_at_ms, int)
|
|
316
|
+
or isinstance(expires_at_ms, bool)
|
|
317
|
+
or body.get("asset_id") != asset_id
|
|
318
|
+
):
|
|
319
|
+
raise SessionError(
|
|
320
|
+
f"Athena's studio token answer for {asset_id} is not one this SDK can use "
|
|
321
|
+
"(expected token, audience, access_type, expires_at_ms and this asset id)."
|
|
322
|
+
)
|
|
323
|
+
return StudioToken(
|
|
324
|
+
token=token,
|
|
325
|
+
asset_id=asset_id,
|
|
326
|
+
audience=audience,
|
|
327
|
+
access_type=access_type,
|
|
328
|
+
refresh_at=time.monotonic()
|
|
329
|
+
+ max(
|
|
330
|
+
_lifetime_seconds(expires_at_ms) - REFRESH_BEFORE_EXPIRY_SECONDS,
|
|
331
|
+
MIN_LIFETIME_SECONDS,
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def studio_credentials(*, api_key: str | None) -> tuple[str | None, TokenMinter | None]:
|
|
337
|
+
"""What a client authenticates with: ``(key, minter)``.
|
|
338
|
+
|
|
339
|
+
``key`` serves every route the minter does not: ``api_key`` when the caller
|
|
340
|
+
passed one, else ``ATHENA_DOCX_API_KEY``, else ``ATHENA_API_KEY``. ``minter``
|
|
341
|
+
mints per-document tokens when neither of the first two is set and
|
|
342
|
+
``ATHENA_API_KEY`` and ``ATHENA_API_URL`` are.
|
|
343
|
+
"""
|
|
344
|
+
if api_key:
|
|
345
|
+
return api_key, None
|
|
346
|
+
studio_key = os.environ.get(STUDIO_KEY_ENV)
|
|
347
|
+
if studio_key:
|
|
348
|
+
return studio_key, None
|
|
349
|
+
return os.environ.get(API_KEY_ENV), TokenMinter.from_environment()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _clear_cache() -> None:
|
|
353
|
+
"""Forget every cached token (tests)."""
|
|
354
|
+
_CACHE.clear()
|
|
@@ -36,12 +36,21 @@ import os
|
|
|
36
36
|
import re
|
|
37
37
|
import urllib.parse
|
|
38
38
|
import warnings
|
|
39
|
-
from
|
|
39
|
+
from collections.abc import Callable
|
|
40
|
+
from typing import Any, TypedDict, TypeVar
|
|
40
41
|
|
|
41
42
|
import requests
|
|
42
43
|
from requests.adapters import HTTPAdapter
|
|
43
44
|
from urllib3.util.retry import Retry
|
|
44
45
|
|
|
46
|
+
from docx._asset_token import (
|
|
47
|
+
EDIT,
|
|
48
|
+
STUDIO_KEY_ENV,
|
|
49
|
+
VIEW,
|
|
50
|
+
SigningUnavailable,
|
|
51
|
+
StudioToken,
|
|
52
|
+
TokenMinter,
|
|
53
|
+
)
|
|
45
54
|
from docx._batching import call_abandoned, note_progress
|
|
46
55
|
from docx._buffer import CommandBuffer, keep_reported_message
|
|
47
56
|
from docx._tracked import TRACKED_AUTHORING_CAPABILITY, TRACKED_MINIMAL_PARAGRAPH_TEXT_CAPABILITY
|
|
@@ -211,6 +220,8 @@ VERSION_CHECK_TIMEOUT_SECONDS: float = 10.0
|
|
|
211
220
|
# (``CommandsRequestSchema`` in ``apps/api/src/commands/types.ts``).
|
|
212
221
|
MAX_BATCH_COMMANDS: int = 500
|
|
213
222
|
|
|
223
|
+
_T = TypeVar("_T")
|
|
224
|
+
|
|
214
225
|
|
|
215
226
|
def _minted_ids(results: list[Any]) -> dict[str, str]:
|
|
216
227
|
"""The client → real ids that the Creates among a batch's ``results`` echo."""
|
|
@@ -720,6 +731,16 @@ def _parse_retry_after(value: str | None) -> float | None:
|
|
|
720
731
|
return None
|
|
721
732
|
|
|
722
733
|
|
|
734
|
+
def _rejected(status_code: int, err_body: str) -> AuthenticationError:
|
|
735
|
+
"""docx-studio's 401 or 403, carrying its status (a 401 to a minted token
|
|
736
|
+
is minted again once, :meth:`HttpClient._with_token`)."""
|
|
737
|
+
error = AuthenticationError(
|
|
738
|
+
f"docx-studio rejected the request (HTTP {status_code}): {err_body}",
|
|
739
|
+
)
|
|
740
|
+
error.http_status = status_code # type: ignore[attr-defined]
|
|
741
|
+
return error
|
|
742
|
+
|
|
743
|
+
|
|
723
744
|
def _delegation_headers() -> dict[str, str]:
|
|
724
745
|
"""The user a service-key caller acts for, as ``X-Custom-Attributions``.
|
|
725
746
|
|
|
@@ -934,9 +955,7 @@ def _http_post_json(
|
|
|
934
955
|
|
|
935
956
|
err_body: str = resp.text
|
|
936
957
|
if resp.status_code in (401, 403):
|
|
937
|
-
raise
|
|
938
|
-
f"docx-studio rejected the request (HTTP {resp.status_code}): {err_body}",
|
|
939
|
-
)
|
|
958
|
+
raise _rejected(resp.status_code, err_body)
|
|
940
959
|
if resp.status_code == 404:
|
|
941
960
|
raise NotFoundError(
|
|
942
961
|
f"docx-studio returned HTTP 404: {err_body}",
|
|
@@ -1009,9 +1028,7 @@ def _http_get(
|
|
|
1009
1028
|
return resp
|
|
1010
1029
|
err_body: str = resp.text
|
|
1011
1030
|
if resp.status_code in (401, 403):
|
|
1012
|
-
raise
|
|
1013
|
-
f"docx-studio rejected the request (HTTP {resp.status_code}): {err_body}",
|
|
1014
|
-
)
|
|
1031
|
+
raise _rejected(resp.status_code, err_body)
|
|
1015
1032
|
if resp.status_code == 404:
|
|
1016
1033
|
try:
|
|
1017
1034
|
parsed: object = resp.json()
|
|
@@ -1084,13 +1101,82 @@ class HttpClient:
|
|
|
1084
1101
|
base_url: str,
|
|
1085
1102
|
api_key: str,
|
|
1086
1103
|
session: requests.Session | None = None,
|
|
1104
|
+
asset_tokens: TokenMinter | None = None,
|
|
1105
|
+
token_asset_id: str | None = None,
|
|
1087
1106
|
) -> None:
|
|
1107
|
+
"""``api_key`` authenticates every call, except that with ``asset_tokens``
|
|
1108
|
+
the calls on ``token_asset_id`` carry a token minted for that document
|
|
1109
|
+
alone (``docx._asset_token``): read-only for a read, read-write for a
|
|
1110
|
+
command batch."""
|
|
1088
1111
|
self._base_url: str = base_url.rstrip("/")
|
|
1089
1112
|
self._api_key: str = api_key
|
|
1090
1113
|
self._session: requests.Session = session or _build_retry_session()
|
|
1114
|
+
self._asset_tokens: TokenMinter | None = asset_tokens
|
|
1115
|
+
self._token_asset_id: str | None = token_asset_id
|
|
1091
1116
|
# Header/footer paragraph ids, for the ``block not found`` hint.
|
|
1092
1117
|
self.header_footer_block_ids: set[str] = set()
|
|
1093
1118
|
|
|
1119
|
+
def _bearer(
|
|
1120
|
+
self, *, asset_id: str, access: str
|
|
1121
|
+
) -> tuple[str, tuple[StudioToken, str] | None]:
|
|
1122
|
+
"""The credential for a call on ``asset_id``: its token, else the key."""
|
|
1123
|
+
minter = self._asset_tokens
|
|
1124
|
+
if minter is None or asset_id != self._token_asset_id:
|
|
1125
|
+
return self._api_key, None
|
|
1126
|
+
try:
|
|
1127
|
+
token = minter.token(asset_id=asset_id, access=access)
|
|
1128
|
+
except SigningUnavailable as exc:
|
|
1129
|
+
return self._without_asset_tokens(asset_id=asset_id, reason=str(exc)), None
|
|
1130
|
+
return token.token, (token, access)
|
|
1131
|
+
|
|
1132
|
+
def _without_asset_tokens(self, *, asset_id: str, reason: str) -> str:
|
|
1133
|
+
"""Athena signs no asset tokens here (its 503): use the studio key from
|
|
1134
|
+
now on when one is set, else raise an error that says what to set."""
|
|
1135
|
+
studio_key = os.environ.get(STUDIO_KEY_ENV)
|
|
1136
|
+
if not studio_key:
|
|
1137
|
+
raise AuthenticationError(
|
|
1138
|
+
f"Athena could not mint a DOCX Studio token for {asset_id}: "
|
|
1139
|
+
f"{reason.rstrip('.')} (HTTP 503). Set {STUDIO_KEY_ENV} (or pass api_key=) "
|
|
1140
|
+
"to an Athena API key DOCX Studio accepts."
|
|
1141
|
+
)
|
|
1142
|
+
self._asset_tokens = None
|
|
1143
|
+
self._api_key = studio_key
|
|
1144
|
+
return studio_key
|
|
1145
|
+
|
|
1146
|
+
def _with_token(
|
|
1147
|
+
self, *, asset_id: str, access: str, send: Callable[[str], _T]
|
|
1148
|
+
) -> _T:
|
|
1149
|
+
"""``send(bearer)`` with the call's credential. A 401 to a minted token
|
|
1150
|
+
is sent once more with a newly minted one: docx-studio refused it before
|
|
1151
|
+
acting on it, so nothing is applied twice."""
|
|
1152
|
+
bearer, grant = self._bearer(asset_id=asset_id, access=access)
|
|
1153
|
+
try:
|
|
1154
|
+
return send(bearer)
|
|
1155
|
+
except AuthenticationError as exc:
|
|
1156
|
+
minter = self._asset_tokens
|
|
1157
|
+
if grant is None or minter is None or getattr(exc, "http_status", None) != 401:
|
|
1158
|
+
raise
|
|
1159
|
+
refused, granted_access = grant
|
|
1160
|
+
minter.invalidate(token=refused, access=granted_access)
|
|
1161
|
+
try:
|
|
1162
|
+
fresh = minter.token(asset_id=asset_id, access=granted_access)
|
|
1163
|
+
except SigningUnavailable:
|
|
1164
|
+
raise exc from None
|
|
1165
|
+
if fresh.token == refused.token:
|
|
1166
|
+
raise
|
|
1167
|
+
try:
|
|
1168
|
+
return send(fresh.token)
|
|
1169
|
+
except AuthenticationError as again:
|
|
1170
|
+
if getattr(again, "http_status", None) != 401:
|
|
1171
|
+
raise
|
|
1172
|
+
raise AuthenticationError(
|
|
1173
|
+
f"docx-studio refused the token Athena minted for {asset_id}, and the one "
|
|
1174
|
+
f"minted again after that refusal too ({again}). DOCX Studio at "
|
|
1175
|
+
f"{self._base_url} may belong to another Athena deployment than "
|
|
1176
|
+
"ATHENA_API_URL; check both URLs, or set ATHENA_DOCX_API_KEY to an Athena "
|
|
1177
|
+
"API key the studio accepts."
|
|
1178
|
+
) from again
|
|
1179
|
+
|
|
1094
1180
|
def close(self) -> None:
|
|
1095
1181
|
"""Release the pooled HTTP connections held by ``requests.Session``.
|
|
1096
1182
|
|
|
@@ -1285,13 +1371,18 @@ class HttpClient:
|
|
|
1285
1371
|
body["changeMode"] = change_mode
|
|
1286
1372
|
if user is not None:
|
|
1287
1373
|
body["user"] = user
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1374
|
+
# docx-studio's /commands needs edit access, for a batch of queries too.
|
|
1375
|
+
resp: dict = self._with_token(
|
|
1376
|
+
asset_id=asset_id,
|
|
1377
|
+
access=EDIT,
|
|
1378
|
+
send=lambda bearer: _http_post_json(
|
|
1379
|
+
session=self._session,
|
|
1380
|
+
url=url,
|
|
1381
|
+
api_key=bearer,
|
|
1382
|
+
body=body,
|
|
1383
|
+
timeout=timeout_for_commands(commands=commands),
|
|
1384
|
+
header_footer_block_ids=self.header_footer_block_ids,
|
|
1385
|
+
),
|
|
1295
1386
|
)
|
|
1296
1387
|
applied = resp.get("applied")
|
|
1297
1388
|
# Commands the engine doesn't implement yet are reported in
|
|
@@ -1353,10 +1444,9 @@ class HttpClient:
|
|
|
1353
1444
|
kind): a held read is then read again, so the read's own request
|
|
1354
1445
|
reports what went wrong."""
|
|
1355
1446
|
try:
|
|
1356
|
-
resp =
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
api_key=self._api_key,
|
|
1447
|
+
resp = self._get(
|
|
1448
|
+
asset_id,
|
|
1449
|
+
"version",
|
|
1360
1450
|
params={"since": token},
|
|
1361
1451
|
timeout=VERSION_CHECK_TIMEOUT_SECONDS,
|
|
1362
1452
|
)
|
|
@@ -1369,14 +1459,31 @@ class HttpClient:
|
|
|
1369
1459
|
def _doc_url(self, asset_id: str, path: str) -> str:
|
|
1370
1460
|
return f"{self._base_url}/docs/{urllib.parse.quote(asset_id, safe='')}/{path}"
|
|
1371
1461
|
|
|
1462
|
+
def _get(
|
|
1463
|
+
self,
|
|
1464
|
+
asset_id: str,
|
|
1465
|
+
path: str,
|
|
1466
|
+
*,
|
|
1467
|
+
params: dict[str, str] | None = None,
|
|
1468
|
+
timeout: float = DEFAULT_OP_TIMEOUT_SECONDS,
|
|
1469
|
+
) -> requests.Response:
|
|
1470
|
+
"""``GET /docs/:id/<path>``, one of docx-studio's read-only routes (VIEW gate)."""
|
|
1471
|
+
return self._with_token(
|
|
1472
|
+
asset_id=asset_id,
|
|
1473
|
+
access=VIEW,
|
|
1474
|
+
send=lambda bearer: _http_get(
|
|
1475
|
+
session=self._session,
|
|
1476
|
+
url=self._doc_url(asset_id, path),
|
|
1477
|
+
api_key=bearer,
|
|
1478
|
+
params=params,
|
|
1479
|
+
timeout=timeout,
|
|
1480
|
+
),
|
|
1481
|
+
)
|
|
1482
|
+
|
|
1372
1483
|
def get_layout(self, asset_id: str) -> dict[str, Any]:
|
|
1373
1484
|
"""``GET /docs/:id/layout.json`` — the page count and page sizes as
|
|
1374
1485
|
DOCX Studio's layout engine paginates the document (VIEW gate)."""
|
|
1375
|
-
resp =
|
|
1376
|
-
session=self._session,
|
|
1377
|
-
url=self._doc_url(asset_id, "layout.json"),
|
|
1378
|
-
api_key=self._api_key,
|
|
1379
|
-
)
|
|
1486
|
+
resp = self._get(asset_id, "layout.json")
|
|
1380
1487
|
try:
|
|
1381
1488
|
data: object = resp.json()
|
|
1382
1489
|
except ValueError as e:
|
|
@@ -1388,12 +1495,7 @@ class HttpClient:
|
|
|
1388
1495
|
def render_page_png(self, asset_id: str, page_number: int, *, scale: float) -> RenderedPagePng:
|
|
1389
1496
|
"""``GET /docs/:id/pages/:n.png?scale=`` — one page (1-indexed) as PNG,
|
|
1390
1497
|
from the same layout engine the editor paginates with (VIEW gate)."""
|
|
1391
|
-
resp =
|
|
1392
|
-
session=self._session,
|
|
1393
|
-
url=self._doc_url(asset_id, f"pages/{page_number}.png"),
|
|
1394
|
-
api_key=self._api_key,
|
|
1395
|
-
params={"scale": f"{scale:g}"},
|
|
1396
|
-
)
|
|
1498
|
+
resp = self._get(asset_id, f"pages/{page_number}.png", params={"scale": f"{scale:g}"})
|
|
1397
1499
|
page_count_header = resp.headers.get("X-Page-Count", "")
|
|
1398
1500
|
page_count = int(page_count_header) if page_count_header.isdigit() else 0
|
|
1399
1501
|
return RenderedPagePng(png=resp.content, page_count=page_count)
|
|
@@ -1405,10 +1507,9 @@ class HttpClient:
|
|
|
1405
1507
|
the plain export without ``X-Docx-Clean-Copy``: that is refused here,
|
|
1406
1508
|
so an unclean copy never reaches the caller.
|
|
1407
1509
|
"""
|
|
1408
|
-
resp =
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
api_key=self._api_key,
|
|
1510
|
+
resp = self._get(
|
|
1511
|
+
asset_id,
|
|
1512
|
+
"export.docx",
|
|
1412
1513
|
params={"clean": "1", **({"clearTitle": "1"} if clear_title else {})},
|
|
1413
1514
|
timeout=LONG_RUNNING_COMMAND_TIMEOUT_SECONDS,
|
|
1414
1515
|
)
|