athena-python-docx 0.5.0__tar.gz → 0.5.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.
Files changed (259) hide show
  1. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/CLAUDE.md +38 -0
  2. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/PKG-INFO +1 -1
  3. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/__init__.py +1 -1
  4. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_buffer.py +72 -2
  5. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_http_doc.py +32 -0
  6. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/api.py +15 -0
  7. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/commands.py +75 -0
  8. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/document.py +186 -13
  9. athena_python_docx-0.5.2/docx/revisions.py +377 -0
  10. athena_python_docx-0.5.2/docx/settings.py +71 -0
  11. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/pyproject.toml +1 -1
  12. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/scripts/release.sh +8 -8
  13. athena_python_docx-0.5.2/scripts/round_trip_smoke.py +232 -0
  14. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_buffer.py +9 -0
  15. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_http_transport.py +90 -0
  16. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_parity_misc.py +19 -0
  17. athena_python_docx-0.5.2/tests/test_revisions.py +966 -0
  18. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_wire_contract.py +21 -0
  19. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/uv.lock +1 -1
  20. athena_python_docx-0.5.0/docx/settings.py +0 -30
  21. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/.gitignore +0 -0
  22. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/README.md +0 -0
  23. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_batching.py +0 -0
  24. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_http.py +0 -0
  25. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/_image_utils.py +0 -0
  26. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/client.py +0 -0
  27. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/comments.py +0 -0
  28. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/__init__.py +0 -0
  29. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/section.py +0 -0
  30. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/style.py +0 -0
  31. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/table.py +0 -0
  32. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/enum/text.py +0 -0
  33. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/errors.py +0 -0
  34. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/exceptions.py +0 -0
  35. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/opc/__init__.py +0 -0
  36. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/opc/coreprops.py +0 -0
  37. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/section.py +0 -0
  38. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/shape.py +0 -0
  39. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/shared.py +0 -0
  40. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/styles/__init__.py +0 -0
  41. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/styles/style.py +0 -0
  42. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/styles/styles.py +0 -0
  43. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/table.py +0 -0
  44. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/__init__.py +0 -0
  45. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/font.py +0 -0
  46. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/hyperlink.py +0 -0
  47. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/pagebreak.py +0 -0
  48. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/paragraph.py +0 -0
  49. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/parfmt.py +0 -0
  50. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/run.py +0 -0
  51. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/text/tabstops.py +0 -0
  52. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/docx/typing.py +0 -0
  53. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/scripts/publish.sh +0 -0
  54. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/__init__.py +0 -0
  55. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/conftest.py +0 -0
  56. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/METHODOLOGY.md +0 -0
  57. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/README.md +0 -0
  58. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/__init__.py +0 -0
  59. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/ab_probe_cases.py +0 -0
  60. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/ab_probe_runner.py +0 -0
  61. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/auto_gen_cases.py +0 -0
  62. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/binary_round_trip.py +0 -0
  63. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/cases.py +0 -0
  64. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/complex_cases.py +0 -0
  65. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/coverage_report.py +0 -0
  66. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/extract.py +0 -0
  67. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/extreme_cases.py +0 -0
  68. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/fake_session.py +0 -0
  69. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/local_runner.py +0 -0
  70. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/mega_cases.py +0 -0
  71. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshot.py +0 -0
  72. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  73. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  74. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  75. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  76. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  77. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  78. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  79. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  80. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  81. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  82. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  83. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  84. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/103_cell_tables_enumeration.json +0 -0
  85. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  86. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  87. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  88. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  89. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  90. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  91. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  92. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  93. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  94. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  95. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  96. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  97. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  98. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  99. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  100. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  101. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  102. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/23_nested_table.json +0 -0
  103. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  104. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  105. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  106. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  107. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  108. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  109. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  110. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  111. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  112. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  113. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  114. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  115. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  116. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  117. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  118. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  119. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  120. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  121. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  122. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  123. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  124. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  125. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  126. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  127. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  128. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  129. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  130. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/51_nested_tables_deep.json +0 -0
  131. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  132. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  133. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  134. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  135. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  136. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  137. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  138. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  139. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  140. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  141. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  142. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  143. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  144. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  145. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  146. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  147. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  148. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  149. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  150. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  151. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  152. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  153. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  154. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  155. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  156. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  157. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  158. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  159. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  160. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  161. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  162. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  163. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  164. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  165. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  166. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  167. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  168. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  169. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  170. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  171. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  172. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  173. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  174. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  175. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  176. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  177. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  178. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  179. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex01_five_levels_deep_tables.json +0 -0
  180. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  181. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  182. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  183. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  184. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  185. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  186. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  187. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  188. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  189. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  190. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  191. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  192. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  193. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  194. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  195. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  196. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  197. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  198. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  199. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  200. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  201. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  202. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  203. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  204. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  205. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  206. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  207. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  208. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  209. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  210. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  211. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  212. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  213. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  214. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  215. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  216. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  217. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  218. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  219. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  220. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  221. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  222. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw14_nested_cell_table.json +0 -0
  223. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  224. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/ours_spec.json +0 -0
  225. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/parity_crawl.py +0 -0
  226. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/parity_diff.json +0 -0
  227. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/real_world_cases.py +0 -0
  228. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/round_trip_tests.py +0 -0
  229. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/runner.py +0 -0
  230. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/fidelity/stock_spec.json +0 -0
  231. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/README.md +0 -0
  232. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/__init__.py +0 -0
  233. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/baseline_gaps.json +0 -0
  234. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/compare.py +0 -0
  235. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/intentional_deviations.json +0 -0
  236. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/introspect.py +0 -0
  237. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  238. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/reports/gap_report.json +0 -0
  239. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/run_parity.py +0 -0
  240. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/snapshots/athena_latest.json +0 -0
  241. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  242. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/parity/test_parity_gap.py +0 -0
  243. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_command_dataclasses.py +0 -0
  244. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_commands.py +0 -0
  245. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_comments.py +0 -0
  246. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_document_create.py +0 -0
  247. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_iter_inner_content.py +0 -0
  248. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_merged_cells.py +0 -0
  249. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_parity_round2.py +0 -0
  250. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_phase_a_behavior.py +0 -0
  251. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_phase_b_headers_footers.py +0 -0
  252. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_phase_c_tables.py +0 -0
  253. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_pr19766_review_fixes.py +0 -0
  254. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_python_docx_api_parity.py +0 -0
  255. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_smoke_integration.py +0 -0
  256. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_style_acceptance.py +0 -0
  257. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_style_font.py +0 -0
  258. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_style_setters_contract.py +0 -0
  259. {athena_python_docx-0.5.0 → athena_python_docx-0.5.2}/tests/test_zod_wire_contract.py +0 -0
