athena-python-docx 0.1.0__tar.gz → 0.1.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.1.0 → athena_python_docx-0.1.2}/PKG-INFO +1 -1
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/__init__.py +1 -1
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/document.py +83 -17
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/pyproject.toml +1 -1
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/.gitignore +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/CLAUDE.md +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/README.md +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/_batching.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/api.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/client.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/enum/__init__.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/enum/table.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/enum/text.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/errors.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/shared.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/table.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/text/__init__.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/text/paragraph.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/text/run.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/docx/typing.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/scripts/publish.sh +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/tests/__init__.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/tests/conftest.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/tests/test_commands.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/tests/test_python_docx_api_parity.py +0 -0
- {athena_python_docx-0.1.0 → athena_python_docx-0.1.2}/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.
|
|
3
|
+
Version: 0.1.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>
|
|
@@ -100,22 +100,49 @@ class Document:
|
|
|
100
100
|
from docx.text.paragraph import Paragraph
|
|
101
101
|
|
|
102
102
|
self._ensure_open()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
|
|
104
|
+
# Superdoc's markdown parser rejects empty input with
|
|
105
|
+
# "Markdown produced no content to insert." For an empty paragraph
|
|
106
|
+
# (python-docx's `doc.add_paragraph()` with no args), we use the
|
|
107
|
+
# text-type insert with a single space and immediately clear it —
|
|
108
|
+
# same pattern as `agora/web/api/super_docs/superdoc_write_utils.py`
|
|
109
|
+
# around line 1085.
|
|
110
|
+
result: dict
|
|
111
|
+
node_id: str
|
|
112
|
+
if not text and not (style and style.lower().startswith("heading")):
|
|
113
|
+
result = run_sync(
|
|
114
|
+
self._session.doc.insert(
|
|
115
|
+
{"value": " ", "type": "text"},
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
node_id = _extract_inserted_node_id(result, expected_type="paragraph")
|
|
119
|
+
if node_id:
|
|
120
|
+
# Clear the placeholder space so the paragraph reads as empty
|
|
121
|
+
# (matches python-docx semantics: a freshly-added blank paragraph
|
|
122
|
+
# has empty `.text`).
|
|
123
|
+
run_sync(
|
|
124
|
+
self._session.doc.blocks.update_text(
|
|
125
|
+
{"nodeId": node_id, "text": ""},
|
|
126
|
+
),
|
|
127
|
+
)
|
|
110
128
|
else:
|
|
111
|
-
md
|
|
129
|
+
md: str
|
|
130
|
+
if style and style.lower().startswith("heading"):
|
|
131
|
+
try:
|
|
132
|
+
level: int = int(style.rsplit(" ", 1)[-1])
|
|
133
|
+
except ValueError:
|
|
134
|
+
level = 1
|
|
135
|
+
md = f"{'#' * level} {text}\n\n"
|
|
136
|
+
else:
|
|
137
|
+
md = f"{text}\n\n"
|
|
138
|
+
|
|
139
|
+
result = run_sync(
|
|
140
|
+
self._session.doc.insert(
|
|
141
|
+
{"value": md, "type": "markdown"},
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
node_id = _extract_inserted_node_id(result, expected_type="paragraph")
|
|
112
145
|
|
|
113
|
-
result: dict = run_sync(
|
|
114
|
-
self._session.doc.insert(
|
|
115
|
-
{"value": md, "type": "markdown"},
|
|
116
|
-
),
|
|
117
|
-
)
|
|
118
|
-
node_id: str = _extract_inserted_node_id(result, expected_type="paragraph")
|
|
119
146
|
if not node_id:
|
|
120
147
|
raise RuntimeError(
|
|
121
148
|
f"Superdoc did not return a nodeId for add_paragraph: {result!r}",
|
|
@@ -306,12 +333,50 @@ class Document:
|
|
|
306
333
|
# ---- Module-level helpers ----
|
|
307
334
|
|
|
308
335
|
def _extract_inserted_node_id(result: dict, *, expected_type: str) -> str:
|
|
309
|
-
"""Parse Superdoc's insert() response to find the
|
|
310
|
-
|
|
311
|
-
Superdoc rotates response
|
|
336
|
+
"""Parse Superdoc's insert() response to find the relevant blockId.
|
|
337
|
+
|
|
338
|
+
Superdoc rotates response shapes across versions; we probe in order
|
|
339
|
+
of "most current" first. Real observed shape (superdoc-sdk 1.6.0.dev29):
|
|
340
|
+
|
|
341
|
+
{
|
|
342
|
+
"receipt": {
|
|
343
|
+
"resolution": {
|
|
344
|
+
"target": {"kind": "text", "blockId": "<uuid>", "range": {...}}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
...
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
Older/alternate shapes we also accept:
|
|
351
|
+
- top-level ``blockId``
|
|
352
|
+
- nested ``result.insertedNodes[].nodeId`` (when typed = expected_type)
|
|
353
|
+
- last element of ``result.nodes``
|
|
312
354
|
"""
|
|
313
355
|
if not isinstance(result, dict):
|
|
314
356
|
return ""
|
|
357
|
+
|
|
358
|
+
# Shape 1 (current): receipt.resolution.target.blockId
|
|
359
|
+
receipt: object = result.get("receipt")
|
|
360
|
+
if isinstance(receipt, dict):
|
|
361
|
+
resolution: object = receipt.get("resolution")
|
|
362
|
+
if isinstance(resolution, dict):
|
|
363
|
+
target: object = resolution.get("target")
|
|
364
|
+
if isinstance(target, dict):
|
|
365
|
+
block_id: str = str(target.get("blockId", ""))
|
|
366
|
+
if block_id:
|
|
367
|
+
return block_id
|
|
368
|
+
|
|
369
|
+
# Shape 2: top-level blockId or target.blockId
|
|
370
|
+
top_block: object = result.get("blockId")
|
|
371
|
+
if isinstance(top_block, str) and top_block:
|
|
372
|
+
return top_block
|
|
373
|
+
top_target: object = result.get("target")
|
|
374
|
+
if isinstance(top_target, dict):
|
|
375
|
+
tb: str = str(top_target.get("blockId", ""))
|
|
376
|
+
if tb:
|
|
377
|
+
return tb
|
|
378
|
+
|
|
379
|
+
# Shape 3 (legacy): nested result.result.insertedNodes[] / nodes[]
|
|
315
380
|
data_obj: object = result.get("result", {})
|
|
316
381
|
data: dict = data_obj if isinstance(data_obj, dict) else {}
|
|
317
382
|
nodes_obj: object = data.get("insertedNodes", data.get("nodes", []))
|
|
@@ -323,6 +388,7 @@ def _extract_inserted_node_id(result: dict, *, expected_type: str) -> str:
|
|
|
323
388
|
return str(node.get("nodeId", ""))
|
|
324
389
|
if nodes and isinstance(nodes[-1], dict):
|
|
325
390
|
return str(nodes[-1].get("nodeId", ""))
|
|
391
|
+
|
|
326
392
|
return ""
|
|
327
393
|
|
|
328
394
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-docx"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|