athena-python-docx 0.23.0__tar.gz → 0.25.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.23.0 → athena_python_docx-0.25.0}/AGENTS.md +44 -8
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/PKG-INFO +1 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/__init__.py +1 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_buffer.py +17 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_http_doc.py +134 -8
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_postproc.py +1 -1
- athena_python_docx-0.25.0/docx/_run_order.py +217 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_timeouts.py +2 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/client.py +16 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/commands.py +41 -6
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/document.py +236 -43
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/errors.py +38 -15
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/section.py +172 -122
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/shape.py +89 -12
- athena_python_docx-0.25.0/docx/styles/__init__.py +38 -0
- athena_python_docx-0.25.0/docx/styles/_table.py +303 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/styles/style.py +5 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/styles/styles.py +88 -34
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/table.py +188 -60
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/paragraph.py +98 -45
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/run.py +249 -54
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/pyproject.toml +1 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/fake_session.py +15 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +1 -0
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/15_run_add_break_page.json +6 -0
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/29_section_headers_linked.json +7 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/35_full_report.json +1 -0
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/48_font_color_round_trip.json +5 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/49_resume_layout.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +3 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/71_academic_paper.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/72_legal_contract.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +1 -0
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +5 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +2 -2
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/85_header_footer_access.json +7 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +1 -1
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/47_font_size_round_trip.json → athena_python_docx-0.25.0/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +1 -1
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +7 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +1 -0
- athena_python_docx-0.25.0/tests/fidelity/op_snapshots/98_style_contains_check.json +6 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +2 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +13 -6
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +2 -2
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +2 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +2 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw11_header_text.json +2 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +4 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +4 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +1 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/intentional_deviations.json +0 -3
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_athena_extensions_registry.py +3 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_block_not_found_error.py +29 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_buffer.py +43 -6
- athena_python_docx-0.25.0/tests/test_cell_borders_spec.py +100 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_inner_format_stash.py +4 -2
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_commands.py +19 -7
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_http_transport.py +10 -10
- athena_python_docx-0.25.0/tests/test_inline_shape_alt_text.py +230 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_iter_inner_content.py +3 -3
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_list_styles.py +8 -3
- athena_python_docx-0.25.0/tests/test_page_break.py +49 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_parity_misc.py +2 -2
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_parity_round2.py +19 -5
- athena_python_docx-0.25.0/tests/test_pdf_export_and_page_renders.py +321 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_phase_a_behavior.py +2 -1
- athena_python_docx-0.25.0/tests/test_phase_b_headers_footers.py +471 -0
- athena_python_docx-0.25.0/tests/test_run_identity.py +306 -0
- athena_python_docx-0.25.0/tests/test_run_mark_inheritance.py +414 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_paragraph_style_strict.py +2 -1
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_run_add_break.py +7 -7
- athena_python_docx-0.25.0/tests/test_style_names.py +272 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_wire_contract.py +23 -6
- athena_python_docx-0.23.0/docx/styles/__init__.py +0 -1
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -5
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -6
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -3
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -6
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -5
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -8
- athena_python_docx-0.23.0/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -6
- athena_python_docx-0.23.0/tests/test_phase_b_headers_footers.py +0 -281
- athena_python_docx-0.23.0/tests/test_run_mark_inheritance.py +0 -88
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/.gitignore +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/CLAUDE.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/DOCX_EXEC_LAB.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/README.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_athena_extension.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_batching.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_citations.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_execution.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_http.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_ptc.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_references.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/_table_styles.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/api.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/bookmarks.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/charts.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/comments.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/enum/section.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/enum/shape.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/enum/style.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/enum/table.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/enum/text.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/exceptions.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/fields.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/footnotes.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/math.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/oxml/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/revisions.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/sdt.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/session.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/settings.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/shared.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/font.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/toc.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/docx/typing.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/docx_exec_lab.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/dump_wire_fixtures.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/release.sh +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/smoke_test_block_not_found.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/scripts/validate_find_replace_asset.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/conftest.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/firm_templates/README.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/firm_templates/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/firm_templates/_runner.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/firm_templates/extractor.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- /athena_python_docx-0.23.0/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json → /athena_python_docx-0.25.0/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- /athena_python_docx-0.23.0/tests/fidelity/op_snapshots/98_style_contains_check.json → /athena_python_docx-0.25.0/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
- /athena_python_docx-0.23.0/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json → /athena_python_docx-0.25.0/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/README.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/introspect.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/reports/gap_report.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/snapshots/athena_latest.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_add_section_extract_items.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_athena_extension_creates_session_open.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_athena_extensions_contract.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_add_table_not_supported.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_fastpath_runs.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_inner_run_format_stash.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_inner_run_guard.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_text_plain_fastpath.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_cell_text_replace_semantics.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_citations.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_collapsed_range_format.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_comments.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_document_asset_id_property.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_document_clear.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_document_create_from_template.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_document_factory_validation.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_docx_exec_lab.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_docx_exec_lab_server.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_execution_scope.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_find_replace_session_open.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_hyperlink_coalescing.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_image_url_data_uri.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_insert_deferred.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_merged_cell_secondary_slot.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_oxml_shim.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_paragraph_text_len_cache.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_partial_failure_cascade.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_positional_cell_id.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_postproc_ref_restore.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_ptc.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_run_add_picture_anchor.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_add_run.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_enum_section.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_inline_shape.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_paragraph_style.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_row_col_cell.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_run_style.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_run_style_strict.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_run_text.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_run_underline.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_section_dimensions.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_section_onoff.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_silent_stub_table_layout.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_table_set_cell_perf.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_table_style_id_resolution.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_temporarily_unavailable.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_validate_find_replace_asset_script.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_xml_attr_guard.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.23.0 → athena_python_docx-0.25.0}/uv.lock +0 -0
|
@@ -268,7 +268,14 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
268
268
|
inside_horizontal=, inside_vertical=)`** + **`_Cell.borders`**
|
|
269
269
|
property + **`Table.set_borders(...)`** — per-edge border control.
|
|
270
270
|
python-docx issue #201 (5 👍), #1238 demand a cell-borders API.
|
|
271
|
-
Routes through `SetCellBorders` / `SetTableBorders`.
|
|
271
|
+
Routes through `SetCellBorders` / `SetTableBorders`. Each edge is
|
|
272
|
+
`{"style", "size_pt", "color", "space"}` (points; `sizePt` also
|
|
273
|
+
accepted) or `"single 0.5pt #D9D9D9"`; the SDK validates it and sends
|
|
274
|
+
`{style, sizePt, color: "#RRGGBB", space}`. Any other key (`val`, `sz`,
|
|
275
|
+
`size`, `width`) raises `ValueError` naming the accepted form, and
|
|
276
|
+
docx-studio refuses it too (`InvalidBorderSpec`): such edges were once
|
|
277
|
+
stored without their size and colour, and one with no style broke every
|
|
278
|
+
later export of the document.
|
|
272
279
|
|
|
273
280
|
- **`_Cell.set_shading(*, fill=, pattern=, color=)`** +
|
|
274
281
|
**`_Cell.shading`** property — cell fill / shading. python-docx
|
|
@@ -370,7 +377,29 @@ Issue numbers reference `python-openxml/python-docx`.
|
|
|
370
377
|
include_revisions=False) -> bytes`** — render to PDF. python-docx
|
|
371
378
|
issue #113 was closed as
|
|
372
379
|
wontfix upstream but the request keeps recurring. Routes through
|
|
373
|
-
`ExportPDF` (
|
|
380
|
+
`ExportPDF` (0.25.0+ opens the session first and sends buffered edits in
|
|
381
|
+
the same request). DOCX Studio lays the document out with the engine the
|
|
382
|
+
editor paginates with and paints every page onto Skia's PDF backend: a
|
|
383
|
+
vector PDF with selectable text in embedded fonts that matches
|
|
384
|
+
`render_page` — no LibreOffice anywhere, so never install it in a sandbox.
|
|
385
|
+
`quality` is `"draft"` (pictures as JPEG), `"standard"` (default) or
|
|
386
|
+
`"high"`; `include_revisions=True` shows tracked changes as markup (the
|
|
387
|
+
default renders them accepted); `destination` writes a local file and
|
|
388
|
+
refuses asset ids and URLs with `ValueError`. A deployment that cannot
|
|
389
|
+
produce the PDF raises `DocxUnsupportedCommandError` naming the reason.
|
|
390
|
+
`GET /docs/:id/export.pdf` serves the same PDF read-only (VIEW gate).
|
|
391
|
+
|
|
392
|
+
- **`Document.render_page(page_number=1, scale=2, as_pil=False)`** +
|
|
393
|
+
**`Document.screenshot_to_path(path, page=1, scale=2) -> Path`** +
|
|
394
|
+
**`Document.page_count`** (0.25.0+) — look at the document as the editor
|
|
395
|
+
lays it out: one page (numbered from 1) as PNG bytes or a Pillow image,
|
|
396
|
+
written to disk, and the page count. Names follow
|
|
397
|
+
athena-python-pptx's `slide.render(scale=2, as_pil=True)` and
|
|
398
|
+
athena-openpyxl's `Worksheet.screenshot_to_path`. Buffered edits flush
|
|
399
|
+
first. Backed by docx-studio's read-only `GET /docs/:id/pages/:n.png` and
|
|
400
|
+
`GET /docs/:id/layout.json` (VIEW gate, the SDK's API key); a page past
|
|
401
|
+
the end raises `PageOutOfRangeError` (an `IndexError`) carrying
|
|
402
|
+
`page_count`.
|
|
374
403
|
|
|
375
404
|
- **`Document.clear()`** (0.14.0+) — empty the document body so subsequent
|
|
376
405
|
writes start from a clean slate. python-docx has no equivalent (it
|
|
@@ -754,12 +783,19 @@ architecture or a DOCX Studio constraint:
|
|
|
754
783
|
cheaply enumerate every cell. `(0,0)` and in-range negative indices
|
|
755
784
|
behave identically to upstream.
|
|
756
785
|
|
|
757
|
-
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
786
|
+
- **Unknown paragraph style names raise only when Word has no such
|
|
787
|
+
built-in.** Style names and ids follow python-docx: `.name` is the UI
|
|
788
|
+
name (`"Heading 1"`), `.style_id` the id (`"Heading1"`), read from the
|
|
789
|
+
document's style table (docx-studio `Info` with `includeStyles`, fetched
|
|
790
|
+
once per session in `docx/styles/_table.py`). A style set by name or id
|
|
791
|
+
is sent as the document's id, and an unstyled paragraph reads as the
|
|
792
|
+
default paragraph style. `paragraph.style = …` raises `KeyError` for a
|
|
793
|
+
name the document defines no style for, like python-docx — except Word's
|
|
794
|
+
built-in styles (`"Heading 4"`, `"List Bullet"`, `"Title"`…), which many
|
|
795
|
+
templates never write into `styles.xml` until used. Those are sent by
|
|
796
|
+
their Word id; Word and docx-studio apply the built-in definition. Against
|
|
797
|
+
an older docx-studio (no style table) a built-in name is sent as its id
|
|
798
|
+
and anything else as given.
|
|
763
799
|
|
|
764
800
|
- **`Document.add_table(rows, cols)` requires `rows >= 1` and
|
|
765
801
|
`cols >= 1`.** Upstream allows a 0-row/0-col grid you later populate,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.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>
|
|
@@ -341,6 +341,10 @@ class CommandBuffer:
|
|
|
341
341
|
self._asset_id: str = asset_id
|
|
342
342
|
self._pending: list[Command] = []
|
|
343
343
|
self._lock: threading.RLock = threading.RLock()
|
|
344
|
+
# Held for the whole of a flush (drain + POST), so batches reach the
|
|
345
|
+
# server one at a time and in order, and a flush returns only once
|
|
346
|
+
# every batch taken before it has been delivered.
|
|
347
|
+
self._post_lock: threading.RLock = threading.RLock()
|
|
344
348
|
self._timer: threading.Timer | None = None
|
|
345
349
|
self._auto_flush_seconds: float = auto_flush_seconds
|
|
346
350
|
self._closed: bool = False
|
|
@@ -532,6 +536,14 @@ class CommandBuffer:
|
|
|
532
536
|
proxies pick up real ids without the caller ever seeing the
|
|
533
537
|
UUID swap.
|
|
534
538
|
"""
|
|
539
|
+
# One batch in flight at a time: a flush that finds the queue empty
|
|
540
|
+
# still waits for a batch the idle timer already took and is posting,
|
|
541
|
+
# so a read that follows (a query, a page render) sees those edits and
|
|
542
|
+
# a failure of that batch is raised here.
|
|
543
|
+
with self._post_lock:
|
|
544
|
+
return self._flush_in_order()
|
|
545
|
+
|
|
546
|
+
def _flush_in_order(self) -> list[Any]:
|
|
535
547
|
self._assert_current_execution()
|
|
536
548
|
self._raise_deferred_flush_error()
|
|
537
549
|
with self._lock:
|
|
@@ -599,7 +611,12 @@ class CommandBuffer:
|
|
|
599
611
|
still get their real ids written back via the same flush-time
|
|
600
612
|
rewrite that :meth:`flush` does.
|
|
601
613
|
"""
|
|
614
|
+
with self._post_lock:
|
|
615
|
+
return self._eager_flush_in_order(cmd)
|
|
616
|
+
|
|
617
|
+
def _eager_flush_in_order(self, cmd: Command) -> Any:
|
|
602
618
|
self._assert_current_execution()
|
|
619
|
+
self._raise_deferred_flush_error()
|
|
603
620
|
with self._lock:
|
|
604
621
|
self._cancel_timer_locked()
|
|
605
622
|
pending = self._pending
|
|
@@ -31,15 +31,16 @@ from __future__ import annotations
|
|
|
31
31
|
|
|
32
32
|
import json
|
|
33
33
|
import os
|
|
34
|
+
import urllib.parse
|
|
34
35
|
import warnings
|
|
35
|
-
from typing import Any
|
|
36
|
+
from typing import Any, TypedDict
|
|
36
37
|
|
|
37
38
|
import requests
|
|
38
39
|
from requests.adapters import HTTPAdapter
|
|
39
40
|
from urllib3.util.retry import Retry
|
|
40
41
|
|
|
41
42
|
from docx._buffer import CommandBuffer
|
|
42
|
-
from docx._timeouts import timeout_for_commands
|
|
43
|
+
from docx._timeouts import DEFAULT_OP_TIMEOUT_SECONDS, timeout_for_commands
|
|
43
44
|
from docx.commands import (
|
|
44
45
|
BlocksList,
|
|
45
46
|
BookmarksGet,
|
|
@@ -177,6 +178,7 @@ from docx.errors import (
|
|
|
177
178
|
DocxUnsupportedCommandWarning,
|
|
178
179
|
NotFoundError,
|
|
179
180
|
SessionError,
|
|
181
|
+
_missing_block_id,
|
|
180
182
|
)
|
|
181
183
|
|
|
182
184
|
|
|
@@ -338,6 +340,19 @@ _TABLE_CLIENT_ID_HINT: str = (
|
|
|
338
340
|
)
|
|
339
341
|
|
|
340
342
|
|
|
343
|
+
_HEADER_FOOTER_PARAGRAPH_HINT: str = (
|
|
344
|
+
"\n\nHint: this paragraph is in a header or footer (``section.header`` / "
|
|
345
|
+
"``section.footer``), not in a table cell. docx-studio found no "
|
|
346
|
+
"paragraph with this id: the header/footer part that held it was "
|
|
347
|
+
"deleted, or the docx-studio server predates run and paragraph edits "
|
|
348
|
+
"in headers and footers. Add the paragraph again with "
|
|
349
|
+
"``section.footer.add_paragraph(text)`` (or ``section.header``) and edit "
|
|
350
|
+
"the paragraph it returns; until the server supports those edits, pass "
|
|
351
|
+
"the paragraph's full text to ``add_paragraph`` and set only its style "
|
|
352
|
+
"and alignment."
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
341
356
|
def _looks_like_not_found(parsed: dict) -> bool:
|
|
342
357
|
"""Inspect a docx-studio partial-failure dict and decide whether it
|
|
343
358
|
describes a "no such entity" miss (vs. a real transport / validation
|
|
@@ -446,7 +461,9 @@ def _build_retry_session() -> requests.Session:
|
|
|
446
461
|
"other": 0,
|
|
447
462
|
"backoff_factor": 2.0,
|
|
448
463
|
"status_forcelist": (429, 502, 503, 504),
|
|
449
|
-
|
|
464
|
+
# GET serves only the read-only render routes (page PNGs, layout),
|
|
465
|
+
# which are always safe to retry.
|
|
466
|
+
"allowed_methods": frozenset(["POST", "GET"]),
|
|
450
467
|
"raise_on_status": False,
|
|
451
468
|
"respect_retry_after_header": True,
|
|
452
469
|
}
|
|
@@ -482,6 +499,16 @@ def _parse_retry_after(value: str | None) -> float | None:
|
|
|
482
499
|
return None
|
|
483
500
|
|
|
484
501
|
|
|
502
|
+
def _delegation_headers() -> dict[str, str]:
|
|
503
|
+
"""The user a service-key caller acts for, as ``X-Custom-Attributions``.
|
|
504
|
+
|
|
505
|
+
docx-studio's ownership gate reads it (``delegatedUserId``) and agora's
|
|
506
|
+
``/access`` refuses a service-key call that delegates no one, so every
|
|
507
|
+
request carries it: commands and the read-only GETs alike."""
|
|
508
|
+
custom_attr = os.environ.get("ATHENA_DOCX_CUSTOM_ATTRIBUTIONS")
|
|
509
|
+
return {"X-Custom-Attributions": custom_attr} if custom_attr else {}
|
|
510
|
+
|
|
511
|
+
|
|
485
512
|
def _http_post_json(
|
|
486
513
|
*,
|
|
487
514
|
session: requests.Session,
|
|
@@ -489,17 +516,21 @@ def _http_post_json(
|
|
|
489
516
|
api_key: str,
|
|
490
517
|
body: dict,
|
|
491
518
|
timeout: float = 60.0,
|
|
519
|
+
header_footer_block_ids: "set[str] | frozenset[str]" = frozenset(),
|
|
492
520
|
) -> dict:
|
|
493
|
-
"""POST JSON, parse JSON, raise typed errors on non-2xx.
|
|
521
|
+
"""POST JSON, parse JSON, raise typed errors on non-2xx.
|
|
522
|
+
|
|
523
|
+
``header_footer_block_ids`` are the header/footer paragraph ids this
|
|
524
|
+
document's ``_HeaderFooter.add_paragraph`` returned: a ``block not
|
|
525
|
+
found`` on one of them gets the header/footer hint.
|
|
526
|
+
"""
|
|
494
527
|
headers = {
|
|
495
528
|
"Content-Type": "application/json",
|
|
496
529
|
"Authorization": f"Bearer {api_key}",
|
|
497
530
|
"Accept": "application/json",
|
|
498
531
|
"User-Agent": _user_agent(),
|
|
532
|
+
**_delegation_headers(),
|
|
499
533
|
}
|
|
500
|
-
custom_attr = os.environ.get("ATHENA_DOCX_CUSTOM_ATTRIBUTIONS")
|
|
501
|
-
if custom_attr:
|
|
502
|
-
headers["X-Custom-Attributions"] = custom_attr
|
|
503
534
|
try:
|
|
504
535
|
resp = session.post(
|
|
505
536
|
url,
|
|
@@ -621,7 +652,9 @@ def _http_post_json(
|
|
|
621
652
|
isinstance(cmd_type, str) and cmd_type in _PARAGRAPH_TARGETING_COMMANDS
|
|
622
653
|
)
|
|
623
654
|
table_query = isinstance(cmd_type, str) and cmd_type in _TABLE_QUERY_COMMANDS
|
|
624
|
-
if
|
|
655
|
+
if _missing_block_id(err_obj.get("message")) in header_footer_block_ids:
|
|
656
|
+
hint = _HEADER_FOOTER_PARAGRAPH_HINT
|
|
657
|
+
elif paragraph_targeting:
|
|
625
658
|
hint = _CELL_PARAGRAPH_HINT
|
|
626
659
|
elif table_query:
|
|
627
660
|
hint = _TABLE_CLIENT_ID_HINT
|
|
@@ -696,6 +729,63 @@ def _warn_skipped_commands(skipped: list[Any]) -> None:
|
|
|
696
729
|
warnings.warn(message, DocxUnsupportedCommandWarning, stacklevel=2)
|
|
697
730
|
|
|
698
731
|
|
|
732
|
+
def _http_get(
|
|
733
|
+
*,
|
|
734
|
+
session: requests.Session,
|
|
735
|
+
url: str,
|
|
736
|
+
api_key: str,
|
|
737
|
+
params: dict[str, str] | None = None,
|
|
738
|
+
timeout: float = DEFAULT_OP_TIMEOUT_SECONDS,
|
|
739
|
+
) -> requests.Response:
|
|
740
|
+
"""GET one of docx-studio's read-only routes; raise typed errors on non-2xx.
|
|
741
|
+
|
|
742
|
+
A 404 keeps the server's JSON body as the error payload, so a caller can
|
|
743
|
+
tell a page past the end (``error: "PageNotFound"``, ``pageCount``) from an
|
|
744
|
+
asset the caller cannot read.
|
|
745
|
+
"""
|
|
746
|
+
headers = {
|
|
747
|
+
"Authorization": f"Bearer {api_key}",
|
|
748
|
+
"User-Agent": _user_agent(),
|
|
749
|
+
**_delegation_headers(),
|
|
750
|
+
}
|
|
751
|
+
try:
|
|
752
|
+
resp = session.get(url, params=params, headers=headers, timeout=timeout)
|
|
753
|
+
except requests.Timeout as e:
|
|
754
|
+
raise SessionError(f"docx-studio timed out at {url}: {e}") from e
|
|
755
|
+
except requests.ConnectionError as e:
|
|
756
|
+
raise SessionError(f"Unable to reach docx-studio at {url}: {e}") from e
|
|
757
|
+
if 200 <= resp.status_code < 300:
|
|
758
|
+
return resp
|
|
759
|
+
err_body: str = resp.text
|
|
760
|
+
if resp.status_code in (401, 403):
|
|
761
|
+
raise AuthenticationError(
|
|
762
|
+
f"docx-studio rejected the request (HTTP {resp.status_code}): {err_body}",
|
|
763
|
+
)
|
|
764
|
+
if resp.status_code == 404:
|
|
765
|
+
try:
|
|
766
|
+
parsed: object = resp.json()
|
|
767
|
+
except ValueError:
|
|
768
|
+
parsed = None
|
|
769
|
+
payload: dict[str, Any] = {"status": 404, "body": err_body}
|
|
770
|
+
if isinstance(parsed, dict):
|
|
771
|
+
payload.update(parsed)
|
|
772
|
+
raise NotFoundError(f"docx-studio returned HTTP 404: {err_body}", payload=payload)
|
|
773
|
+
if resp.status_code in (502, 503, 504):
|
|
774
|
+
raise DocxStudioTemporarilyUnavailable(
|
|
775
|
+
f"docx-studio returned HTTP {resp.status_code}: {err_body}",
|
|
776
|
+
http_status=resp.status_code,
|
|
777
|
+
retry_after_seconds=_parse_retry_after(resp.headers.get("Retry-After")),
|
|
778
|
+
)
|
|
779
|
+
raise DocxError(f"docx-studio returned HTTP {resp.status_code}: {err_body}")
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
class RenderedPagePng(TypedDict):
|
|
783
|
+
"""One page from ``GET /docs/:id/pages/:n.png``."""
|
|
784
|
+
|
|
785
|
+
png: bytes
|
|
786
|
+
page_count: int
|
|
787
|
+
|
|
788
|
+
|
|
699
789
|
# ---------------------------------------------------------------------------
|
|
700
790
|
# HttpClient — the bare HTTP transport. Stateless wrt batching.
|
|
701
791
|
# ---------------------------------------------------------------------------
|
|
@@ -720,6 +810,8 @@ class HttpClient:
|
|
|
720
810
|
self._base_url: str = base_url.rstrip("/")
|
|
721
811
|
self._api_key: str = api_key
|
|
722
812
|
self._session: requests.Session = session or _build_retry_session()
|
|
813
|
+
# Header/footer paragraph ids, for the ``block not found`` hint.
|
|
814
|
+
self.header_footer_block_ids: set[str] = set()
|
|
723
815
|
|
|
724
816
|
def close(self) -> None:
|
|
725
817
|
"""Release the pooled HTTP connections held by ``requests.Session``.
|
|
@@ -801,6 +893,7 @@ class HttpClient:
|
|
|
801
893
|
api_key=self._api_key,
|
|
802
894
|
body=body,
|
|
803
895
|
timeout=timeout_for_commands(commands=commands),
|
|
896
|
+
header_footer_block_ids=self.header_footer_block_ids,
|
|
804
897
|
)
|
|
805
898
|
applied = resp.get("applied")
|
|
806
899
|
# Commands the engine doesn't implement yet are reported in
|
|
@@ -852,6 +945,38 @@ class HttpClient:
|
|
|
852
945
|
skipped_by_index[idx] = entry
|
|
853
946
|
return results, skipped_by_index
|
|
854
947
|
|
|
948
|
+
def _doc_url(self, asset_id: str, path: str) -> str:
|
|
949
|
+
return f"{self._base_url}/docs/{urllib.parse.quote(asset_id, safe='')}/{path}"
|
|
950
|
+
|
|
951
|
+
def get_layout(self, asset_id: str) -> dict[str, Any]:
|
|
952
|
+
"""``GET /docs/:id/layout.json`` — the page count and page sizes as
|
|
953
|
+
DOCX Studio's layout engine paginates the document (VIEW gate)."""
|
|
954
|
+
resp = _http_get(
|
|
955
|
+
session=self._session,
|
|
956
|
+
url=self._doc_url(asset_id, "layout.json"),
|
|
957
|
+
api_key=self._api_key,
|
|
958
|
+
)
|
|
959
|
+
try:
|
|
960
|
+
data: object = resp.json()
|
|
961
|
+
except ValueError as e:
|
|
962
|
+
raise DocxError(f"docx-studio returned a non-JSON layout: {resp.text[:200]!r}") from e
|
|
963
|
+
if not isinstance(data, dict):
|
|
964
|
+
raise DocxError(f"docx-studio returned an unexpected layout: {data!r}")
|
|
965
|
+
return data
|
|
966
|
+
|
|
967
|
+
def render_page_png(self, asset_id: str, page_number: int, *, scale: float) -> RenderedPagePng:
|
|
968
|
+
"""``GET /docs/:id/pages/:n.png?scale=`` — one page (1-indexed) as PNG,
|
|
969
|
+
from the same layout engine the editor paginates with (VIEW gate)."""
|
|
970
|
+
resp = _http_get(
|
|
971
|
+
session=self._session,
|
|
972
|
+
url=self._doc_url(asset_id, f"pages/{page_number}.png"),
|
|
973
|
+
api_key=self._api_key,
|
|
974
|
+
params={"scale": f"{scale:g}"},
|
|
975
|
+
)
|
|
976
|
+
page_count_header = resp.headers.get("X-Page-Count", "")
|
|
977
|
+
page_count = int(page_count_header) if page_count_header.isdigit() else 0
|
|
978
|
+
return RenderedPagePng(png=resp.content, page_count=page_count)
|
|
979
|
+
|
|
855
980
|
|
|
856
981
|
# ---------------------------------------------------------------------------
|
|
857
982
|
# Path proxy — translates dotted paths + camelCase param dicts into typed
|
|
@@ -1045,6 +1170,7 @@ _FIELD_RENAMES: dict[str, dict[str, str]] = {
|
|
|
1045
1170
|
# path-proxy snake-case transform leaves `in` as a Python keyword
|
|
1046
1171
|
# collision; rename to `in_` here so the dataclass accepts it.
|
|
1047
1172
|
"find_replace": {"in": "in_"},
|
|
1173
|
+
"blocks.list": {"in": "in_"},
|
|
1048
1174
|
"find_text": {"in": "in_"},
|
|
1049
1175
|
"iter_runs": {"in": "in_"},
|
|
1050
1176
|
"numbering.list": {"in": "in_"},
|
|
@@ -2055,7 +2055,7 @@ def _border_spec_to_ooxml_attrs(spec: dict[str, Any]) -> dict[str, str]:
|
|
|
2055
2055
|
out["sz"] = str(int(round(float(size_pt) * 8)))
|
|
2056
2056
|
except (TypeError, ValueError):
|
|
2057
2057
|
pass
|
|
2058
|
-
space = spec.get("space_pt")
|
|
2058
|
+
space = spec.get("space", spec.get("space_pt"))
|
|
2059
2059
|
if space is not None:
|
|
2060
2060
|
try:
|
|
2061
2061
|
out["space"] = str(int(round(float(space))))
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Run order of a paragraph, as ``Paragraph.add_run`` created its runs.
|
|
2
|
+
|
|
3
|
+
A picture takes no text offset, so two runs can end at the same offset:
|
|
4
|
+
``r1 = p.add_run("A"); r2 = p.add_run(); r2.add_picture(img)`` puts ``r2`` and
|
|
5
|
+
its picture at offset 1, where ``r1`` ends. The offset alone cannot tell the
|
|
6
|
+
server whether content for ``r1`` goes before or after that picture. The SDK
|
|
7
|
+
knows: ``add_run`` appends each run to the end of its paragraph.
|
|
8
|
+
|
|
9
|
+
This module keeps a record of each such run (its range and the pictures that
|
|
10
|
+
its ``add_picture`` added), in creation order, and gives:
|
|
11
|
+
|
|
12
|
+
- the run identity of a run-end insert (:func:`run_ref`): the first picture
|
|
13
|
+
that a later run holds at the same offset (for a read segment that ends
|
|
14
|
+
inside its run, also the run's own picture there). The server puts the
|
|
15
|
+
content in front of it; without it, after the pictures at the offset.
|
|
16
|
+
- current ranges: when a run grows or shrinks, its pictures and the runs after
|
|
17
|
+
it move (:func:`note_length_change`), and so do the live run proxies. A
|
|
18
|
+
replace cuts every record that it touches.
|
|
19
|
+
|
|
20
|
+
A record lives as long as its paragraph proxy, not as long as its run proxy,
|
|
21
|
+
so ``p.add_run().add_picture(img)`` keeps the picture's place. The records of
|
|
22
|
+
every paragraph proxy of one block (``doc.paragraphs`` makes new ones) are one
|
|
23
|
+
order. A read run (``paragraph.runs``) stands for the recorded run that holds
|
|
24
|
+
its last character; a read run over text that no recorded run holds counts as
|
|
25
|
+
a run before all of them. The order lives in the SDK process only; the
|
|
26
|
+
document stores none of it.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import itertools
|
|
32
|
+
import weakref
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from typing import TYPE_CHECKING, Any
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from docx.text.run import Run
|
|
38
|
+
|
|
39
|
+
_SEQ = itertools.count()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(eq=False)
|
|
43
|
+
class RunRecord:
|
|
44
|
+
"""One run that ``add_run`` created."""
|
|
45
|
+
|
|
46
|
+
seq: int
|
|
47
|
+
range: tuple[int, int]
|
|
48
|
+
run: "weakref.ref[Run]"
|
|
49
|
+
# ``(picture id, offset)`` of each picture that the run's ``add_picture`` added.
|
|
50
|
+
pictures: list[tuple[str, int]] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Paragraph proxy -> the records of the runs its ``add_run`` created. A record
|
|
54
|
+
# holds no reference to the paragraph or session, so an entry goes with its
|
|
55
|
+
# paragraph proxy. Keyed by the proxy, not by its id: the first flush moves a
|
|
56
|
+
# new paragraph from its client id to the server id, and the runs created
|
|
57
|
+
# before that keep the client id.
|
|
58
|
+
_ORDERS: "weakref.WeakKeyDictionary[Any, list[RunRecord]]" = weakref.WeakKeyDictionary()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _records_of(run: "Run") -> list[RunRecord]:
|
|
62
|
+
paragraph = getattr(run, "_paragraph", None)
|
|
63
|
+
ids = {run._block_id}
|
|
64
|
+
if paragraph is not None:
|
|
65
|
+
ids.add(paragraph._node_id)
|
|
66
|
+
records: list[RunRecord] = []
|
|
67
|
+
for owner, owner_records in list(_ORDERS.items()):
|
|
68
|
+
if owner is paragraph or (owner._session is run._session and owner._node_id in ids):
|
|
69
|
+
records.extend(owner_records)
|
|
70
|
+
records.sort(key=lambda record: record.seq)
|
|
71
|
+
return records
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def register_run(run: "Run") -> None:
|
|
75
|
+
"""Record ``run`` as the last run of its paragraph (``Paragraph.add_run``)."""
|
|
76
|
+
record = RunRecord(seq=next(_SEQ), range=run._range, run=weakref.ref(run))
|
|
77
|
+
_ORDERS.setdefault(run._paragraph, []).append(record)
|
|
78
|
+
run._run_record = record
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def forget_paragraph(paragraph: Any) -> None:
|
|
82
|
+
"""Drop the order of a paragraph whose runs are gone (``Paragraph.text = ...``)."""
|
|
83
|
+
for owner in list(_ORDERS.keys()):
|
|
84
|
+
if owner is paragraph or (owner._session is paragraph._session and owner._node_id == paragraph._node_id):
|
|
85
|
+
_ORDERS.pop(owner, None)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _place(
|
|
89
|
+
run: "Run", span: tuple[int, int], records: list[RunRecord] | None = None
|
|
90
|
+
) -> tuple[RunRecord | None, list[RunRecord]]:
|
|
91
|
+
"""The record of ``run`` (over ``span``), if any, and the records after it."""
|
|
92
|
+
if records is None:
|
|
93
|
+
records = _records_of(run)
|
|
94
|
+
# The run itself, or the text that its ``add_text`` returned.
|
|
95
|
+
own = getattr(run, "_run_record", None) or getattr(run, "_owner_record", None)
|
|
96
|
+
for index, record in enumerate(records):
|
|
97
|
+
if record is own:
|
|
98
|
+
return record, records[index + 1 :]
|
|
99
|
+
# ``paragraph.runs`` makes a new proxy of a kept run: over the same range,
|
|
100
|
+
# or over a part of it (a read splits a run at a tab, a break or a change
|
|
101
|
+
# of format). The proxy stands for the run that holds its last character.
|
|
102
|
+
if span[0] != span[1]:
|
|
103
|
+
last = span[1] - 1
|
|
104
|
+
for index, record in enumerate(records):
|
|
105
|
+
if record.range[0] <= last < record.range[1]:
|
|
106
|
+
return record, records[index + 1 :]
|
|
107
|
+
return None, records
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _set_range(record: RunRecord, value: tuple[int, int]) -> None:
|
|
111
|
+
record.range = value
|
|
112
|
+
proxy = record.run()
|
|
113
|
+
if proxy is not None:
|
|
114
|
+
proxy._range = value
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def note_length_change(
|
|
118
|
+
run: "Run",
|
|
119
|
+
at: int,
|
|
120
|
+
delta: int,
|
|
121
|
+
*,
|
|
122
|
+
replaced: tuple[int, int] | None = None,
|
|
123
|
+
before: str | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""``run`` (already over its new range) changed its text by ``delta`` units.
|
|
126
|
+
|
|
127
|
+
Without ``replaced``: an insert of ``delta`` units at ``at`` (the end of
|
|
128
|
+
``run``), in front of the picture ``before`` when the command named one
|
|
129
|
+
(else after the pictures at ``at``). With ``replaced``: ``Run.text``
|
|
130
|
+
replaced the old range ``replaced``; the server keeps the pictures at its
|
|
131
|
+
start and end and removes the ones inside.
|
|
132
|
+
|
|
133
|
+
The text belongs to the record of ``run`` (see :func:`_place`). Every
|
|
134
|
+
record that the replaced range touches is cut; the records and pictures
|
|
135
|
+
after the change move.
|
|
136
|
+
"""
|
|
137
|
+
if delta == 0 and replaced is None:
|
|
138
|
+
return
|
|
139
|
+
records = _records_of(run)
|
|
140
|
+
if replaced is None:
|
|
141
|
+
start = end = at
|
|
142
|
+
inserted = delta
|
|
143
|
+
old_span = (run._range[0], run._range[1] - delta)
|
|
144
|
+
else:
|
|
145
|
+
start, end = replaced
|
|
146
|
+
inserted = (end - start) + delta
|
|
147
|
+
old_span = replaced
|
|
148
|
+
owner = _place(run, old_span, records)[0]
|
|
149
|
+
owner_index = records.index(owner) if owner is not None else -1
|
|
150
|
+
removed = end - start
|
|
151
|
+
|
|
152
|
+
def cut(x: int) -> int:
|
|
153
|
+
return x if x <= start else start if x < end else x - removed
|
|
154
|
+
|
|
155
|
+
# The pictures at ``at`` that an insert goes in front of: ``before`` and
|
|
156
|
+
# the ones after it, in document order (records in order, then the
|
|
157
|
+
# pictures of each record in order).
|
|
158
|
+
moved_at: set[str] = set()
|
|
159
|
+
if replaced is None and before is not None:
|
|
160
|
+
order = [pid for record in records for pid, off in record.pictures if off == at]
|
|
161
|
+
if before in order:
|
|
162
|
+
moved_at = set(order[order.index(before) :])
|
|
163
|
+
|
|
164
|
+
for index, record in enumerate(records):
|
|
165
|
+
rec_start, rec_end = cut(record.range[0]), cut(record.range[1])
|
|
166
|
+
if record is owner:
|
|
167
|
+
rec_end += inserted
|
|
168
|
+
elif index > owner_index and rec_start >= start:
|
|
169
|
+
rec_start, rec_end = rec_start + inserted, rec_end + inserted
|
|
170
|
+
_set_range(record, (rec_start, rec_end))
|
|
171
|
+
pictures: list[tuple[str, int]] = []
|
|
172
|
+
for pid, off in record.pictures:
|
|
173
|
+
if replaced is None:
|
|
174
|
+
pictures.append((pid, off + delta if off > at or pid in moved_at else off))
|
|
175
|
+
elif off <= start:
|
|
176
|
+
pictures.append((pid, off))
|
|
177
|
+
elif off >= end:
|
|
178
|
+
pictures.append((pid, off + delta))
|
|
179
|
+
# The moves keep document order; a stable sort by offset keeps it so.
|
|
180
|
+
pictures.sort(key=lambda item: item[1])
|
|
181
|
+
record.pictures = pictures
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def note_picture(run: "Run", picture_id: str, offset: int, *, before: str | None = None) -> None:
|
|
185
|
+
"""``run.add_picture`` put the picture ``picture_id`` at ``offset``, in front of ``before``.
|
|
186
|
+
|
|
187
|
+
A record keeps its pictures in document order: the new one goes in front
|
|
188
|
+
of ``before``, else after the pictures at ``offset`` and before the ones
|
|
189
|
+
after it (the server puts a picture after the pictures at its offset).
|
|
190
|
+
"""
|
|
191
|
+
record = _place(run, run._range)[0]
|
|
192
|
+
if record is None:
|
|
193
|
+
return
|
|
194
|
+
ids = [pid for pid, _ in record.pictures]
|
|
195
|
+
if before in ids:
|
|
196
|
+
index = ids.index(before)
|
|
197
|
+
else:
|
|
198
|
+
index = next((i for i, (_, off) in enumerate(record.pictures) if off > offset), len(ids))
|
|
199
|
+
record.pictures.insert(index, (picture_id, offset))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def run_ref(run: "Run") -> dict[str, str] | None:
|
|
203
|
+
"""``{"beforeImageId": id}``: the picture that an insert at the end of ``run`` goes in front of.
|
|
204
|
+
|
|
205
|
+
The first picture of a later run where ``run`` ends. A read segment that
|
|
206
|
+
ends inside its run (a picture of the run between two of its texts) also
|
|
207
|
+
puts the insert in front of the run's own picture there, right after the
|
|
208
|
+
segment's text.
|
|
209
|
+
"""
|
|
210
|
+
end = run._range[1]
|
|
211
|
+
record, later = _place(run, run._range)
|
|
212
|
+
candidates = ([record] if record is not None and end < record.range[1] else []) + later
|
|
213
|
+
for candidate in candidates:
|
|
214
|
+
for picture_id, offset in candidate.pictures:
|
|
215
|
+
if offset == end:
|
|
216
|
+
return {"beforeImageId": picture_id}
|
|
217
|
+
return None
|
|
@@ -22,6 +22,8 @@ _LONG_RUNNING_COMMAND_TYPES: frozenset[str] = frozenset(
|
|
|
22
22
|
# ``Document.clear()`` replaces the whole body story in one write;
|
|
23
23
|
# on a large document that single command can outlast the 60s budget.
|
|
24
24
|
"ClearBody",
|
|
25
|
+
# ``Document.to_pdf()`` lays out and paints every page of the document.
|
|
26
|
+
"ExportPDF",
|
|
25
27
|
}
|
|
26
28
|
)
|
|
27
29
|
|
|
@@ -178,6 +178,22 @@ class Session:
|
|
|
178
178
|
)
|
|
179
179
|
return self._doc_handle
|
|
180
180
|
|
|
181
|
+
@property
|
|
182
|
+
def http_client(self) -> Any:
|
|
183
|
+
"""The opened :class:`HttpClient`, for docx-studio's read-only routes
|
|
184
|
+
outside the command bus (page renders, layout)."""
|
|
185
|
+
self._assert_current_execution()
|
|
186
|
+
if not self._opened:
|
|
187
|
+
raise SessionError(
|
|
188
|
+
"Session not yet opened; call await session.open() first "
|
|
189
|
+
"or use the sync facade via Document().",
|
|
190
|
+
)
|
|
191
|
+
if self._closed:
|
|
192
|
+
raise DocumentClosedError(
|
|
193
|
+
f"Session for asset {self._asset_id} is closed.",
|
|
194
|
+
)
|
|
195
|
+
return self._client
|
|
196
|
+
|
|
181
197
|
async def send_command(self, cmd: Any) -> Any:
|
|
182
198
|
"""Route a pre-built :class:`docx.commands.Command` through the
|
|
183
199
|
buffer.
|