athena-python-docx 0.44.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.44.0 → athena_python_docx-0.45.0}/PKG-INFO +1 -1
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_asset_token.py +28 -3
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_http.py +114 -2
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_http_doc.py +6 -1
- athena_python_docx-0.45.0/docx/_public_create.py +270 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/document.py +5 -1
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/pyproject.toml +1 -1
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_asset_tokens.py +39 -24
- athena_python_docx-0.45.0/tests/test_public_create.py +473 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/uv.lock +1 -1
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/.gitignore +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/AGENTS.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/CLAUDE.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/DOCX_EXEC_LAB.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/README.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_buffer.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_capabilities.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_citations.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_postproc.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_proxy.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_references.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_run_order.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_timeouts.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_tracked.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/_xml_text.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/api.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/blkcntnr.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/charts.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/client.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/commands.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/comments.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/drawing/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/base.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/dml.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/shape.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/errors.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/fields.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/image/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/image/exceptions.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/image/image.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/math.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/section.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/session.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/settings.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/shape.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/shared.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/styles/_table.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/styles/latent.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/styles/style.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/table.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/run.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/toc.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/docx/typing.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/dump_wire_fixtures.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/agent_documents.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/default_style_run_formatting.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/header_footer_pictures.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/less_used_writes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/merged_cell_identity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/paragraph_text_setter.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/picture_runs.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/proxy_equality.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/return_types.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/xml_incompatible_text.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe/zero_and_negative_indents.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/fake_session.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.44.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.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw16_header_footer_tables.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw17_header_letterhead_filled.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw18_header_table_properties.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw19_header_table_rows_columns.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/op_snapshots/rw20_header_table_merges.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fixtures/field_reads.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fixtures/note_reference_reads.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fixtures/ruby_reads.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/fixtures/symbol_reads.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/baseline.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/fixture_scripts.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/reader.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/runner.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/sdk_driver.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/stock_driver.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/live/test_live_runner.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_add_section.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_alignment_values.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_athena_extension_creates_session_open.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_athena_extensions_registry.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_batch_chunking.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_battery_cases.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_block_not_found_error.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_borders_spec.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_fastpath_runs.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_format_stash.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_merge.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_nested_tables.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_paragraphs_listed.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_citations.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_color_format_theme.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_commands.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_comment_paragraphs.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_comments_first_read.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_content_control_value.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_core_properties.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_create_workspace.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_document_settings_session_open.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_drawing_image.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_empty_hyperlinks.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_enum_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_export_docx_clean.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_export_rescue_anchors.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_field_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_field_refresh.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_field_runs.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_flushed_write_failures.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_image_url_data_uri.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_inline_page_break.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_inline_shape_alt_text.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_inline_shape_size.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_inline_shape_type.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_inline_shapes_floating.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_latent_style_writes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_line_clear_break.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_list_styles.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_listed_paragraph_reads.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_literal_no_break_hyphens.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_literal_soft_hyphens.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_merged_cell_identity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_missing_modules.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_nested_table_refused.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_note_reference_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_page_break.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_paragraph_delete.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_paragraph_indent_reads.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_paragraph_runs_hyperlinks.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_paragraph_text_empty_run.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_parfmt_clears.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_pdf_export_and_page_renders.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_positional_cell_id.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_proxy_equality.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_read_run_ranges.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_rendered_page_breaks.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_repeated_node_read.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_restart_numbering.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_round_trip_smoke_script.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_ruby_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_add_picture_anchor.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_break_inner_content.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_edges.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_formatting_wire.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_identity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_later_runs.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_mark_inheritance.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_page_number_blocks.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_table_style_objects.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_text_python_docx.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_text_refreshes_paragraph.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_run_unmodeled_toggles.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_sdk_reads_served.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_section_read_lengths.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_sections_empty_list.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_add_break.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_delete.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_formatting_writes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_metadata_writes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_names.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_resolution.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_tab_stops.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_style_tab_stops_read.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_symbol_reads_python_docx_parity.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_tab_stop_writes.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_add_row_grid_row.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_anchors.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_clears.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_direction.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_length_reads.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_listing_reads.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_read_reuse.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_text_control_characters.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_tracked_revisions_offsets.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_unmapped_alignment_reads.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_uv_lock_version.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_versioned_read_reuse.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_wire_contract.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.44.0 → athena_python_docx-0.45.0}/tests/test_xml_incompatible_text.py +0 -0
- {athena_python_docx-0.44.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>
|
|
@@ -13,8 +13,9 @@ A call on an existing Word document reaches DOCX Studio with, in this order:
|
|
|
13
13
|
4. otherwise ``ATHENA_API_KEY`` itself, as before.
|
|
14
14
|
|
|
15
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.
|
|
17
|
-
|
|
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.
|
|
18
19
|
|
|
19
20
|
A token is minted once per (agora, key, asset, access) for the whole process,
|
|
20
21
|
again 90 seconds before it expires, and once more when the studio answers 401.
|
|
@@ -28,7 +29,9 @@ or locally; both work.
|
|
|
28
29
|
|
|
29
30
|
Athena's refusals (400, 401, 403, 404, 422) raise at once, without a retry. A
|
|
30
31
|
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
|
+
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.
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
37
|
from __future__ import annotations
|
|
@@ -112,6 +115,26 @@ class SigningUnavailable(Exception):
|
|
|
112
115
|
"""Athena's 503: this deployment signs no asset tokens."""
|
|
113
116
|
|
|
114
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
|
+
|
|
115
138
|
class _TokenCache:
|
|
116
139
|
"""Process-wide tokens by (agora, key, asset, access), least recently used first."""
|
|
117
140
|
|
|
@@ -274,6 +297,8 @@ class TokenMinter:
|
|
|
274
297
|
status = response.status_code
|
|
275
298
|
if status == 503:
|
|
276
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}")
|
|
277
302
|
detail = _detail(response)
|
|
278
303
|
reason = f"(HTTP {status}{': ' + detail if detail else ''})"
|
|
279
304
|
if status == 401:
|
|
@@ -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 = (
|
|
@@ -47,6 +47,7 @@ from docx._asset_token import (
|
|
|
47
47
|
EDIT,
|
|
48
48
|
STUDIO_KEY_ENV,
|
|
49
49
|
VIEW,
|
|
50
|
+
MintRouteMissing,
|
|
50
51
|
SigningUnavailable,
|
|
51
52
|
StudioToken,
|
|
52
53
|
TokenMinter,
|
|
@@ -1127,6 +1128,10 @@ class HttpClient:
|
|
|
1127
1128
|
token = minter.token(asset_id=asset_id, access=access)
|
|
1128
1129
|
except SigningUnavailable as exc:
|
|
1129
1130
|
return self._without_asset_tokens(asset_id=asset_id, reason=str(exc)), None
|
|
1131
|
+
except MintRouteMissing:
|
|
1132
|
+
# The Athena at ATHENA_API_URL predates the route: the key, as before.
|
|
1133
|
+
self._asset_tokens = None
|
|
1134
|
+
return self._api_key, None
|
|
1130
1135
|
return token.token, (token, access)
|
|
1131
1136
|
|
|
1132
1137
|
def _without_asset_tokens(self, *, asset_id: str, reason: str) -> str:
|
|
@@ -1160,7 +1165,7 @@ class HttpClient:
|
|
|
1160
1165
|
minter.invalidate(token=refused, access=granted_access)
|
|
1161
1166
|
try:
|
|
1162
1167
|
fresh = minter.token(asset_id=asset_id, access=granted_access)
|
|
1163
|
-
except SigningUnavailable:
|
|
1168
|
+
except (SigningUnavailable, MintRouteMissing):
|
|
1164
1169
|
raise exc from None
|
|
1165
1170
|
if fresh.token == refused.token:
|
|
1166
1171
|
raise
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Creating a Word document through Athena's public API when no studio key is set.
|
|
2
|
+
|
|
3
|
+
``Document.create()`` creates a document with the key every other call sends
|
|
4
|
+
(``api_key``, ``ATHENA_DOCX_API_KEY``, then ``ATHENA_API_KEY``), except when
|
|
5
|
+
neither of the first two is set and ``ATHENA_API_KEY`` and ``ATHENA_API_URL``
|
|
6
|
+
both are. That is a computer's or a session VM's scoped credential, which
|
|
7
|
+
Athena's public API (``/api/v0``) takes and neither docx-studio's ``/docs/empty``
|
|
8
|
+
nor agora's ``/api/docx-studio/assets`` does, so Athena creates the document:
|
|
9
|
+
|
|
10
|
+
* a document holding Athena's default template:
|
|
11
|
+
``POST {ATHENA_API_URL}/api/v0/assets/create`` with
|
|
12
|
+
``{"asset_type": "word_document", ...}``;
|
|
13
|
+
* a document holding a local ``.docx`` (``Document.create(docx=...)``):
|
|
14
|
+
``POST {ATHENA_API_URL}/api/v0/assets/create-from-file`` (multipart ``file``,
|
|
15
|
+
query ``title``, ``parent_folder_id``, ``workspace_id``, ``source_asset_id``).
|
|
16
|
+
|
|
17
|
+
Both send ``X-API-KEY: $ATHENA_API_KEY`` and, inside an agent run, the run's
|
|
18
|
+
``X-Athena-Session-Credential`` (``ATHENA_CLI_SESSION_KEY``), so the document's
|
|
19
|
+
first write records the run. Athena writes it with its own studio credential,
|
|
20
|
+
as the key's user, and answers once the document holds its content; its own
|
|
21
|
+
calls then carry a token minted for it (``docx._asset_token``).
|
|
22
|
+
|
|
23
|
+
An Athena older than these routes answers 422 naming the asset type, or
|
|
24
|
+
FastAPI's bare 404 or 405: the caller then creates through docx-studio and agora
|
|
25
|
+
with ``ATHENA_API_KEY``, as before (:class:`PublicCreateUnavailable`). Every
|
|
26
|
+
other refusal raises. A create is not resent once its request may have reached
|
|
27
|
+
Athena: a resend could make a second document.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import inspect
|
|
33
|
+
import os
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Any, TypedDict
|
|
37
|
+
|
|
38
|
+
import requests
|
|
39
|
+
from requests.adapters import HTTPAdapter
|
|
40
|
+
from urllib3.util.retry import Retry
|
|
41
|
+
|
|
42
|
+
from docx._asset_token import API_KEY_ENV, API_URL_ENV, STUDIO_KEY_ENV, public_api_base
|
|
43
|
+
from docx.errors import AuthenticationError, DocxError, SessionError
|
|
44
|
+
|
|
45
|
+
#: The credential the athena CLI's session VM exports for one ``execute`` call.
|
|
46
|
+
SESSION_CREDENTIAL_ENV = "ATHENA_CLI_SESSION_KEY"
|
|
47
|
+
#: The header Athena reads it from to name the agent run a write belongs to.
|
|
48
|
+
SESSION_CREDENTIAL_HEADER = "X-Athena-Session-Credential"
|
|
49
|
+
#: Athena waits for docx-studio's seed or import before it answers.
|
|
50
|
+
CREATE_TIMEOUT_SECONDS = 120.0
|
|
51
|
+
_DOCX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
52
|
+
# What FastAPI answers for a path no route serves (404) or serves with other
|
|
53
|
+
# methods only (405: ``/assets/{asset_id}`` takes GET and PATCH).
|
|
54
|
+
_ROUTE_MISSING_BODIES = ({"detail": "Not Found"}, {"detail": "Method Not Allowed"})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PublicCreateUnavailable(Exception):
|
|
58
|
+
"""The Athena at ``ATHENA_API_URL`` predates creating a Word document through it."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CreatedDocument(TypedDict):
|
|
62
|
+
"""What Athena answered: the new document's id, title and workspace."""
|
|
63
|
+
|
|
64
|
+
asset_id: str
|
|
65
|
+
title: str
|
|
66
|
+
workspace_id: str | None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class PublicApi:
|
|
71
|
+
"""Athena's public API, reached with ``ATHENA_API_KEY``."""
|
|
72
|
+
|
|
73
|
+
base: str
|
|
74
|
+
api_key: str = field(repr=False)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def for_creation(cls, *, api_key: str | None) -> PublicApi | None:
|
|
78
|
+
"""The public API a new document is created through, or None.
|
|
79
|
+
|
|
80
|
+
None when the caller passed ``api_key`` or ``ATHENA_DOCX_API_KEY`` is
|
|
81
|
+
set (they create on docx-studio, as before), or when ``ATHENA_API_KEY``
|
|
82
|
+
or ``ATHENA_API_URL`` is unset.
|
|
83
|
+
"""
|
|
84
|
+
if api_key or os.environ.get(STUDIO_KEY_ENV):
|
|
85
|
+
return None
|
|
86
|
+
athena_key = os.environ.get(API_KEY_ENV)
|
|
87
|
+
api_url = os.environ.get(API_URL_ENV)
|
|
88
|
+
if not athena_key or not api_url or not api_url.strip():
|
|
89
|
+
return None
|
|
90
|
+
return cls(base=public_api_base(api_url), api_key=athena_key)
|
|
91
|
+
|
|
92
|
+
def headers(self) -> dict[str, str]:
|
|
93
|
+
from docx import __version__
|
|
94
|
+
|
|
95
|
+
headers = {
|
|
96
|
+
"X-API-KEY": self.api_key,
|
|
97
|
+
"Accept": "application/json",
|
|
98
|
+
"User-Agent": f"athena-python-docx/{__version__}",
|
|
99
|
+
}
|
|
100
|
+
session_credential = os.environ.get(SESSION_CREDENTIAL_ENV)
|
|
101
|
+
if session_credential:
|
|
102
|
+
headers[SESSION_CREDENTIAL_HEADER] = session_credential
|
|
103
|
+
return headers
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _create_session() -> requests.Session:
|
|
107
|
+
"""Connection retries only: a create that may have reached Athena is never resent."""
|
|
108
|
+
retry_kwargs: dict[str, Any] = {
|
|
109
|
+
"total": None,
|
|
110
|
+
"status": 0,
|
|
111
|
+
"connect": 2,
|
|
112
|
+
"read": 0,
|
|
113
|
+
"other": 0,
|
|
114
|
+
"backoff_factor": 0.5,
|
|
115
|
+
"allowed_methods": frozenset(["POST"]),
|
|
116
|
+
"raise_on_status": False,
|
|
117
|
+
}
|
|
118
|
+
if "backoff_max" in inspect.signature(Retry).parameters:
|
|
119
|
+
retry_kwargs["backoff_max"] = 5.0
|
|
120
|
+
session = requests.Session()
|
|
121
|
+
adapter = HTTPAdapter(max_retries=Retry(**retry_kwargs))
|
|
122
|
+
session.mount("http://", adapter)
|
|
123
|
+
session.mount("https://", adapter)
|
|
124
|
+
return session
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _json(response: requests.Response) -> Any:
|
|
128
|
+
try:
|
|
129
|
+
return response.json()
|
|
130
|
+
except ValueError:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _detail(body: Any, response: requests.Response) -> str:
|
|
135
|
+
"""Athena's reason: ``detail`` as text, or the first validation message."""
|
|
136
|
+
detail = body.get("detail") if isinstance(body, dict) else None
|
|
137
|
+
if isinstance(detail, str):
|
|
138
|
+
return detail.strip()
|
|
139
|
+
if isinstance(detail, list) and detail and isinstance(detail[0], dict):
|
|
140
|
+
message = detail[0].get("msg")
|
|
141
|
+
if isinstance(message, str):
|
|
142
|
+
return message.strip()
|
|
143
|
+
return " ".join((response.text or "").split())[:200]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _is_unavailable(*, status: int, body: Any) -> bool:
|
|
147
|
+
"""An Athena that predates the route, or the ``word_document`` asset type."""
|
|
148
|
+
if status in (404, 405) and body in _ROUTE_MISSING_BODIES:
|
|
149
|
+
return True
|
|
150
|
+
if status != 422 or not isinstance(body, dict):
|
|
151
|
+
return False
|
|
152
|
+
errors = body.get("detail")
|
|
153
|
+
return isinstance(errors, list) and any(
|
|
154
|
+
isinstance(error, dict)
|
|
155
|
+
and "asset_type" in (error.get("loc") or [])
|
|
156
|
+
and "Unsupported asset type" in str(error.get("msg", ""))
|
|
157
|
+
for error in errors
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _created(response: requests.Response, *, what: str) -> CreatedDocument:
|
|
162
|
+
status = response.status_code
|
|
163
|
+
body = _json(response)
|
|
164
|
+
if _is_unavailable(status=status, body=body):
|
|
165
|
+
raise PublicCreateUnavailable(f"{response.url} answered HTTP {status}")
|
|
166
|
+
if status == 200 and isinstance(body, dict):
|
|
167
|
+
asset_id = body.get("asset_id")
|
|
168
|
+
title = body.get("title")
|
|
169
|
+
workspace_id = body.get("workspace_id")
|
|
170
|
+
if (
|
|
171
|
+
isinstance(asset_id, str)
|
|
172
|
+
and asset_id.startswith("asset_")
|
|
173
|
+
and isinstance(title, str)
|
|
174
|
+
and (workspace_id is None or isinstance(workspace_id, str))
|
|
175
|
+
):
|
|
176
|
+
return CreatedDocument(asset_id=asset_id, title=title, workspace_id=workspace_id)
|
|
177
|
+
detail = _detail(body, response)
|
|
178
|
+
if status in (401, 403):
|
|
179
|
+
raise AuthenticationError(
|
|
180
|
+
f"Athena refused to create {what} with ATHENA_API_KEY (HTTP {status})"
|
|
181
|
+
f"{': ' + detail if detail else ''}",
|
|
182
|
+
)
|
|
183
|
+
if status == 200:
|
|
184
|
+
raise SessionError(f"Athena's answer to creating {what} is not one this SDK can use: {detail}")
|
|
185
|
+
raise DocxError(f"Athena could not create {what} (HTTP {status}){': ' + detail if detail else ''}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _post(
|
|
189
|
+
*,
|
|
190
|
+
session: requests.Session,
|
|
191
|
+
url: str,
|
|
192
|
+
what: str,
|
|
193
|
+
**kwargs: Any,
|
|
194
|
+
) -> CreatedDocument:
|
|
195
|
+
try:
|
|
196
|
+
response = session.post(url, **kwargs)
|
|
197
|
+
except requests.RequestException as exc:
|
|
198
|
+
raise SessionError(f"Unable to reach Athena at {url} to create {what}: {exc}") from exc
|
|
199
|
+
try:
|
|
200
|
+
return _created(response, what=what)
|
|
201
|
+
finally:
|
|
202
|
+
response.close()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def create_word_document(
|
|
206
|
+
*,
|
|
207
|
+
api: PublicApi,
|
|
208
|
+
name: str | None,
|
|
209
|
+
parent_folder_id: str | None,
|
|
210
|
+
workspace_id: str | None,
|
|
211
|
+
) -> CreatedDocument:
|
|
212
|
+
"""Have Athena create a Word document holding its default template."""
|
|
213
|
+
payload: dict[str, str] = {"asset_type": "word_document"}
|
|
214
|
+
if name is not None:
|
|
215
|
+
payload["title"] = name
|
|
216
|
+
if parent_folder_id is not None:
|
|
217
|
+
payload["parent_folder_id"] = parent_folder_id
|
|
218
|
+
if workspace_id is not None:
|
|
219
|
+
payload["workspace_id"] = workspace_id
|
|
220
|
+
session = _create_session()
|
|
221
|
+
try:
|
|
222
|
+
return _post(
|
|
223
|
+
session=session,
|
|
224
|
+
url=f"{api.base}/assets/create",
|
|
225
|
+
what="a Word document",
|
|
226
|
+
json=payload,
|
|
227
|
+
headers=api.headers(),
|
|
228
|
+
timeout=CREATE_TIMEOUT_SECONDS,
|
|
229
|
+
)
|
|
230
|
+
finally:
|
|
231
|
+
session.close()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def create_word_document_from_file(
|
|
235
|
+
*,
|
|
236
|
+
api: PublicApi,
|
|
237
|
+
docx_path: str,
|
|
238
|
+
title: str,
|
|
239
|
+
parent_folder_id: str | None,
|
|
240
|
+
workspace_id: str | None,
|
|
241
|
+
source_asset_id: str | None,
|
|
242
|
+
timeout: float,
|
|
243
|
+
) -> CreatedDocument:
|
|
244
|
+
"""Have Athena create a Word document holding the ``.docx`` at ``docx_path``.
|
|
245
|
+
|
|
246
|
+
Athena answers once the file is imported, and leaves nothing behind when the
|
|
247
|
+
import fails.
|
|
248
|
+
"""
|
|
249
|
+
params: dict[str, str] = {"title": title}
|
|
250
|
+
if parent_folder_id is not None:
|
|
251
|
+
params["parent_folder_id"] = parent_folder_id
|
|
252
|
+
if workspace_id is not None:
|
|
253
|
+
params["workspace_id"] = workspace_id
|
|
254
|
+
if source_asset_id is not None:
|
|
255
|
+
params["source_asset_id"] = source_asset_id
|
|
256
|
+
file_path = Path(docx_path)
|
|
257
|
+
session = _create_session()
|
|
258
|
+
try:
|
|
259
|
+
with file_path.open("rb") as handle:
|
|
260
|
+
return _post(
|
|
261
|
+
session=session,
|
|
262
|
+
url=f"{api.base}/assets/create-from-file",
|
|
263
|
+
what=f"a Word document from {file_path.name}",
|
|
264
|
+
params=params,
|
|
265
|
+
files={"file": (file_path.name, handle, _DOCX_CONTENT_TYPE)},
|
|
266
|
+
headers=api.headers(),
|
|
267
|
+
timeout=max(timeout, CREATE_TIMEOUT_SECONDS),
|
|
268
|
+
)
|
|
269
|
+
finally:
|
|
270
|
+
session.close()
|
|
@@ -282,7 +282,11 @@ class Document:
|
|
|
282
282
|
api_key: Athena API key (PropelAuth user API key) or
|
|
283
283
|
access token. Falls back to ``$ATHENA_DOCX_API_KEY``, then
|
|
284
284
|
``$ATHENA_API_KEY``, as every other call does; the upload path
|
|
285
|
-
(``docx=``) sends the same key to agora.
|
|
285
|
+
(``docx=``) sends the same key to agora. With neither of the
|
|
286
|
+
first two set and both ``$ATHENA_API_KEY`` and
|
|
287
|
+
``$ATHENA_API_URL`` set (a computer's or session VM's scoped
|
|
288
|
+
key), Athena's public API creates the document instead, from
|
|
289
|
+
the template or from ``docx`` (``docx._public_create``).
|
|
286
290
|
parent_folder_id: Optional parent folder; defaults to
|
|
287
291
|
the source document's folder when source_asset_id is set,
|
|
288
292
|
otherwise workspace root.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-docx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.45.0"
|
|
8
8
|
description = "Drop-in replacement for python-docx that connects to Athena's DOCX Studio collaborative document stack"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Order for a call on an existing Word document: ``api_key=``;
|
|
4
4
|
``ATHENA_DOCX_API_KEY``; a token minted from Athena's public API with
|
|
5
5
|
``ATHENA_API_KEY`` (when ``ATHENA_API_URL`` is set too); else ``ATHENA_API_KEY``
|
|
6
|
-
itself. Creating a document
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
itself. Creating a document in the token case goes through Athena's public
|
|
7
|
+
API (``test_public_create.py``). Nothing here reaches a real service: every
|
|
8
|
+
request goes through ``requests.Session.request``, faked below.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -256,29 +256,22 @@ def test_athena_api_url_works_with_and_without_the_public_api_prefix(
|
|
|
256
256
|
assert [call.url for call in world.mint_calls()] == [MINT_URL]
|
|
257
257
|
|
|
258
258
|
|
|
259
|
-
def
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def __exit__(self, *_: object) -> None:
|
|
267
|
-
return None
|
|
268
|
-
|
|
269
|
-
def read(self) -> bytes:
|
|
270
|
-
return json.dumps(
|
|
271
|
-
{"assetId": ASSET, "name": "Doc", "workspaceId": "ws_1"}
|
|
272
|
-
).encode()
|
|
273
|
-
|
|
274
|
-
def _capture(request: Any, **_: Any) -> _Created:
|
|
275
|
-
sent.append(request)
|
|
276
|
-
return _Created()
|
|
259
|
+
def test_creating_a_document_goes_through_athena_and_mints_nothing(world: _World) -> None:
|
|
260
|
+
# A document that does not exist has no token, and docx-studio refuses the
|
|
261
|
+
# scoped key: Athena's public API creates it (docx._public_create).
|
|
262
|
+
world.studio.append(
|
|
263
|
+
_response(200, {"asset_id": ASSET, "title": "Doc", "workspace_id": "ws_1"})
|
|
264
|
+
)
|
|
277
265
|
|
|
278
|
-
with patch("docx._http.urllib.request.urlopen"
|
|
279
|
-
create_empty_document(name="Doc")
|
|
266
|
+
with patch("docx._http.urllib.request.urlopen") as urlopen:
|
|
267
|
+
created = create_empty_document(name="Doc")
|
|
280
268
|
|
|
281
|
-
|
|
269
|
+
urlopen.assert_not_called()
|
|
270
|
+
assert created == {"asset_id": ASSET, "name": "Doc", "workspace_id": "ws_1"}
|
|
271
|
+
[call] = world.calls
|
|
272
|
+
assert (call.method, call.url) == ("POST", f"{AGORA}/api/v0/assets/create")
|
|
273
|
+
assert call.headers["X-API-KEY"] == SCOPED_KEY
|
|
274
|
+
assert call.bearer is None
|
|
282
275
|
assert world.mint_calls() == []
|
|
283
276
|
|
|
284
277
|
|
|
@@ -459,6 +452,28 @@ def test_a_503_falls_back_to_a_studio_key_set_since(
|
|
|
459
452
|
assert len(world.mint_calls()) == 1
|
|
460
453
|
|
|
461
454
|
|
|
455
|
+
def test_an_athena_without_the_mint_route_gets_athena_api_key_as_before(world: _World) -> None:
|
|
456
|
+
# An Athena older than the route: FastAPI's bare 404 for a path no route matches.
|
|
457
|
+
world.mints.append(_response(404, {"detail": "Not Found"}))
|
|
458
|
+
world.studio.extend([_response(200, {"pageCount": 1, "pages": []}), _applied()])
|
|
459
|
+
client = _client()
|
|
460
|
+
|
|
461
|
+
client.get_layout(ASSET)
|
|
462
|
+
_batch(client)
|
|
463
|
+
|
|
464
|
+
assert len(world.mint_calls()) == 1
|
|
465
|
+
assert [call.bearer for call in world.studio_calls()] == [SCOPED_KEY, SCOPED_KEY]
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_athenas_own_asset_not_found_stays_final(world: _World) -> None:
|
|
469
|
+
world.mints.append(_response(404, {"detail": "Asset not found."}))
|
|
470
|
+
|
|
471
|
+
with pytest.raises(NotFoundError, match="Asset not found"):
|
|
472
|
+
_batch(_client())
|
|
473
|
+
|
|
474
|
+
assert world.studio_calls() == []
|
|
475
|
+
|
|
476
|
+
|
|
462
477
|
def test_an_asset_another_studio_edits_is_never_sent_its_token(world: _World) -> None:
|
|
463
478
|
world.mints.append(world.mint_ok(audience="xlsx-studio"))
|
|
464
479
|
|