athena-python-docx 0.1.6__tar.gz → 0.1.8__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 (31) hide show
  1. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/PKG-INFO +1 -1
  2. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/__init__.py +1 -1
  3. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/text/paragraph.py +38 -11
  4. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/pyproject.toml +1 -1
  5. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/fidelity/cases.py +1 -1
  6. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/fidelity/runner.py +32 -1
  7. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/.gitignore +0 -0
  8. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/CLAUDE.md +0 -0
  9. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/README.md +0 -0
  10. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/_batching.py +0 -0
  11. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/api.py +0 -0
  12. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/client.py +0 -0
  13. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/document.py +0 -0
  14. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/enum/__init__.py +0 -0
  15. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/enum/table.py +0 -0
  16. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/enum/text.py +0 -0
  17. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/errors.py +0 -0
  18. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/shared.py +0 -0
  19. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/table.py +0 -0
  20. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/text/__init__.py +0 -0
  21. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/text/run.py +0 -0
  22. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/docx/typing.py +0 -0
  23. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/scripts/publish.sh +0 -0
  24. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/__init__.py +0 -0
  25. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/conftest.py +0 -0
  26. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/fidelity/README.md +0 -0
  27. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/fidelity/__init__.py +0 -0
  28. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/fidelity/extract.py +0 -0
  29. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/test_commands.py +0 -0
  30. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/test_python_docx_api_parity.py +0 -0
  31. {athena_python_docx-0.1.6 → athena_python_docx-0.1.8}/tests/test_smoke_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-docx
3
- Version: 0.1.6
3
+ Version: 0.1.8
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.1.6"
9
+ __version__ = "0.1.8"
10
10
 
11
11
  from docx.api import Document
12
12
 
