unify NAPI and Python binding APIs for consistent surface

Both bindings now expose the same 6 function families: process, detect,
classify, extractText, extractTextWithPositions, and extractTextInRegions.
Bumps PyO3 from 0.22 to 0.25 for Python 3.14 support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Abimael Martell
2026-04-02 11:48:18 -07:00
co-authored by Claude Opus 4.6
parent 5f829f5258
commit 506b2a0c70
11 changed files with 935 additions and 199 deletions
+144 -8
View File
@@ -11,6 +11,11 @@ def fixture_path(name: str) -> str:
return os.path.join(FIXTURES_DIR, name)
def fixture_bytes(name: str) -> bytes:
with open(fixture_path(name), "rb") as f:
return f.read()
# ---------------------------------------------------------------------------
# process_pdf
# ---------------------------------------------------------------------------
@@ -61,15 +66,13 @@ class TestProcessPdf:
class TestProcessPdfBytes:
def test_basic(self):
with open(fixture_path("thermo-freon12.pdf"), "rb") as f:
data = f.read()
data = fixture_bytes("thermo-freon12.pdf")
result = pdf_inspector.process_pdf_bytes(data)
assert result.pdf_type == "text_based"
assert result.markdown is not None
def test_with_pages(self):
with open(fixture_path("thermo-freon12.pdf"), "rb") as f:
data = f.read()
data = fixture_bytes("thermo-freon12.pdf")
result = pdf_inspector.process_pdf_bytes(data, pages=[1, 2])
assert result.markdown is not None
@@ -87,15 +90,48 @@ class TestDetectPdf:
assert result.page_count == 3
def test_detect_bytes(self):
with open(fixture_path("thermo-freon12.pdf"), "rb") as f:
data = f.read()
data = fixture_bytes("thermo-freon12.pdf")
result = pdf_inspector.detect_pdf_bytes(data)
assert result.pdf_type == "text_based"
assert result.markdown is None
# ---------------------------------------------------------------------------
# extract_text
# classify_pdf / classify_pdf_bytes
# ---------------------------------------------------------------------------
class TestClassifyPdf:
def test_classify_file(self):
result = pdf_inspector.classify_pdf(fixture_path("thermo-freon12.pdf"))
assert result.pdf_type == "text_based"
assert result.page_count == 3
assert result.confidence > 0.0
assert isinstance(result.pages_needing_ocr, list)
def test_classify_bytes(self):
data = fixture_bytes("thermo-freon12.pdf")
result = pdf_inspector.classify_pdf_bytes(data)
assert result.pdf_type == "text_based"
assert result.page_count == 3
assert result.confidence > 0.0
def test_classify_repr(self):
result = pdf_inspector.classify_pdf(fixture_path("thermo-freon12.pdf"))
r = repr(result)
assert "PdfClassification" in r
assert "text_based" in r
def test_classify_fields(self):
result = pdf_inspector.classify_pdf(fixture_path("thermo-freon12.pdf"))
assert isinstance(result.pdf_type, str)
assert isinstance(result.page_count, int)
assert isinstance(result.pages_needing_ocr, list)
assert isinstance(result.confidence, float)
# ---------------------------------------------------------------------------
# extract_text / extract_text_bytes
# ---------------------------------------------------------------------------
@@ -105,9 +141,20 @@ class TestExtractText:
assert isinstance(text, str)
assert len(text) > 0
def test_bytes(self):
data = fixture_bytes("thermo-freon12.pdf")
text = pdf_inspector.extract_text_bytes(data)
assert isinstance(text, str)
assert len(text) > 0
def test_bytes_matches_file(self):
text_file = pdf_inspector.extract_text(fixture_path("thermo-freon12.pdf"))
text_bytes = pdf_inspector.extract_text_bytes(fixture_bytes("thermo-freon12.pdf"))
assert text_file == text_bytes
# ---------------------------------------------------------------------------
# extract_text_with_positions
# extract_text_with_positions / extract_text_with_positions_bytes
# ---------------------------------------------------------------------------
@@ -144,6 +191,77 @@ class TestExtractTextWithPositions:
r = repr(items[0])
assert "TextItem" in r
def test_bytes(self):
data = fixture_bytes("thermo-freon12.pdf")
items = pdf_inspector.extract_text_with_positions_bytes(data)
assert len(items) > 0
assert isinstance(items[0].text, str)
def test_bytes_with_pages(self):
data = fixture_bytes("thermo-freon12.pdf")
items = pdf_inspector.extract_text_with_positions_bytes(data, pages=[1])
assert len(items) > 0
assert all(item.page == 1 for item in items)
# ---------------------------------------------------------------------------
# extract_text_in_regions / extract_text_in_regions_bytes
# ---------------------------------------------------------------------------
class TestExtractTextInRegions:
def test_file(self):
results = pdf_inspector.extract_text_in_regions(
fixture_path("thermo-freon12.pdf"),
[(0, [[0.0, 0.0, 600.0, 100.0]])],
)
assert len(results) == 1
assert results[0].page == 0
assert len(results[0].regions) == 1
assert isinstance(results[0].regions[0].text, str)
assert isinstance(results[0].regions[0].needs_ocr, bool)
def test_bytes(self):
data = fixture_bytes("thermo-freon12.pdf")
results = pdf_inspector.extract_text_in_regions_bytes(
data,
[(0, [[0.0, 0.0, 600.0, 100.0]])],
)
assert len(results) == 1
assert results[0].page == 0
assert len(results[0].regions) == 1
assert isinstance(results[0].regions[0].text, str)
def test_repr(self):
results = pdf_inspector.extract_text_in_regions(
fixture_path("thermo-freon12.pdf"),
[(0, [[0.0, 0.0, 600.0, 100.0]])],
)
r = repr(results[0])
assert "PageRegionTexts" in r
r2 = repr(results[0].regions[0])
assert "RegionText" in r2
def test_multiple_regions(self):
results = pdf_inspector.extract_text_in_regions(
fixture_path("thermo-freon12.pdf"),
[(0, [[0.0, 0.0, 300.0, 100.0], [300.0, 0.0, 600.0, 100.0]])],
)
assert len(results) == 1
assert len(results[0].regions) == 2
def test_multiple_pages(self):
results = pdf_inspector.extract_text_in_regions(
fixture_path("thermo-freon12.pdf"),
[
(0, [[0.0, 0.0, 600.0, 100.0]]),
(1, [[0.0, 0.0, 600.0, 100.0]]),
],
)
assert len(results) == 2
assert results[0].page == 0
assert results[1].page == 1
# ---------------------------------------------------------------------------
# Error handling
@@ -163,6 +281,24 @@ class TestErrors:
with pytest.raises(ValueError):
pdf_inspector.process_pdf_bytes(b"")
def test_classify_not_a_pdf(self):
with pytest.raises(ValueError):
pdf_inspector.classify_pdf_bytes(b"not a pdf")
def test_classify_nonexistent(self):
with pytest.raises((ValueError, OSError)):
pdf_inspector.classify_pdf("/nonexistent/file.pdf")
def test_extract_text_bytes_not_a_pdf(self):
with pytest.raises(ValueError):
pdf_inspector.extract_text_bytes(b"not a pdf")
def test_regions_not_a_pdf(self):
with pytest.raises(ValueError):
pdf_inspector.extract_text_in_regions_bytes(
b"not a pdf", [(0, [[0.0, 0.0, 100.0, 100.0]])]
)
# ---------------------------------------------------------------------------
# Multiple fixtures