@@ -76,6 +76,44 @@ not file-backed. Each is documented in the relevant docstring.
76
76
  hits `POST {base_url}/docs/empty`. The constructor positional-arg
77
77
  shape (`Document(asset_id)`) is preserved for parity.
78
78
 
79
+ - **`Document.save(path_or_stream=None)`** — argument is optional.
80
+ Stock python-docx requires it and `TypeError`s on no-arg, but in an
81
+ asset-backed SDK there is no local file to write — writes are always
82
+ in-place against the Y.Doc. Forcing the upstream signature broke
83
+ every agent invocation that reflexively called `doc.save()` (a
84
+ near-universal Python pattern), so we accept it for parity-friendly
85
+ call sites and ignore the value at runtime.
86
+
87
+ - **`Document.track_revisions: bool`** — when `True`, all subsequent
88
+ mutations are recorded as tracked revisions instead of direct edits.
89
+ python-docx upstream has no top-level toggle (the closest is
90
+ hand-edited `<w:trackChanges/>` on `Document.settings.element`).
91
+ We expose this as a Document-level property because every SuperDoc
92
+ mutation accepts a per-call `changeMode` envelope field. The
93
+ python-docx-parity alias `Document.settings.track_revisions` reads
94
+ and writes the same flag so call sites that walk `doc.settings`
95
+ see consistent state.
96
+
97
+ - **`Document.tracked_revisions()` context manager** — scope
98
+ `track_revisions=True` to a `with` block; restores the previous
99
+ value on exit (even on exception). Block-scoped track-changes is a
100
+ Pythonic ergonomic that doesn't exist in upstream python-docx
101
+ because the upstream package has no track-changes API at all.
102
+
103
+ - **`Document.user_name: str | None` / `Document.user_email: str |
104
+ None`** — author identity threaded through to SuperDoc on session-
105
+ open for tracked-change attribution. python-docx's
106
+ `Document.core_properties.last_modified_by` is the closest upstream
107
+ surface; we use the SuperDoc-native fields directly because SuperDoc
108
+ bakes them into the change records.
109
+
110
+ - **`Document.revisions: Revisions`** — collection of tracked changes
111
+ with iteration, `get(id)`, `accept_all()`, `reject_all()`,
112
+ `accept(id)`, `reject(id)`. Per-revision `Revision.accept()` /
113
+ `Revision.reject()` resolve a single change. python-docx upstream
114
+ has zero track-changes API, so this entire surface is an Athena
115
+ extension wired to SuperDoc's `doc.trackChanges.{list,get,decide}`.
116
+
79
117
  ### If you need a deviation