@@ -36,18 +36,38 @@ _STUB_SUFFIX = (
36
36
 
37
37
 
38
38
  def _node_text(session: "Session", node_id: str) -> str:
39
- """Fetch a block's full text via doc.get_node_by_id."""
39
+ """Fetch a block's full text via doc.get_node_by_id.
40
+
41
+ The response shape for a paragraph is:
42
+ {"node": {"paragraph": {"inlines": [{"run": {"text": "..."}}, ...]}}}
43
+ We concatenate each run's ``text`` in order. Non-paragraph blocks or
44
+ empty responses return ``""``.
45
+ """
40
46
  info: object = run_sync(
41
47
  session.doc.get_node_by_id({"id": node_id}),
42
48
  )
43
49
  if not isinstance(info, dict):
44
50
  return ""
45
51
  node: object = info.get("node")
46
- if isinstance(node, dict):
47
- text: object = node.get("text")
52
+ if not isinstance(node, dict):
53
+ return ""
54
+ paragraph: object = node.get("paragraph")
55
+ if not isinstance(paragraph, dict):
56
+ return ""
57
+ inlines: object = paragraph.get("inlines")
58
+ if not isinstance(inlines, list):
59
+ return ""
60
+ parts: list[str] = []
61
+ for inline in inlines:
62
+ if not isinstance(inline, dict):
63
+ continue
64
+ run: object = inline.get("run")
65
+ if not isinstance(run, dict):
66
+ continue
67
+ text: object = run.get("text")
48
68
  if isinstance(text, str):
49
- return text
50
- return ""
69
+ parts.append(text)
70
+ return "".join(parts)
51
71
 
52
72
 
53
73
  class Paragraph:
@@ -131,17 +151,24 @@ class Paragraph:
131
151
  current: str = _node_text(self._session, self._node_id)
132
152
  start: int = len(current)
133
153
 
134
- # Insert at end of the block. The insert params support targeting
135
- # a block + offset: {blockId, start, end} at top level where start==end
136
- # means an insertion point (not a range).
154
+ # Superdoc expects a selection target with text cursors. A zero-
155
+ # length selection (start == end) is an insertion point, not a
156
+ # replace-range.
157
+ cursor: dict = {
158
+ "kind": "text",
159
+ "blockId": self._node_id,
160
+ "offset": start,
161
+ }
137
162
  run_sync(
138
163
  self._session.doc.insert(
139
164
  {
165
+ "target": {
166
+ "kind": "selection",
167
+ "start": cursor,
168
+ "end": cursor,
169
+ },
140
170
  "value": text,
141
171
  "type": "text",
142
- "blockId": self._node_id,
143
- "start": start,
144
- "end": start,
145
172
  },
146
173
  ),
147
174
  )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-docx"
7
- version = "0.1.6"
7
+ version = "0.1.8"
8
8
  description = "Drop-in replacement for python-docx that connects to Athena's Superdoc/Keryx collaborative document stack"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -119,7 +119,7 @@ CASES: list[Case] = [
119
119
  ),
120
120
  Case(
121
121
  name="cell_text_setter",
122
- description="Set cell(0,0).text on a 2x2 table — known NotImplementedError in 0.1.5.",
122
+ description="Set cell(0,0).text on a 2x2 table — known NotImplementedError (Phase 2 stub).",
123
123
  script=(
124
124
  "t = doc.add_table(rows=2, cols=2)\n"
125
125
  't.cell(0, 0).text = "A1"'
@@ -128,6 +128,10 @@ import os
128
128
  import traceback
129
129
  from pathlib import Path
130
130
 
131
+ import docx as _docx_mod
132
+ print(f"[sandbox] athena-python-docx __version__ = {{_docx_mod.__version__}}")
133
+ print(f"[sandbox] loaded from {{_docx_mod.__file__}}")
134
+
131
135
  OUT_DIR = Path("/tmp/fidelity_out")
132
136
  OUT_DIR.mkdir(parents=True, exist_ok=True)
133
137
  ASSET_ID = {asset_id!r}
@@ -295,6 +299,24 @@ async def run_daytona_batch(cases: list[Case], asset_id: str) -> tuple[list[dict
295
299
  print(f"[fidelity] sandbox {sandbox.id} started")
296
300
 
297
301
  try:
302
+ # Upload the latest-built athena-python-docx wheel so the sandbox runs
303
+ # whichever version is currently in dist/, not the one baked into the
304
+ # image. This keeps the fidelity harness honest during SDK iteration.
305
+ wheel_dir = _SDK_ROOT / "dist"
306
+ wheels = sorted(wheel_dir.glob("athena_python_docx-*.whl"))
307
+ if wheels:
308
+ latest_wheel = wheels[-1]
309
+ await sandbox.fs.upload_file(
310
+ latest_wheel.read_bytes(),
311
+ f"/tmp/{latest_wheel.name}",
312
+ )
313
+ install_cmd = f"pip install --quiet --force-reinstall --no-deps /tmp/{latest_wheel.name}"
314
+ install_res = await sandbox.process.exec(install_cmd, timeout=120)
315
+ print(
316
+ f"[fidelity] installed {latest_wheel.name} "
317
+ f"(exit={install_res.exit_code})",
318
+ )
319
+
298
320
  script = build_sandbox_script(cases, asset_id)
299
321
  await sandbox.fs.upload_file(script.encode(), "/tmp/fidelity_runner.py")
300
322
  exec_result = await sandbox.process.exec(
@@ -302,6 +324,12 @@ async def run_daytona_batch(cases: list[Case], asset_id: str) -> tuple[list[dict
302
324
  timeout=600,
303
325
  )
304
326
  print(f"[fidelity] sandbox exit_code={exec_result.exit_code}")
327
+ # Surface sandbox stdout so [sandbox] diagnostics are visible in the log.
328
+ sandbox_stdout = getattr(exec_result, "result", None) or ""
329
+ if sandbox_stdout:
330
+ print("─── sandbox stdout ─────────────────────────────────")
331
+ print(sandbox_stdout)
332
+ print("────────────────────────────────────────────────────")
305
333
  # Load the results JSON
306
334
  raw = await sandbox.fs.download_file("/tmp/fidelity_results.json")
307
335
  results: list[dict] = json.loads(raw.decode())
@@ -341,7 +369,10 @@ def print_scorecard(
341
369
  """Print a scorecard. Return exit code (0 = all green)."""
342
370
  print()
343
371
  print("=" * 88)
344
- print("FIDELITY SCORECARD — athena-python-docx 0.1.5 vs stock python-docx")
372
+ from docx import __version__ as _sdk_version
373
+ print(
374
+ f"FIDELITY SCORECARD — athena-python-docx {_sdk_version} vs stock python-docx",
375
+ )
345
376
  print("=" * 88)
346
377
 
347
378
  by_name = {r["name"]: r for r in daytona_results}