tabella-docs 2.0.3__tar.gz → 2.0.4__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.
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/PKG-INFO +1 -1
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/pyproject.toml +1 -1
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella/__init__.py +1 -1
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella/read.py +10 -4
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella_docs.egg-info/PKG-INFO +1 -1
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella_docs.egg-info/SOURCES.txt +1 -0
- tabella_docs-2.0.4/tests/test_read_ocr_fallback.py +49 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/LICENSE +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/README.md +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/setup.cfg +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella/bootstrap.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella/convert.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella/create.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella/utils.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella_docs.egg-info/dependency_links.txt +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella_docs.egg-info/requires.txt +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tabella_docs.egg-info/top_level.txt +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tests/test_bootstrap_paths.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tests/test_ensure_venv_win.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tests/test_odt_table.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tests/test_security_gates.py +0 -0
- {tabella_docs-2.0.3 → tabella_docs-2.0.4}/tests/test_text_section_alias.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tabella-docs
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: Portable document lifecycle suite — create (.docx/.xlsx/.pptx/.pdf/.odt/.ods/.odp), convert (pandoc), and read (anydoc + LiteParse OCR) documents. Agent Plugins v1 compatible.
|
|
5
5
|
Author-email: D Yiapanis <d@yiapanis.co>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tabella-docs"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.4"
|
|
8
8
|
description = "Portable document lifecycle suite — create (.docx/.xlsx/.pptx/.pdf/.odt/.ods/.odp), convert (pandoc), and read (anydoc + LiteParse OCR) documents. Agent Plugins v1 compatible."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -104,7 +104,8 @@ def read(path: str, max_chars: Optional[int] = None, extract_images: bool = True
|
|
|
104
104
|
1. Plain text → direct file read (fast path)
|
|
105
105
|
2. Office formats + text PDFs → anydoc (Markdown output)
|
|
106
106
|
3. Images → LiteParse (OCR)
|
|
107
|
-
4. PDFs
|
|
107
|
+
4. PDFs with no text layer — anydoc returns empty or raises NeedsOcrError →
|
|
108
|
+
LiteParse (OCR fallback for scanned PDFs)
|
|
108
109
|
"""
|
|
109
110
|
suffix = Path(path).suffix.lower()
|
|
110
111
|
|
|
@@ -126,15 +127,20 @@ def read(path: str, max_chars: Optional[int] = None, extract_images: bool = True
|
|
|
126
127
|
anydoc_error = None
|
|
127
128
|
try:
|
|
128
129
|
text = _run_anydoc(path)
|
|
129
|
-
except
|
|
130
|
-
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# A fallback boundary: any primary-engine failure means "try the
|
|
132
|
+
# other engine". anydoc raises NeedsOcrError for a PDF with no text
|
|
133
|
+
# layer, and its hierarchy (NeedsOcrError -> ConvertError ->
|
|
134
|
+
# Exception) is theirs, not ours — enumerating it is what made the
|
|
135
|
+
# OCR lane below unreachable for scanned PDFs.
|
|
136
|
+
anydoc_error = f"{type(e).__name__}: {e}"
|
|
131
137
|
|
|
132
138
|
# 4. If anydoc returned empty text (scanned PDF) or failed, try LiteParse OCR
|
|
133
139
|
if not text.strip():
|
|
134
140
|
try:
|
|
135
141
|
data = _run_liteparse(path, extract_images=extract_images)
|
|
136
142
|
text = data.get("text") or ""
|
|
137
|
-
except
|
|
143
|
+
except Exception as liteparse_err:
|
|
138
144
|
if anydoc_error:
|
|
139
145
|
raise RuntimeError(f"anydoc failed: {anydoc_error}; liteparse fallback also failed: {liteparse_err}")
|
|
140
146
|
raise RuntimeError(f"both anydoc and liteparse returned no text. liteparse: {liteparse_err}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tabella-docs
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: Portable document lifecycle suite — create (.docx/.xlsx/.pptx/.pdf/.odt/.ods/.odp), convert (pandoc), and read (anydoc + LiteParse OCR) documents. Agent Plugins v1 compatible.
|
|
5
5
|
Author-email: D Yiapanis <d@yiapanis.co>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""doc_read on an image-only PDF must reach the OCR lane, not raise.
|
|
2
|
+
|
|
3
|
+
anydoc signals a missing text layer by *raising* NeedsOcrError (MRO:
|
|
4
|
+
NeedsOcrError -> ConvertError -> Exception, so not a RuntimeError), where the
|
|
5
|
+
fallback was gated on empty text and caught only RuntimeError/ImportError.
|
|
6
|
+
Net effect: the fallback was unreachable for exactly the input it exists for.
|
|
7
|
+
|
|
8
|
+
Runs the real pipeline — PIL makes the page, Tesseract reads it — because a
|
|
9
|
+
mocked empty return is what let this ship.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
pytest.importorskip("PIL")
|
|
17
|
+
pytest.importorskip("anydoc")
|
|
18
|
+
pytest.importorskip("liteparse")
|
|
19
|
+
|
|
20
|
+
from PIL import Image, ImageDraw, ImageFont # noqa: E402
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _page(tmp_path: Path):
|
|
24
|
+
img = Image.new("RGB", (1200, 400), "white")
|
|
25
|
+
draw = ImageDraw.Draw(img)
|
|
26
|
+
try:
|
|
27
|
+
font = ImageFont.truetype(
|
|
28
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 64
|
|
29
|
+
)
|
|
30
|
+
except OSError:
|
|
31
|
+
font = ImageFont.load_default(size=64)
|
|
32
|
+
draw.text((60, 120), "SCANNED HELLO 42", fill="black", font=font)
|
|
33
|
+
return img
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_image_only_pdf_falls_back_to_ocr(tmp_path):
|
|
37
|
+
png, pdf = tmp_path / "scan.png", tmp_path / "scan.pdf"
|
|
38
|
+
img = _page(tmp_path)
|
|
39
|
+
img.save(png)
|
|
40
|
+
img.save(pdf, "PDF", resolution=150.0)
|
|
41
|
+
|
|
42
|
+
from tabella.read import read
|
|
43
|
+
|
|
44
|
+
from_png = " ".join(read(str(png)).split())
|
|
45
|
+
from_pdf = " ".join(read(str(pdf)).split())
|
|
46
|
+
|
|
47
|
+
assert "HELLO" in from_png.upper(), f"OCR lane itself is broken: {from_png!r}"
|
|
48
|
+
assert from_pdf, "image-only PDF returned no text"
|
|
49
|
+
assert from_pdf == from_png, f"PDF and PNG disagree: {from_pdf!r} vs {from_png!r}"
|
|
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
|