80
118
 
81
119
  If there is a genuine technical reason why a deviation from python-docx is necessary:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.5.0
3
+ Version: 0.5.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>
@@ -6,7 +6,7 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.5.0"
9
+ __version__ = "0.5.2"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import flush_all
@@ -123,6 +123,13 @@ class CommandBuffer:
123
123
  self._timer: threading.Timer | None = None
124
124
  self._auto_flush_seconds: float = auto_flush_seconds
125
125
  self._closed: bool = False
126
+ # Track-changes envelope state — propagated as request-level
127
+ # ``changeMode`` and ``user`` on every batch. ``Document``
128
+ # mutates these via :meth:`set_change_mode` / :meth:`set_user`,
129
+ # which flush before changing so prior buffered ops keep their
130
+ # original semantics.
131
+ self._change_mode: str | None = None
132
+ self._user: dict[str, str] | None = None
126
133
  _register(self)
127
134
 
128
135
  @property
@@ -134,6 +141,55 @@ class CommandBuffer:
134
141
  with self._lock:
135
142
  return len(self._pending)
136
143
 
144
+ @property
145
+ def change_mode(self) -> str | None:
146
+ return self._change_mode
147
+
148
+ @property
149
+ def user(self) -> "dict[str, str] | None":
150
+ return None if self._user is None else dict(self._user)
151
+
152
+ def set_change_mode(self, mode: str | None) -> None:
153
+ """Set the batch-level change mode and flush prior pending ops.
154
+
155
+ Flushing first means commands queued under the previous mode
156
+ retain those semantics — switching modes mid-stream doesn't
157
+ retro-actively re-tag earlier mutations. ``mode`` is one of
158
+ ``"direct"``, ``"tracked"``, or ``None`` to clear.
159
+ """
160
+ if mode is not None and mode not in ("direct", "tracked"):
161
+ raise ValueError(
162
+ f"change_mode must be 'direct', 'tracked', or None; got {mode!r}"
163
+ )
164
+ with self._lock:
165
+ current = self._change_mode
166
+ if current == mode:
167
+ return
168
+ # Drain any queued commands with the *previous* mode before
169
+ # adopting the new one.
170
+ self.flush()
171
+ with self._lock:
172
+ self._change_mode = mode
173
+
174
+ def set_user(self, name: str | None, email: str | None = None) -> None:
175
+ """Set the batch-level user identity.
176
+
177
+ Sent on every commands request envelope. SuperDoc honors the
178
+ identity at session-open time only — subsequent batches against
179
+ a pooled session ignore the value, but the payload is still
180
+ included so a fresh session picks it up.
181
+ """
182
+ if name is None:
183
+ with self._lock:
184
+ self._user = None
185
+ return
186
+ if not name:
187
+ raise ValueError("user name must be a non-empty string")
188
+ with self._lock:
189
+ self._user = {"name": name}
190
+ if email:
191
+ self._user["email"] = email
192
+
137
193
  def call(self, cmd: Command) -> Any:
