athena-python-docx 0.6.0__tar.gz → 0.6.2__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.6.0 → athena_python_docx-0.6.2}/PKG-INFO +1 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/__init__.py +1 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_buffer.py +5 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_http_doc.py +10 -3
- athena_python_docx-0.6.2/docx/_ptc.py +138 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/commands.py +40 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/comments.py +20 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/document.py +218 -6
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/section.py +18 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/text.py +27 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/section.py +87 -20
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/shape.py +26 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/table.py +118 -19
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/paragraph.py +163 -8
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/run.py +172 -8
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/pyproject.toml +1 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/fake_session.py +34 -0
- athena_python_docx-0.6.0/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/03_runs_with_formatting.json +4 -4
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -1
- athena_python_docx-0.6.2/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +7 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/35_full_report.json +0 -4
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -1
- athena_python_docx-0.6.0/tests/fidelity/op_snapshots/07_font_subscript_superscript.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +3 -3
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -7
- athena_python_docx-0.6.2/tests/fidelity/op_snapshots/54_empty_everything.json +4 -0
- athena_python_docx-0.6.0/tests/fidelity/op_snapshots/03_runs_with_formatting.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/55_single_character_runs.json +2 -5
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -6
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -3
- athena_python_docx-0.6.0/tests/fidelity/op_snapshots/55_single_character_runs.json → athena_python_docx-0.6.2/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +3 -6
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/68_invoice.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/69_newsletter.json +0 -9
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -2
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -6
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -10
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -2
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -3
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -100
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -4
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -3
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -15
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -22
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -8
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -4
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -3
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -3
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -17
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -15
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -11
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -7
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +2 -3
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +6 -1
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -2
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -4
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -6
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -30
- athena_python_docx-0.6.2/tests/fidelity/test_e2e_against_staging.py +523 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/introspect.py +14 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/reports/GAP_ANALYSIS.md +3 -28
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/reports/gap_report.json +9 -1659
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/snapshots/athena_latest.json +83 -2
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +4046 -3115
- athena_python_docx-0.6.2/tests/test_collapsed_range_format.py +190 -0
- athena_python_docx-0.6.2/tests/test_list_styles.py +226 -0
- athena_python_docx-0.6.2/tests/test_paragraph_text_len_cache.py +161 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_ptc.py +55 -3
- athena_python_docx-0.6.2/tests/test_silent_stub_add_paragraph_style.py +67 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_add_run.py +90 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_cell_add_paragraph.py +47 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_comments_add_comment.py +71 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_enum_section.py +83 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_inline_shape.py +105 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_insert_paragraph_before.py +61 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_paragraph_strict.py +170 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_paragraph_style.py +17 -9
- athena_python_docx-0.6.2/tests/test_silent_stub_paragraph_style_strict.py +93 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_row_col_cell.py +181 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_run_add_break.py +82 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_run_style.py +57 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_run_style_strict.py +71 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_run_text.py +71 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_run_underline.py +123 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_section_dimensions.py +96 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_section_onoff.py +83 -0
- athena_python_docx-0.6.2/tests/test_silent_stub_table_layout.py +153 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_wire_contract.py +16 -1
- athena_python_docx-0.6.0/docx/_ptc.py +0 -173
- athena_python_docx-0.6.0/tests/fidelity/op_snapshots/54_empty_everything.json +0 -6
- athena_python_docx-0.6.0/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -18
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/.gitignore +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/CLAUDE.md +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/README.md +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_batching.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_http.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/_image_utils.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/api.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/client.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/style.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/enum/table.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/errors.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/exceptions.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/opc/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/opc/coreprops.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/revisions.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/settings.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/shared.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/styles/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/styles/style.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/styles/styles.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/font.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/hyperlink.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/pagebreak.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/parfmt.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/text/tabstops.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/docx/typing.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/scripts/publish.sh +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/scripts/release.sh +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/scripts/round_trip_smoke.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/conftest.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/METHODOLOGY.md +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/README.md +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/ab_probe_cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/ab_probe_runner.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/auto_gen_cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/binary_round_trip.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/complex_cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/coverage_report.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/extract.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/extreme_cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/local_runner.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/mega_cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshot.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/ours_spec.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/parity_crawl.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/parity_diff.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/real_world_cases.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/round_trip_tests.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/runner.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/fidelity/stock_spec.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/README.md +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/__init__.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/baseline_gaps.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/compare.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/intentional_deviations.json +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/run_parity.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/parity/test_parity_gap.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_batching_perf.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_buffer.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_command_dataclasses.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_commands.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_comments.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_document_create.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_http_transport.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_iter_inner_content.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_merged_cells.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_parity_misc.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_parity_round2.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_phase_a_behavior.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_phase_b_headers_footers.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_phase_c_tables.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_pr19766_review_fixes.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_revisions.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_add_picture.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_comments_get.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_document_audit.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_document_element.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_font_audit.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_header_footer.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_hyperlink.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_misc.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_parfmt.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_run_bool_setters.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_section_audit.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_settings.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_shared_audit.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_style.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_styles.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_table_audit.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_table_cell.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_silent_stub_table_dimensions.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_smoke_integration.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_style_acceptance.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_style_font.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_style_setters_contract.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/tests/test_zod_wire_contract.py +0 -0
- {athena_python_docx-0.6.0 → athena_python_docx-0.6.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-docx
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack
|
|
5
5
|
Project-URL: Homepage, https://athenaintelligence.ai
|
|
6
6
|
Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
|
|
@@ -278,7 +278,11 @@ class CommandBuffer:
|
|
|
278
278
|
# PTC begin: one event per user-facing method call, before any
|
|
279
279
|
# batching. Failures here can't crash the user's code path.
|
|
280
280
|
try:
|
|
281
|
-
cmd._ptc_call_id = _ptc.emit_begin(
|
|
281
|
+
cmd._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
|
|
282
|
+
type(cmd).__name__,
|
|
283
|
+
cmd.to_dict(),
|
|
284
|
+
asset_id=self._asset_id,
|
|
285
|
+
)
|
|
282
286
|
except Exception: # noqa: BLE001
|
|
283
287
|
pass
|
|
284
288
|
|
|
@@ -68,6 +68,8 @@ from docx.commands import (
|
|
|
68
68
|
Insert,
|
|
69
69
|
InsertLineBreak,
|
|
70
70
|
InsertTab,
|
|
71
|
+
ListsAttach,
|
|
72
|
+
ListsCreate,
|
|
71
73
|
ListsMerge,
|
|
72
74
|
ListsSplit,
|
|
73
75
|
MarkdownToFragment,
|
|
@@ -503,9 +505,14 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
|
|
|
503
505
|
"header_footers.get": HeaderFootersGet,
|
|
504
506
|
"header_footers.resolve": HeaderFootersResolve,
|
|
505
507
|
"header_footers.refs.set_linked_to_previous": HeaderFootersRefsSetLinkedToPrevious,
|
|
506
|
-
# Lists & Selection
|
|
507
|
-
#
|
|
508
|
-
#
|
|
508
|
+
# Lists & Selection. ``lists.create`` and ``lists.attach`` are
|
|
509
|
+
# consumed internally by ``Document.add_paragraph`` to make
|
|
510
|
+
# ``style="List Bullet" | "List Number"`` produce real list
|
|
511
|
+
# structure (not just a style attribute on a flat paragraph).
|
|
512
|
+
# ``lists.merge``/``lists.split`` stay typed-bus-only — no
|
|
513
|
+
# python-docx parity surface for them.
|
|
514
|
+
"lists.create": ListsCreate,
|
|
515
|
+
"lists.attach": ListsAttach,
|
|
509
516
|
"lists.merge": ListsMerge,
|
|
510
517
|
"lists.split": ListsSplit,
|
|
511
518
|
"selection.current": SelectionCurrent,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Programmatic Tool Calling (PTC) — client side.
|
|
2
|
+
|
|
3
|
+
Activated only when ``ATHENA_PTC_URL`` is set. The URL is a presigned
|
|
4
|
+
endpoint URL with an HMAC-signed token embedded as ``?t=…``; the token
|
|
5
|
+
carries the (thread, parent_tool_call_id, run) triple and an expiry.
|
|
6
|
+
The SDK doesn't need to know that triple — it just POSTs to the URL
|
|
7
|
+
verbatim, and the server derives identity from the token.
|
|
8
|
+
|
|
9
|
+
Without ``ATHENA_PTC_URL`` set, every call here is a no-op.
|
|
10
|
+
|
|
11
|
+
Emits are fire-and-forget on a background daemon thread:
|
|
12
|
+
|
|
13
|
+
- Never raise into user code.
|
|
14
|
+
- Never block the calling thread.
|
|
15
|
+
- Re-read ``ATHENA_PTC_URL`` on every emit so updates between sandbox
|
|
16
|
+
executions take effect immediately (the SDK module itself is cached
|
|
17
|
+
across runs).
|
|
18
|
+
- Snapshot the URL at ``emit_begin`` time and carry it to ``emit_end``
|
|
19
|
+
so late end events can't be misattributed if a new sandbox run
|
|
20
|
+
swapped in a different URL between begin and end.
|
|
21
|
+
|
|
22
|
+
This module is *intentionally byte-identical* across the docx / pptx /
|
|
23
|
+
xlsx SDKs — the three SDKs have no shared base. Duplicating ~100 LOC
|
|
24
|
+
of stdlib code is cheaper than spinning up a 4th release pipeline.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import queue
|
|
32
|
+
import threading
|
|
33
|
+
import time
|
|
34
|
+
import urllib.error
|
|
35
|
+
import urllib.request
|
|
36
|
+
import uuid
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
_MAX_QUEUE = 4096
|
|
40
|
+
_MAX_BODY = 64 * 1024
|
|
41
|
+
_HTTP_TIMEOUT = 2.0
|
|
42
|
+
|
|
43
|
+
# Singleton state. PTC has exactly one outbox per process.
|
|
44
|
+
_outbox: queue.Queue[tuple[str, dict[str, Any]]] = queue.Queue(maxsize=_MAX_QUEUE)
|
|
45
|
+
_thread_lock = threading.Lock()
|
|
46
|
+
_thread_started = False
|
|
47
|
+
# call_id -> URL snapshot at begin time; lets emit_end target the
|
|
48
|
+
# original run's endpoint even if ATHENA_PTC_URL changed since.
|
|
49
|
+
_call_url: dict[str, str] = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _drain() -> None:
|
|
53
|
+
while True:
|
|
54
|
+
item = _outbox.get()
|
|
55
|
+
if item is None:
|
|
56
|
+
return
|
|
57
|
+
url, body = item
|
|
58
|
+
try:
|
|
59
|
+
raw = json.dumps(body).encode("utf-8")
|
|
60
|
+
if len(raw) > _MAX_BODY:
|
|
61
|
+
key = "args" if body.get("phase") == "begin" else "result"
|
|
62
|
+
body[key] = {"__truncated__": True}
|
|
63
|
+
raw = json.dumps(body).encode("utf-8")
|
|
64
|
+
req = urllib.request.Request(
|
|
65
|
+
url,
|
|
66
|
+
data=raw,
|
|
67
|
+
method="POST",
|
|
68
|
+
headers={"Content-Type": "application/json"},
|
|
69
|
+
)
|
|
70
|
+
urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT).close()
|
|
71
|
+
except (urllib.error.URLError, OSError, ValueError):
|
|
72
|
+
pass # never propagate
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _ensure_thread() -> None:
|
|
76
|
+
global _thread_started
|
|
77
|
+
if _thread_started:
|
|
78
|
+
return
|
|
79
|
+
with _thread_lock:
|
|
80
|
+
if _thread_started:
|
|
81
|
+
return
|
|
82
|
+
threading.Thread(target=_drain, name="athena-ptc", daemon=True).start()
|
|
83
|
+
_thread_started = True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _send(url: str, body: dict[str, Any]) -> None:
|
|
87
|
+
_ensure_thread()
|
|
88
|
+
try:
|
|
89
|
+
_outbox.put_nowait((url, body))
|
|
90
|
+
except queue.Full:
|
|
91
|
+
pass # drop on backpressure; never block user code
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def emit_begin(
|
|
95
|
+
tool_name: str,
|
|
96
|
+
args: dict[str, Any],
|
|
97
|
+
*,
|
|
98
|
+
asset_id: str | None = None,
|
|
99
|
+
) -> str:
|
|
100
|
+
call_id = uuid.uuid4().hex
|
|
101
|
+
url = os.environ.get("ATHENA_PTC_URL")
|
|
102
|
+
if not url:
|
|
103
|
+
return call_id
|
|
104
|
+
_call_url[call_id] = url
|
|
105
|
+
body: dict[str, Any] = {
|
|
106
|
+
"callId": call_id,
|
|
107
|
+
"toolName": tool_name,
|
|
108
|
+
"phase": "begin",
|
|
109
|
+
"args": args,
|
|
110
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
|
|
111
|
+
}
|
|
112
|
+
if asset_id is not None:
|
|
113
|
+
body["assetId"] = asset_id
|
|
114
|
+
_send(url, body)
|
|
115
|
+
return call_id
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def emit_end(
|
|
119
|
+
*,
|
|
120
|
+
call_id: str,
|
|
121
|
+
tool_name: str,
|
|
122
|
+
result: dict[str, Any] | None,
|
|
123
|
+
is_error: bool,
|
|
124
|
+
) -> None:
|
|
125
|
+
url = _call_url.pop(call_id, None)
|
|
126
|
+
if url is None:
|
|
127
|
+
return # PTC was disabled at begin, or begin wasn't emitted
|
|
128
|
+
_send(
|
|
129
|
+
url,
|
|
130
|
+
{
|
|
131
|
+
"callId": call_id,
|
|
132
|
+
"toolName": tool_name,
|
|
133
|
+
"phase": "end",
|
|
134
|
+
"result": result,
|
|
135
|
+
"isError": is_error,
|
|
136
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
|
|
137
|
+
},
|
|
138
|
+
)
|
|
@@ -619,6 +619,40 @@ class HeaderFootersRefsSetLinkedToPrevious(Command):
|
|
|
619
619
|
# every other op.
|
|
620
620
|
|
|
621
621
|
|
|
622
|
+
@dataclass
|
|
623
|
+
class ListsCreate(Command):
|
|
624
|
+
"""Create a new list — either an empty list at an anchor, or by
|
|
625
|
+
converting an existing paragraph (range) into a list.
|
|
626
|
+
|
|
627
|
+
SuperDoc: ``doc.lists.create``.
|
|
628
|
+
|
|
629
|
+
- ``mode="empty"`` requires ``at`` (BlockAddress).
|
|
630
|
+
- ``mode="fromParagraphs"`` requires ``target`` (BlockAddressOrRange).
|
|
631
|
+
- ``kind`` is ``"bullet"`` or ``"ordered"``.
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
mode: str = "fromParagraphs"
|
|
635
|
+
at: dict[str, Any] | None = None
|
|
636
|
+
target: dict[str, Any] | None = None
|
|
637
|
+
kind: str | None = None
|
|
638
|
+
level: int | None = None
|
|
639
|
+
preset: str | None = None
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@dataclass
|
|
643
|
+
class ListsAttach(Command):
|
|
644
|
+
"""Attach an existing paragraph to an existing list as a new item.
|
|
645
|
+
|
|
646
|
+
SuperDoc: ``doc.lists.attach``. ``attach_to`` is the list item to
|
|
647
|
+
attach next to (defines list membership); ``target`` is the
|
|
648
|
+
paragraph being converted.
|
|
649
|
+
"""
|
|
650
|
+
|
|
651
|
+
attach_to: dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
652
|
+
target: dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
653
|
+
level: int | None = None
|
|
654
|
+
|
|
655
|
+
|
|
622
656
|
@dataclass
|
|
623
657
|
class ListsMerge(Command):
|
|
624
658
|
"""Merge a list item with an adjacent list / list item.
|
|
@@ -768,6 +802,12 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
|
|
|
768
802
|
# describing the entities affected by accept/reject — callers
|
|
769
803
|
# need that synchronously to invalidate their Revision proxies.
|
|
770
804
|
"TrackChangesDecide",
|
|
805
|
+
# ListsCreate / ListsAttach return {item: {nodeId: <listItemId>}}.
|
|
806
|
+
# Document.add_paragraph reads listItemId to (1) update the
|
|
807
|
+
# Paragraph proxy's node_id so subsequent ops target the
|
|
808
|
+
# listItem, and (2) chain the next list item via lists.attach.
|
|
809
|
+
"ListsCreate",
|
|
810
|
+
"ListsAttach",
|
|
771
811
|
}
|
|
772
812
|
)
|
|
773
813
|
|
|
@@ -314,6 +314,26 @@ class Comments:
|
|
|
314
314
|
Use :meth:`Document.add_comment` to thread a run-anchored
|
|
315
315
|
target — this method creates an unanchored comment.
|
|
316
316
|
"""
|
|
317
|
+
# python-docx 1.x routes ``text`` / ``author`` / ``initials``
|
|
318
|
+
# through lxml's element setters which TypeError on non-str.
|
|
319
|
+
# Was: any value shipped to wire (text) or stashed as-is on
|
|
320
|
+
# the local info dict (author/initials) — caller's intent was
|
|
321
|
+
# lost when they passed a wrong type.
|
|
322
|
+
if not isinstance(text, str):
|
|
323
|
+
raise TypeError(
|
|
324
|
+
f"Comments.add_comment text requires str; got "
|
|
325
|
+
f"{type(text).__name__} {text!r}",
|
|
326
|
+
)
|
|
327
|
+
if not isinstance(author, str):
|
|
328
|
+
raise TypeError(
|
|
329
|
+
f"Comments.add_comment author requires str; got "
|
|
330
|
+
f"{type(author).__name__} {author!r}",
|
|
331
|
+
)
|
|
332
|
+
if initials is not None and not isinstance(initials, str):
|
|
333
|
+
raise TypeError(
|
|
334
|
+
f"Comments.add_comment initials requires str or None; "
|
|
335
|
+
f"got {type(initials).__name__} {initials!r}",
|
|
336
|
+
)
|
|
317
337
|
result: object = run_sync(
|
|
318
338
|
self._session.doc.comments.create({"text": text}),
|
|
319
339
|
)
|
|
@@ -79,6 +79,49 @@ class _InlineShapeIdAdapter:
|
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _classify_list_style(style_id: str | None) -> tuple[str, int] | None:
|
|
83
|
+
"""Map a python-docx list-style id to a SuperDoc (kind, level) pair.
|
|
84
|
+
|
|
85
|
+
Returns ``None`` for non-list styles. Recognized styles:
|
|
86
|
+
|
|
87
|
+
- ``"List Bullet"`` → ``("bullet", 0)``
|
|
88
|
+
- ``"List Bullet 2"`` → ``("bullet", 1)`` (... up to ``5`` → level 4)
|
|
89
|
+
- ``"List Number"`` → ``("ordered", 0)``
|
|
90
|
+
- ``"List Number 2"`` → ``("ordered", 1)`` (... up to ``5`` → level 4)
|
|
91
|
+
|
|
92
|
+
Accepts both display-name (``"List Bullet 2"``) and style-id
|
|
93
|
+
(``"ListBullet2"``) forms. ``"List Paragraph"`` and ``"List Continue"``
|
|
94
|
+
are intentionally NOT classified as bullet/number lists — those styles
|
|
95
|
+
indicate "indented body text", not a numbered/bulleted list, and Word
|
|
96
|
+
renders them without a marker. Callers wanting those effects should
|
|
97
|
+
use the style as-is via the generic ``setStyle`` path.
|
|
98
|
+
"""
|
|
99
|
+
if not isinstance(style_id, str) or not style_id:
|
|
100
|
+
return None
|
|
101
|
+
# Normalize: lower-case, strip spaces.
|
|
102
|
+
norm = style_id.lower().replace(" ", "")
|
|
103
|
+
if norm.startswith("listbullet"):
|
|
104
|
+
suffix = norm[len("listbullet"):]
|
|
105
|
+
kind = "bullet"
|
|
106
|
+
elif norm.startswith("listnumber"):
|
|
107
|
+
suffix = norm[len("listnumber"):]
|
|
108
|
+
kind = "ordered"
|
|
109
|
+
else:
|
|
110
|
+
return None
|
|
111
|
+
if suffix == "":
|
|
112
|
+
level = 0
|
|
113
|
+
else:
|
|
114
|
+
try:
|
|
115
|
+
n = int(suffix)
|
|
116
|
+
except ValueError:
|
|
117
|
+
return None
|
|
118
|
+
if n < 1 or n > 9:
|
|
119
|
+
return None
|
|
120
|
+
# "List Bullet 2" → level 1 (zero-indexed depth).
|
|
121
|
+
level = n - 1
|
|
122
|
+
return (kind, level)
|
|
123
|
+
|
|
124
|
+
|
|
82
125
|
class IgnoredSaveTargetWarning(UserWarning):
|
|
83
126
|
"""Emitted when ``Document.save(path_or_stream=...)`` is called with a
|
|
84
127
|
non-None argument.
|
|
@@ -123,6 +166,13 @@ class Document:
|
|
|
123
166
|
self._pending_user_name: str | None = user_name
|
|
124
167
|
self._pending_user_email: str | None = user_email
|
|
125
168
|
self._pending_track_revisions: bool = bool(track_revisions)
|
|
169
|
+
# List-chain state. When the most-recent ``add_paragraph`` call
|
|
170
|
+
# produced a list item, subsequent ``add_paragraph(..., style="List
|
|
171
|
+
# Bullet"|"List Number")`` calls attach to it (lists.attach)
|
|
172
|
+
# instead of starting a new list (lists.create). Any non-list
|
|
173
|
+
# ``add_paragraph`` / ``add_heading`` / ``add_table`` resets this.
|
|
174
|
+
self._last_list_item_id: str | None = None
|
|
175
|
+
self._last_list_kind: str | None = None
|
|
126
176
|
|
|
127
177
|
@classmethod
|
|
128
178
|
def create(
|
|
@@ -596,9 +646,24 @@ class Document:
|
|
|
596
646
|
|
|
597
647
|
# Coerce ParagraphStyle instances to their style id so the rest
|
|
598
648
|
# of this method can treat `style` as an Optional[str].
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
649
|
+
# python-docx 1.x accepts ``str | ParagraphStyle | None`` and
|
|
650
|
+
# raises ``TypeError`` at the style-lookup layer for anything
|
|
651
|
+
# else. Mirror that — the previous code accepted ``style`` as
|
|
652
|
+
# the literal value when it wasn't a ParagraphStyle, so e.g.
|
|
653
|
+
# ``add_paragraph(style=42)`` shipped ``42`` as the style id.
|
|
654
|
+
style_str: str | None
|
|
655
|
+
if style is None:
|
|
656
|
+
style_str = None
|
|
657
|
+
elif isinstance(style, ParagraphStyle):
|
|
658
|
+
style_str = style.style_id
|
|
659
|
+
elif isinstance(style, str):
|
|
660
|
+
style_str = style
|
|
661
|
+
else:
|
|
662
|
+
raise TypeError(
|
|
663
|
+
f"Document.add_paragraph style requires str, "
|
|
664
|
+
f"ParagraphStyle, or None; got "
|
|
665
|
+
f"{type(style).__name__} {style!r}",
|
|
666
|
+
)
|
|
602
667
|
|
|
603
668
|
# Use Superdoc's canonical primitive `doc.create.paragraph`. It
|
|
604
669
|
# accepts an `at` anchor and a `text` body; an empty `text` creates
|
|
@@ -624,8 +689,9 @@ class Document:
|
|
|
624
689
|
# heavy lifting.
|
|
625
690
|
normalized: str = self._normalize_text(text)
|
|
626
691
|
has_control_chars = any(ch in "\t\n\r" for ch in normalized)
|
|
692
|
+
wire_text: str = "" if has_control_chars else normalized
|
|
627
693
|
params: dict = {
|
|
628
|
-
"text":
|
|
694
|
+
"text": wire_text,
|
|
629
695
|
"at": {"kind": "documentEnd"},
|
|
630
696
|
}
|
|
631
697
|
# When inside ``with doc.batch():`` we pre-assign a client UUID
|
|
@@ -650,8 +716,13 @@ class Document:
|
|
|
650
716
|
raise RuntimeError(
|
|
651
717
|
f"Superdoc did not return a nodeId for add_paragraph: {result!r}",
|
|
652
718
|
)
|
|
719
|
+
# Seed the text-length cache so the next ``p.add_run(...)``
|
|
720
|
+
# call can skip its ``get_node_by_id`` HTTP round-trip.
|
|
653
721
|
para = Paragraph(
|
|
654
|
-
session=self._session,
|
|
722
|
+
session=self._session,
|
|
723
|
+
node_id=node_id,
|
|
724
|
+
node_type="paragraph",
|
|
725
|
+
text_len=len(wire_text),
|
|
655
726
|
)
|
|
656
727
|
if client_node_id is not None:
|
|
657
728
|
self._register_proxy_for_batch(client_node_id, para)
|
|
@@ -668,8 +739,142 @@ class Document:
|
|
|
668
739
|
# or transport error otherwise leaves the user with a
|
|
669
740
|
# paragraph that quietly missed the style they asked for.
|
|
670
741
|
para.style = style_str
|
|
742
|
+
|
|
743
|
+
# python-docx parity: ``add_paragraph(text, style="List Bullet")``
|
|
744
|
+
# is the canonical way to author a bulleted/numbered list (the
|
|
745
|
+
# upstream library exposes no separate list primitive — the style
|
|
746
|
+
# is the list). On Word/OOXML the style's ``<w:numPr/>`` makes
|
|
747
|
+
# bullets appear; on SuperDoc/ProseMirror the style attribute
|
|
748
|
+
# alone does NOT, because lists are a distinct node type
|
|
749
|
+
# (``bulletList > listItem > paragraph``) rather than a flat
|
|
750
|
+
# paragraph with a style.
|
|
751
|
+
#
|
|
752
|
+
# To make the python-docx contract render correctly we follow
|
|
753
|
+
# up the paragraph create + setStyle with SuperDoc's
|
|
754
|
+
# ``doc.lists.create`` / ``doc.lists.attach`` so the paragraph
|
|
755
|
+
# is structurally converted into a list item under a real
|
|
756
|
+
# bulletList / orderedList node. Consecutive list-styled
|
|
757
|
+
# ``add_paragraph`` calls (of the same kind) chain into a
|
|
758
|
+
# single list via ``lists.attach``; the first one starts a new
|
|
759
|
+
# list via ``lists.create(mode="fromParagraphs")``.
|
|
760
|
+
list_info = _classify_list_style(style_str)
|
|
761
|
+
if list_info is not None:
|
|
762
|
+
kind, level = list_info
|
|
763
|
+
self._convert_paragraph_to_list_item(
|
|
764
|
+
paragraph_node_id=node_id,
|
|
765
|
+
kind=kind,
|
|
766
|
+
level=level,
|
|
767
|
+
)
|
|
768
|
+
else:
|
|
769
|
+
# Any non-list paragraph breaks the chain — the next list
|
|
770
|
+
# item will start a fresh list.
|
|
771
|
+
self._last_list_item_id = None
|
|
772
|
+
self._last_list_kind = None
|
|
671
773
|
return para
|
|
672
774
|
|
|
775
|
+
def _convert_paragraph_to_list_item(
|
|
776
|
+
self,
|
|
777
|
+
*,
|
|
778
|
+
paragraph_node_id: str,
|
|
779
|
+
kind: str,
|
|
780
|
+
level: int,
|
|
781
|
+
) -> None:
|
|
782
|
+
"""Promote a flat paragraph into a list item.
|
|
783
|
+
|
|
784
|
+
Chains with the previous list (same ``kind``) via
|
|
785
|
+
``lists.attach``; otherwise starts a new list via
|
|
786
|
+
``lists.create(mode="fromParagraphs")``. Updates the
|
|
787
|
+
``_last_list_item_id`` / ``_last_list_kind`` chain state so the
|
|
788
|
+
next list-styled ``add_paragraph`` call attaches to this item.
|
|
789
|
+
|
|
790
|
+
Failures don't raise — the paragraph + setStyle already
|
|
791
|
+
landed, and surfacing a structural-promotion error here would
|
|
792
|
+
roll back work the caller can't undo. We emit a console warning
|
|
793
|
+
and reset the chain so subsequent items start fresh rather than
|
|
794
|
+
attaching to an orphaned id.
|
|
795
|
+
"""
|
|
796
|
+
para_target: dict = {
|
|
797
|
+
"kind": "block",
|
|
798
|
+
"nodeType": "paragraph",
|
|
799
|
+
"nodeId": paragraph_node_id,
|
|
800
|
+
}
|
|
801
|
+
try:
|
|
802
|
+
if (
|
|
803
|
+
self._last_list_item_id is not None
|
|
804
|
+
and self._last_list_kind == kind
|
|
805
|
+
):
|
|
806
|
+
result = run_sync(
|
|
807
|
+
self._session.doc.lists.attach(
|
|
808
|
+
{
|
|
809
|
+
"attach_to": {
|
|
810
|
+
"kind": "block",
|
|
811
|
+
"nodeType": "listItem",
|
|
812
|
+
"nodeId": self._last_list_item_id,
|
|
813
|
+
},
|
|
814
|
+
"target": para_target,
|
|
815
|
+
"level": level,
|
|
816
|
+
},
|
|
817
|
+
),
|
|
818
|
+
)
|
|
819
|
+
else:
|
|
820
|
+
result = run_sync(
|
|
821
|
+
self._session.doc.lists.create(
|
|
822
|
+
{
|
|
823
|
+
"mode": "fromParagraphs",
|
|
824
|
+
"target": para_target,
|
|
825
|
+
"kind": kind,
|
|
826
|
+
"level": level,
|
|
827
|
+
},
|
|
828
|
+
),
|
|
829
|
+
)
|
|
830
|
+
except Exception as e: # noqa: BLE001
|
|
831
|
+
_log_warn(
|
|
832
|
+
f"list-item promotion failed for paragraph "
|
|
833
|
+
f"{paragraph_node_id!r} ({kind}, level {level}): {e}; "
|
|
834
|
+
f"paragraph remains flat with the style attribute set.",
|
|
835
|
+
)
|
|
836
|
+
self._last_list_item_id = None
|
|
837
|
+
self._last_list_kind = None
|
|
838
|
+
return
|
|
839
|
+
|
|
840
|
+
item_id = self._extract_list_item_id(result)
|
|
841
|
+
if item_id:
|
|
842
|
+
self._last_list_item_id = item_id
|
|
843
|
+
self._last_list_kind = kind
|
|
844
|
+
else:
|
|
845
|
+
# Couldn't read back the listItem id — break the chain so the
|
|
846
|
+
# next call doesn't attach to a stale or missing target.
|
|
847
|
+
_log_warn(
|
|
848
|
+
f"list op returned no item.nodeId for paragraph "
|
|
849
|
+
f"{paragraph_node_id!r}; list chain reset. Response: "
|
|
850
|
+
f"{result!r}",
|
|
851
|
+
)
|
|
852
|
+
self._last_list_item_id = None
|
|
853
|
+
self._last_list_kind = None
|
|
854
|
+
|
|
855
|
+
def _reset_list_chain(self) -> None:
|
|
856
|
+
"""Break the consecutive-list-items chain. Called by any block
|
|
857
|
+
insertion that isn't a list-styled paragraph (heading, table,
|
|
858
|
+
picture, page break, section break) so the next list item
|
|
859
|
+
starts a fresh list rather than attaching to a stale predecessor.
|
|
860
|
+
"""
|
|
861
|
+
self._last_list_item_id = None
|
|
862
|
+
self._last_list_kind = None
|
|
863
|
+
|
|
864
|
+
@staticmethod
|
|
865
|
+
def _extract_list_item_id(result: object) -> str | None:
|
|
866
|
+
"""Pull ``item.nodeId`` out of a ``lists.create`` / ``lists.attach``
|
|
867
|
+
SuperDoc response. Returns ``None`` if the shape is unexpected."""
|
|
868
|
+
if not isinstance(result, dict):
|
|
869
|
+
return None
|
|
870
|
+
item = result.get("item")
|
|
871
|
+
if not isinstance(item, dict):
|
|
872
|
+
return None
|
|
873
|
+
node_id = item.get("nodeId") or item.get("node_id")
|
|
874
|
+
if isinstance(node_id, str) and node_id:
|
|
875
|
+
return node_id
|
|
876
|
+
return None
|
|
877
|
+
|
|
673
878
|
def add_heading(
|
|
674
879
|
self,
|
|
675
880
|
text: str = "",
|
|
@@ -679,6 +884,7 @@ class Document:
|
|
|
679
884
|
from docx.text.paragraph import Paragraph
|
|
680
885
|
|
|
681
886
|
self._ensure_open()
|
|
887
|
+
self._reset_list_chain()
|
|
682
888
|
if not 0 <= level <= 9:
|
|
683
889
|
raise ValidationError(
|
|
684
890
|
f"level must be in 0..9; got {level}",
|
|
@@ -722,6 +928,7 @@ class Document:
|
|
|
722
928
|
session=self._session,
|
|
723
929
|
node_id=node_id,
|
|
724
930
|
node_type="paragraph",
|
|
931
|
+
text_len=len(wire_text),
|
|
725
932
|
)
|
|
726
933
|
if client_node_id is not None:
|
|
727
934
|
self._register_proxy_for_batch(client_node_id, paragraph)
|
|
@@ -753,7 +960,10 @@ class Document:
|
|
|
753
960
|
f"Superdoc did not return a nodeId for add_heading: {result!r}",
|
|
754
961
|
)
|
|
755
962
|
heading = Paragraph(
|
|
756
|
-
session=self._session,
|
|
963
|
+
session=self._session,
|
|
964
|
+
node_id=node_id,
|
|
965
|
+
node_type="heading",
|
|
966
|
+
text_len=len(wire_text),
|
|
757
967
|
)
|
|
758
968
|
if client_node_id is not None:
|
|
759
969
|
self._register_proxy_for_batch(client_node_id, heading)
|
|
@@ -771,6 +981,7 @@ class Document:
|
|
|
771
981
|
from docx.table import Table
|
|
772
982
|
|
|
773
983
|
self._ensure_open()
|
|
984
|
+
self._reset_list_chain()
|
|
774
985
|
if rows < 1 or cols < 1:
|
|
775
986
|
raise ValidationError(
|
|
776
987
|
f"rows and cols must be >= 1; got rows={rows} cols={cols}",
|
|
@@ -858,6 +1069,7 @@ class Document:
|
|
|
858
1069
|
from docx.text.run import _build_inline_shape_info
|
|
859
1070
|
|
|
860
1071
|
self._ensure_open()
|
|
1072
|
+
self._reset_list_chain()
|
|
861
1073
|
|
|
862
1074
|
image_bytes: bytes
|
|
863
1075
|
fallback_path: str = ""
|
|
@@ -4,6 +4,17 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
|
|
7
|
+
from docx.enum.text import _warn_enum_coercion
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"WD_HEADER_FOOTER",
|
|
11
|
+
"WD_HEADER_FOOTER_INDEX",
|
|
12
|
+
"WD_ORIENT",
|
|
13
|
+
"WD_ORIENTATION",
|
|
14
|
+
"WD_SECTION",
|
|
15
|
+
"WD_SECTION_START",
|
|
16
|
+
]
|
|
17
|
+
|
|
7
18
|
|
|
8
19
|
class WD_ORIENTATION(Enum):
|
|
9
20
|
PORTRAIT = "portrait"
|
|
@@ -31,6 +42,13 @@ class WD_SECTION_START(Enum):
|
|
|
31
42
|
return "evenPage"
|
|
32
43
|
if self == WD_SECTION_START.ODD_PAGE:
|
|
33
44
|
return "oddPage"
|
|
45
|
+
if self == WD_SECTION_START.NEW_PAGE:
|
|
46
|
+
return "nextPage"
|
|
47
|
+
# NEW_COLUMN — SuperDoc 1.8 doesn't have a column-break section
|
|
48
|
+
# type, so we collapse to "nextPage" but emit
|
|
49
|
+
# PendingEnumCoercionWarning so callers don't silently get a
|
|
50
|
+
# page break when they asked for a column break.
|
|
51
|
+
_warn_enum_coercion(self, "nextPage")
|
|
34
52
|
return "nextPage"
|
|
35
53
|
|
|
36
54
|
|
|
@@ -13,6 +13,19 @@ import warnings
|
|
|
13
13
|
from enum import Enum
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
_INT_TO_ALIGN: "dict[int, str]" = {
|
|
17
|
+
0: "LEFT",
|
|
18
|
+
1: "CENTER",
|
|
19
|
+
2: "RIGHT",
|
|
20
|
+
3: "JUSTIFY",
|
|
21
|
+
4: "DISTRIBUTE",
|
|
22
|
+
5: "JUSTIFY_MED",
|
|
23
|
+
7: "JUSTIFY_HI",
|
|
24
|
+
8: "JUSTIFY_LOW",
|
|
25
|
+
9: "THAI_JUSTIFY",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
16
29
|
class PendingEnumCoercionWarning(UserWarning):
|
|
17
30
|
"""Emitted when an enum member's :meth:`to_superdoc` collapses the
|
|
18
31
|
value to a different SuperDoc-supported primitive.
|
|
@@ -58,6 +71,20 @@ class WD_ALIGN_PARAGRAPH(Enum):
|
|
|
58
71
|
JUSTIFY_LOW = "justifyLow"
|
|
59
72
|
THAI_JUSTIFY = "thaiJustify"
|
|
60
73
|
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_int(cls, value: int) -> "WD_ALIGN_PARAGRAPH | None":
|
|
76
|
+
"""Coerce a python-docx-style int alignment to the enum.
|
|
77
|
+
|
|
78
|
+
python-docx 1.x's ``WD_ALIGN_PARAGRAPH`` is keyed on int (its
|
|
79
|
+
``BaseXmlEnum`` mapping: LEFT=0, CENTER=1, RIGHT=2, JUSTIFY=3,
|
|
80
|
+
DISTRIBUTE=4, JUSTIFY_MED=5, JUSTIFY_HI=7, JUSTIFY_LOW=8,
|
|
81
|
+
THAI_JUSTIFY=9). Callers passing ``paragraph.alignment = 1``
|
|
82
|
+
get CENTER. We mirror that without changing our enum's
|
|
83
|
+
string-valued definition.
|
|
84
|
+
"""
|
|
85
|
+
name = _INT_TO_ALIGN.get(value)
|
|
86
|
+
return getattr(cls, name) if name is not None else None
|
|
87
|
+
|
|
61
88
|
def to_superdoc(self) -> str:
|
|
62
89
|
# Superdoc only supports left/center/right/justify; coerce the
|
|
63
90
|
# distributed/thai variants to "justify" so set_alignment doesn't
|