138
194
  """Execute or buffer ``cmd``.
139
195
 
@@ -167,9 +223,16 @@ class CommandBuffer:
167
223
  self._cancel_timer_locked()
168
224
  pending = self._pending
169
225
  self._pending = []
226
+ change_mode = self._change_mode
227
+ user = None if self._user is None else dict(self._user)
170
228
  if not pending:
171
229
  return []
172
- return self._client.execute_batch(self._asset_id, pending)
230
+ return self._client.execute_batch(
231
+ self._asset_id,
232
+ pending,
233
+ change_mode=change_mode,
234
+ user=user,
235
+ )
173
236
 
174
237
  @contextmanager
175
238
  def batch(self) -> Generator[None, None, None]:
@@ -212,8 +275,15 @@ class CommandBuffer:
212
275
  self._cancel_timer_locked()
213
276
  pending = self._pending
214
277
  self._pending = []
278
+ change_mode = self._change_mode
279
+ user = None if self._user is None else dict(self._user)
215
280
  all_cmds: list[Command] = [*pending, cmd]
216
- results: list[Any] = self._client.execute_batch(self._asset_id, all_cmds)
281
+ results: list[Any] = self._client.execute_batch(
282
+ self._asset_id,
283
+ all_cmds,
284
+ change_mode=change_mode,
285
+ user=user,
286
+ )
217
287
  if not results:
218
288
  return {}
219
289
  return results[-1]
@@ -103,6 +103,9 @@ from docx.commands import (
103
103
  TablesSetRowHeight,
104
104
  TablesSetStyle,
105
105
  TablesSetTableOptions,
106
+ TrackChangesDecide,
107
+ TrackChangesGet,
108
+ TrackChangesList,
106
109
  )
107
110
  from docx.errors import (
108
111
  AuthenticationError,
@@ -260,9 +263,23 @@ class HttpClient:
260
263
  self,
261
264
  asset_id: str,
262
265
  commands: list[Command],
266
+ *,
267
+ change_mode: str | None = None,
268
+ user: dict[str, str] | None = None,
263
269
  ) -> list[Any]:
264
270
  """POST a batch of typed commands, return per-command result dicts.
265
271
 
272
+ Envelope fields:
273
+ change_mode: ``"direct"`` | ``"tracked"`` | ``None``. When
274
+ set, the server injects this into every mutation
275
+ forwarded to SuperDoc — tracked edits land as
276
+ ``<w:ins>`` / ``<w:del>`` revisions instead of replacing
277
+ the underlying text.
278
+ user: ``{"name": ..., "email": ...}`` identity for
279
+ tracked-change attribution. Honored only when SuperDoc
280
+ opens the per-asset session for the first time; ignored
281
+ on subsequent batches against the same pooled session.
282
+
266
283
  On success, length matches len(commands). On 207 partial-failure,
267
284
  the server returns 207 with `applied` (the successful prefix) plus
268
285
  an `error` for the failing command — :func:`_http_post_json`
@@ -278,6 +295,10 @@ class HttpClient:
278
295
  "commands": [c.to_dict() for c in commands],
279
296
  "return": {"snapshot": False},
280
297
  }
298
+ if change_mode is not None:
299
+ body["changeMode"] = change_mode
300
+ if user is not None:
301
+ body["user"] = user
281
302
  resp: dict = _http_post_json(
282
303
  session=self._session,
283
304
  url=url,
@@ -396,6 +417,12 @@ _OP_TO_COMMAND: dict[str, type[Command]] = {
396
417
  "lists.merge": ListsMerge,
397
418
  "lists.split": ListsSplit,
398
419
  "selection.current": SelectionCurrent,
420
+ # Track Changes (Athena extension — see docx.revisions.Revisions).
421
+ # python-docx upstream has no track-changes API; the SDK exposes
422
+ # this as Document.revisions and Document.track_revisions.
423
+ "track_changes.list": TrackChangesList,
424
+ "track_changes.get": TrackChangesGet,
425
+ "track_changes.decide": TrackChangesDecide,
399
426
  }
400
427
 
401
428
 
@@ -423,6 +450,11 @@ _FIELD_RENAMES: dict[str, dict[str, str]] = {
423
450
  "create.paragraph": {"in": "in_"},
424
451
  "create.heading": {"in": "in_"},
425
452
  "create.table": {"in": "in_"},
453
+ # SuperDoc's track-changes-list filter is named `type:` on the wire
454
+ # (matching the rest of its API), but `type` is our Command
455
+ # discriminator. Rename to `change_type` on the Python side; the
456
+ # server applier renames back when calling SuperDoc.
457
+ "track_changes.list": {"type": "change_type", "in": "in_"},
426
458
  }
427
459
 
428
460
 
@@ -26,6 +26,9 @@ def Document(
26
26
  *,
27
27
  base_url: str | None = None,
28
28
  api_key: str | None = None,
29
+ user_name: str | None = None,
30
+ user_email: str | None = None,
31
+ track_revisions: bool = False,
29
32
  ) -> _Document:
30
33
  """Open a Word document for editing.
31
34
 
@@ -39,6 +42,15 @@ def Document(
39
42
  ``$ATHENA_DOCX_BASE_URL``.
40
43
  api_key: Athena API key / PropelAuth bearer. Falls back to
41
44
  ``$ATHENA_DOCX_API_KEY``.
45
+ user_name: (Athena extension) author identity for tracked
46
+ changes. Threaded through to SuperDoc on session-open;
47
+ ignored on subsequent requests against a pooled session.
48
+ user_email: (Athena extension) optional email for the
49
+ tracked-change author tooltip.
50
+ track_revisions: (Athena extension) start the document with
51
+ track-changes mode enabled. Equivalent to setting
52
+ :attr:`Document.track_revisions` to ``True`` after
53
+ construction.
42
54
 
43
55
  Returns:
44
56
  A Document instance bound to the asset.
@@ -59,6 +71,9 @@ def Document(
59
71
  asset_id=docx,
60
72
  http_base_url=base_url,
61
73
  http_api_key=api_key,
74
+ user_name=user_name,
75
+ user_email=user_email,
76
+ track_revisions=track_revisions,
62
77
  )
63
78
 
64
79
 
@@ -622,6 +622,70 @@ class SelectionCurrent(Command):
622
622
  include_text: bool | None = None
623
623
 
624
624
 
625
+ # ---------------------------------------------------------------------------
626
+ # Track Changes
627
+ # ---------------------------------------------------------------------------
628
+
629
+
630
+ @dataclass
631
+ class TrackChangesList(Command):
632
+ """List tracked changes (insertions, deletions, formatting marks).
633
+
634
+ Mirrors SuperDoc's ``doc.trackChanges.list``. The wire field for
635
+ "filter by change type" is ``type`` on SuperDoc's side; we send
636
+ ``changeType`` instead and the server applier renames in transit so
637
+ it doesn't collide with our top-level Command discriminator
638
+ (``type: "TrackChangesList"``).
639
+
640
+ ``in_`` accepts either the literal string ``"all"`` (apply across
641
+ every story — body, headers, footnotes, ...) or a story locator
642
+ dict ``{kind: "story", storyType, ...}``. Trailing-underscore on
643
+ the field name avoids the Python keyword collision; the
644
+ ``_snake_to_camel`` helper strips it for the wire.
645
+ """
646
+
647
+ change_type: str | None = None # "insert" | "delete" | "format"
648
+ limit: int | None = None
649
+ offset: int | None = None
650
+ in_: dict[str, Any] | str | None = None
651
+
652
+
653
+ @dataclass
654
+ class TrackChangesGet(Command):
655
+ """Read a single tracked change by id.
656
+
657
+ Returns ``{address, id, type, author, authorEmail, date, excerpt,
658
+ wordRevisionIds?}``. ``story`` narrows the lookup to a specific
659
+ body/header/footnote scope when the change id might collide across
660
+ stories (rare, but supported).
661
+ """
662
+
663
+ id: str = ""
664
+ story: dict[str, Any] | None = None
665
+
666
+
667
+ @dataclass
668
+ class TrackChangesDecide(Command):
669
+ """Accept or reject one or all tracked changes.
670
+
671
+ Mirrors SuperDoc's ``doc.trackChanges.decide``. ``decision`` is
672
+ ``"accept"`` or ``"reject"``. ``target`` is either a single change
673
+ address (``{id, story?}``) or the bulk sentinel
674
+ (``{scope: "all"}``).
675
+
676
+ ``change_mode`` overrides the batch-level mode for *this one
677
+ decide* — useful when you want to decide a change that itself was
678
+ made under tracked mode without recursively producing more
679
+ changes. Default behaviour: omit and let SuperDoc apply ``direct``.
680
+ """
681
+
682
+ decision: str = "accept" # "accept" | "reject"
683
+ target: dict[str, Any] = dataclasses.field(default_factory=dict)
684
+ force: bool | None = None
685
+ expected_revision: int | None = None
686
+ change_mode: str | None = None
687
+
688
+
625
689
  # ---------------------------------------------------------------------------
626
690
  # Helpers
627
691
  # ---------------------------------------------------------------------------
@@ -649,6 +713,9 @@ _QUERY_TYPES: frozenset[str] = frozenset(
649
713
  "HeaderFootersGet",
650
714
  "HeaderFootersResolve",
651
715
  "SelectionCurrent",
716
+ # Track-changes reads — never mutate, always flush.
717
+ "TrackChangesList",
718
+ "TrackChangesGet",
652
719
  }
653
720
  )
654
721
 
@@ -671,6 +738,10 @@ _RESPONSE_BEARING_TYPES: frozenset[str] = frozenset(
671
738
  # it in a Comment proxy.
672
739
  "CommentsCreate",
673
740
  "CommentsPatch",
741
+ # TrackChangesDecide returns {success, inserted, updated, removed}
742
+ # describing the entities affected by accept/reject — callers
743
+ # need that synchronously to invalidate their Revision proxies.
744
+ "TrackChangesDecide",
674
745
  }
675
746
  )
676
747
 
@@ -745,6 +816,10 @@ __all__ = [
745
816
  "TablesGetCells",
746
817
  "TablesGetProperties",
747
818
  "MarkdownToFragment",
819
+ # Track Changes
820
+ "TrackChangesList",
821
+ "TrackChangesGet",
822
+ "TrackChangesDecide",
748
823
  # Helpers
749
824
  "is_query",
750
825
  "is_response_bearing",
@@ -17,7 +17,8 @@ from __future__ import annotations
17
17
  import base64
18
18
  import io
19
19
  import sys
20
- from typing import TYPE_CHECKING, BinaryIO, Iterator
20
+ from contextlib import contextmanager
21
+ from typing import TYPE_CHECKING, BinaryIO, Generator, Iterator
21
22
 
22
23
  from docx._batching import run_sync
23
24
  from docx.client import Session
@@ -34,6 +35,7 @@ if TYPE_CHECKING:
34
35
  from collections.abc import Sequence
35
36
 
36
37
  from docx.comments import Comment, Comments
38
+ from docx.revisions import Revisions
37
39
  from docx.shape import InlineShape
38
40
  from docx.styles.style import ParagraphStyle, TableStyle
39
41
  from docx.table import Table
@@ -57,6 +59,9 @@ class Document:
57
59
  asset_id: str,
58
60
  http_base_url: str | None = None,
59
61
  http_api_key: str | None = None,
62
+ user_name: str | None = None,
63
+ user_email: str | None = None,
64
+ track_revisions: bool = False,
60
65
  ) -> None:
61
66
  self._session: Session = Session(
62
67
  asset_id=asset_id,
@@ -65,6 +70,12 @@ class Document:
65
70
  )
66
71
  self._saved: bool = False
67
72
  self._closed: bool = False
73
+ # Track-changes state. Defaults are deferred to the buffer (set
74
+ # only when non-default) so existing callers retain the
75
+ # baseline "no envelope changeMode/user" behavior.
76
+ self._pending_user_name: str | None = user_name
77
+ self._pending_user_email: str | None = user_email
78
+ self._pending_track_revisions: bool = bool(track_revisions)
68
79
 
69
80
  @classmethod
70
81
  def create(
@@ -75,6 +86,9 @@ class Document:
75
86
  api_key: str | None = None,
76
87
  parent_folder_id: str | None = None,
77
88
  workspace_id: str | None = None,
89
+ user_name: str | None = None,
90
+ user_email: str | None = None,
91
+ track_revisions: bool = False,
78
92
  ) -> "Document":
79
93
  """Create a new SuperDocument asset and return an opened Document.
80
94
 
@@ -120,6 +134,9 @@ class Document:
120
134
  asset_id=result["asset_id"],
121
135
  http_base_url=base_url,
122
136
  http_api_key=api_key,
137
+ user_name=user_name,
138
+ user_email=user_email,
139
+ track_revisions=track_revisions,
123
140
  )
124
141
 
125
142
  # ---- Public properties ----
@@ -262,10 +279,16 @@ class Document:
262
279
 
263
280
  @property
264
281
  def settings(self):
265
- """Return a minimal Settings proxy (stubbed; Superdoc doesn't surface app settings)."""
282
+ """Return a minimal Settings proxy.
283
+
284
+ Most Word app settings aren't surfaced by Superdoc.
285
+ ``Settings.track_revisions`` is wired through to
286
+ :attr:`Document.track_revisions` so python-docx-style call sites
287
+ (``doc.settings.track_revisions = True``) work end-to-end.
288
+ """
266
289
  from docx.settings import Settings
267
290
 
268
- return Settings(session=self._session)
291
+ return Settings(session=self._session, document=self)
269
292
 
270
293
  @property
271
294
  def element(self):
@@ -274,6 +297,121 @@ class Document:
274
297
 
275
298
  part = element
276
299
 
300
+ # ---- Track changes (Athena extension — see docx.revisions) ----
301
+
302
+ @property
303
+ def track_revisions(self) -> bool:
304
+ """Whether subsequent mutations are recorded as tracked
305
+ revisions instead of direct edits.
306
+
307
+ Setting this to ``True`` flushes any pending buffered commands
308
+ (so they retain their previous semantics) before switching the
309
+ mode for new commands. Toggling repeatedly is safe — a no-op
310
+ when the value doesn't change.
311
+
312
+ DEVIATION FROM python-docx: upstream has no top-level
313
+ ``track_revisions`` property. The closest thing is
314
+ ``Document.settings.element`` carrying ``<w:trackChanges/>``.
315
+ We expose this as a Document-level toggle for ergonomics; the
316
+ ``Document.settings.track_revisions`` alias gives parity-
317
+ friendly call sites the same surface.
318
+ """
319
+ return self._pending_track_revisions
320
+
321
+ @track_revisions.setter
322
+ def track_revisions(self, value: bool) -> None:
323
+ new_value = bool(value)
324
+ if new_value == self._pending_track_revisions:
325
+ return
326
+ self._pending_track_revisions = new_value
327
+ # If the session is open, push immediately so subsequent
328
+ # mutations land under the new mode. Otherwise the value is
329
+ # captured and synced at session-open time.
330
+ buffer = self._buffer()
331
+ if buffer is not None:
332
+ buffer.set_change_mode("tracked" if new_value else None)
333
+
334
+ @contextmanager
335
+ def tracked_revisions(self) -> "Generator[None, None, None]":
336
+ """Scope ``track_revisions=True`` to a ``with`` block.
337
+
338
+ Restores the previous value (typically ``False``) on exit, even
339
+ if the body raises. Useful for "make these specific edits as
340
+ tracked changes" without leaking the mode to surrounding code::
341
+
342
+ with doc.tracked_revisions():
343
+ doc.add_paragraph("Reviewer note: please verify.")
344
+ # ...
345
+
346
+ The mode change forces a buffer flush at entry and exit, so
347
+ prior buffered ops keep their original mode.
348
+ """
349
+ previous: bool = self._pending_track_revisions
350
+ self.track_revisions = True
351
+ try:
352
+ yield
353
+ finally:
354
+ self.track_revisions = previous
355
+
356
+ @property
357
+ def user_name(self) -> "str | None":
358
+ """The author identity threaded through to SuperDoc on
359
+ session-open, used for tracked-change attribution.
360
+
361
+ ``None`` means "fall back to the docx-studio service account".
362
+ Setting after the session is open does not retro-actively
363
+ rename existing changes — SuperDoc honors the value at session
364
+ creation.
365
+ """
366
+ return self._pending_user_name
367
+
368
+ @user_name.setter
369
+ def user_name(self, value: "str | None") -> None:
370
+ if value is not None and not isinstance(value, str):
371
+ raise ValidationError(
372
+ f"user_name must be a string or None; got {type(value)!r}"
373
+ )
374
+ self._pending_user_name = value or None
375
+ buffer = self._buffer()
376
+ if buffer is not None:
377
+ buffer.set_user(self._pending_user_name, self._pending_user_email)
378
+
379
+ @property
380
+ def user_email(self) -> "str | None":
381
+ """Optional email displayed alongside the author name in
382
+ tracked-change tooltips. Same lifecycle caveats as
383
+ :attr:`user_name`."""
384
+ return self._pending_user_email
385
+
386
+ @user_email.setter
387
+ def user_email(self, value: "str | None") -> None:
388
+ if value is not None and not isinstance(value, str):
389
+ raise ValidationError(
390
+ f"user_email must be a string or None; got {type(value)!r}"
391
+ )
392
+ self._pending_user_email = value or None
393
+ buffer = self._buffer()
394
+ if buffer is not None:
395
+ buffer.set_user(self._pending_user_name, self._pending_user_email)
396
+
397
+ @property
398
+ def revisions(self) -> "Revisions":
399
+ """Return the document's tracked-changes collection.
400
+
401
+ Iterating yields :class:`docx.revisions.Revision` proxies.
402
+ Bulk-resolve via :meth:`Revisions.accept_all` /
403
+ :meth:`Revisions.reject_all`; per-change resolve via
404
+ :meth:`Revision.accept` / :meth:`Revision.reject`.
405
+
406
+ DEVIATION FROM python-docx: upstream has no track-changes API
407
+ whatsoever. This is an Athena extension wired to SuperDoc's
408
+ ``doc.trackChanges.{list,get,decide}``.
409
+ """
410
+ from docx.revisions import Revisions
411
+
412
+ self._ensure_open()
413
+ return Revisions(session=self._session)
414
+
277
415
  # ---- Comments (stubbed — see docx-studio/PYTHON_DOCX_PARITY_GAPS.md § 3.1) ----
278
416
 
279
417
  @property
@@ -661,15 +799,20 @@ class Document:
661
799
  run_sync(self._session.save(in_place=True))
662
800
  self._saved = True
663
801
 
664
- def save(self, path_or_stream: "str | BinaryIO") -> None: # noqa: ARG002
802
+ def save(self, path_or_stream: "str | BinaryIO | None" = None) -> None: # noqa: ARG002
665
803
  """Flush pending edits to Keryx.
666
804
 
667
- ``path_or_stream`` matches python-docx's ``Document.save`` signature
668
- and is required to match the upstream contract — calling
669
- ``save()`` with no argument is a TypeError, just like python-docx.
670
- The argument is accepted but ignored at runtime: writes are always
671
- in-place against the Y.Doc. To export to a local file, use the
672
- Olympus UI's Export DOCX.
805
+ Intentional deviation from python-docx: ``path_or_stream`` is
806
+ optional. Stock python-docx requires it and raises ``TypeError``
807
+ on ``save()`` no-arg, but in this asset-backed SDK there is no
808
+ local file to write to writes are always in-place against the
809
+ Y.Doc. Agent-generated code reflexively calls ``doc.save()`` and
810
+ forcing the upstream signature broke every such invocation
811
+ (`Untitled Word Document` session
812
+ ``thread_3f6b59e0-ae25-4f6e-8391-c286b17a3334``). The argument
813
+ is accepted for parity-friendly call sites but ignored at
814
+ runtime. To export to a local file, use the Olympus UI's
815
+ Export DOCX.
673
816
  """
674
817
  self._flush()
675
818
 
@@ -688,9 +831,9 @@ class Document:
688
831
  def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001
689
832
  if exc_type is None and not self._saved and self._session.is_open:
690
833
  try:
691
- # Use _flush(), not save() python-docx requires
692
- # save(path_or_stream); the context-manager path has no
693
- # path to forward, so it drains internally.
834
+ # Drain via _flush() directly save() works no-arg as
835
+ # an intentional deviation, but skipping it avoids the
836
+ # public-API hop on the autosave path.
694
837
  self._flush()
695
838
  except Exception as e:
696
839
  _log_warn(f"autosave failed for {self._session.asset_id}: {e}")
@@ -705,6 +848,36 @@ class Document:
705
848
  )
706
849
  if not self._session.is_open:
707
850
  run_sync(self._session.open())
851
+ # Propagate pending track-changes state to the freshly-built
852
+ # buffer. Done here (and not at construction) because the
853
+ # buffer doesn't exist until session.open() runs.
854
+ self._sync_track_changes_state()
855
+
856
+ def _buffer(self):
857
+ """Return the live :class:`CommandBuffer`, or ``None`` if the
858
+ session hasn't been opened yet."""
859
+ handle = getattr(self._session, "_doc_handle", None)
860
+ return getattr(handle, "buffer", None) if handle is not None else None
861
+
862
+ def _sync_track_changes_state(self) -> None:
863
+ """Push pending track-changes state into the buffer.
864
+
865
+ Called when the session is opened (deferred init) and on
866
+ subsequent property setters. Idempotent — applying twice with
867
+ the same values is cheap (the buffer skips the flush when the
868
+ value didn't change).
869
+ """
870
+ buffer = self._buffer()
871
+ if buffer is None:
872
+ return
873
+ if self._pending_user_name is not None or self._pending_user_email is not None:
874
+ buffer.set_user(
875
+ self._pending_user_name,
876
+ self._pending_user_email,
877
+ )
878
+ buffer.set_change_mode(
879
+ "tracked" if self._pending_track_revisions else None
880
+ )
708
881
 
709
882
 
710
883
  # ---- Module-level helpers ----