dot-parser 2.1.1__tar.gz → 2.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.
- {dot_parser-2.1.1 → dot_parser-2.1.2}/CHANGELOG.md +12 -1
- {dot_parser-2.1.1 → dot_parser-2.1.2}/PKG-INFO +1 -1
- {dot_parser-2.1.1 → dot_parser-2.1.2}/pyproject.toml +1 -1
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/chunking.py +35 -25
- {dot_parser-2.1.1 → dot_parser-2.1.2}/tests/test_chunking.py +25 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/uv.lock +1 -1
- {dot_parser-2.1.1 → dot_parser-2.1.2}/.gitignore +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/.gitlab-ci.yml +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/.pre-commit-config.yaml +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/.python-version +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/AUTHORS.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/CONTRIBUTING.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/DCO +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/LICENSE.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/README.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/README.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/input/broken_encoding.pdf +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/input/image_only.pdf +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/input/native_equations.pdf +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/input/native_simple.pdf +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/input/native_tables.pdf +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_docling_broken_encoding.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_docling_image_only.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_docling_native_equations.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_docling_native_simple.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_docling_native_tables.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_llama_broken_encoding.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_llama_image_only.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_llama_native_equations.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_llama_native_simple.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_llama_native_tables.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_broken_encoding.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_image_only.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_equations.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_simple.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_tables.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_broken_encoding.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_image_only.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_native_equations.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_native_simple.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_native_tables.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_broken_encoding.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_image_only.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_native_equations.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_native_simple.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_native_tables.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/results.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/run.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/docs/DESIGN.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/docs/DEVELOPMENT.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/docs/IMAGE_EXTRACTION.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/docs/PUBLISHING.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/docs/VERSIONING.md +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/__init__.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/backends/__init__.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/backends/_base.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/backends/docling.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/backends/llama.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/backends/mistral.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/backends/pymu.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/docx_images.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/docx_markdown.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/image_utils.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/images.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/markdown_utils.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/models.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/parsers.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/pricing.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/tokens.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/src/dot_parser/vlms.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/tests/test_docx_markdown.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/tests/test_images.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/tests/test_mistral_batch_logging.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/tests/test_parsers.py +0 -0
- {dot_parser-2.1.1 → dot_parser-2.1.2}/tests/test_tokens.py +0 -0
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.2] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `chunk()` keeps every chunk within `max_tokens`. A paragraph larger than
|
|
15
|
+
the limit on its own (a long table, text without blank lines) is now split
|
|
16
|
+
instead of kept whole, and an undersized chunk is absorbed into its neighbour
|
|
17
|
+
only when the result still fits: a long run of heading-only sections (a table
|
|
18
|
+
of contents, an index) no longer piles up into a single chunk.
|
|
19
|
+
|
|
10
20
|
## [2.1.1] - 2026-09-30
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -90,7 +100,8 @@ this release.
|
|
|
90
100
|
- `parse_with_images(backend=...)` accepts DOCX- and PPTX-capable backends in
|
|
91
101
|
its type signature, matching what the function already supported at runtime.
|
|
92
102
|
|
|
93
|
-
[Unreleased]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.
|
|
103
|
+
[Unreleased]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.2...main
|
|
104
|
+
[2.1.2]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.1...v2.1.2
|
|
94
105
|
[2.1.1]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.0...v2.1.1
|
|
95
106
|
[2.1.0]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.0.0...v2.1.0
|
|
96
107
|
[2.0.0]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/tags/v2.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dot-parser
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Document-to-markdown parser and chunker for RAG pipelines
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
5
|
import statistics
|
|
6
|
+
from typing import cast
|
|
6
7
|
|
|
7
8
|
import semchunk
|
|
8
9
|
|
|
@@ -63,17 +64,16 @@ def _split_oversized(section: dict, max_tokens: int) -> list[dict]:
|
|
|
63
64
|
body = section["body"]
|
|
64
65
|
path = section["section_path"]
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
if count_tokens(
|
|
67
|
+
prefix = f"{heading}\n\n" if heading else ""
|
|
68
|
+
if count_tokens(prefix + body) <= max_tokens:
|
|
68
69
|
return [section]
|
|
69
70
|
|
|
70
71
|
# Try paragraph splitting first
|
|
71
72
|
paragraphs = re.split(r"\n\n+", body)
|
|
72
73
|
if len(paragraphs) > 1:
|
|
73
|
-
sub_chunks = _group_paragraphs(paragraphs, max_tokens,
|
|
74
|
+
sub_chunks = _group_paragraphs(paragraphs, max_tokens, prefix)
|
|
74
75
|
else:
|
|
75
|
-
|
|
76
|
-
sub_chunks = semchunk.chunk(body, chunk_size=max_tokens, token_counter=count_tokens)
|
|
76
|
+
sub_chunks = _split_text(body, max_tokens, prefix)
|
|
77
77
|
|
|
78
78
|
results = []
|
|
79
79
|
for i, text in enumerate(sub_chunks):
|
|
@@ -96,31 +96,37 @@ def _split_oversized(section: dict, max_tokens: int) -> list[dict]:
|
|
|
96
96
|
return results
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
def
|
|
100
|
-
"""
|
|
99
|
+
def _split_text(text: str, max_tokens: int, prefix: str) -> list[str]:
|
|
100
|
+
"""Split text semantically (semchunk) into pieces that fit within max_tokens
|
|
101
|
+
even with `prefix` (the heading the first piece carries) before them.
|
|
102
|
+
semchunk returns a bare list unless asked for offsets."""
|
|
103
|
+
pieces = semchunk.chunk(
|
|
104
|
+
text, chunk_size=max_tokens, token_counter=lambda t: count_tokens(prefix + t)
|
|
105
|
+
)
|
|
106
|
+
return cast("list[str]", pieces)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _group_paragraphs(paragraphs: list[str], max_tokens: int, prefix: str) -> list[str]:
|
|
110
|
+
"""Group paragraphs into chunks that fit within max_tokens, the first one
|
|
111
|
+
with `prefix` (its heading) before it. A paragraph too large on its own is
|
|
112
|
+
split semantically first."""
|
|
101
113
|
groups: list[str] = []
|
|
102
114
|
current: list[str] = []
|
|
103
|
-
current_tokens = 0
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
def fits(texts: list[str]) -> bool:
|
|
117
|
+
lead = "" if groups else prefix
|
|
118
|
+
return count_tokens(lead + "\n\n".join(texts)) <= max_tokens
|
|
108
119
|
|
|
109
120
|
for para in paragraphs:
|
|
110
121
|
para = para.strip()
|
|
111
122
|
if not para:
|
|
112
123
|
continue
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
current
|
|
119
|
-
current_tokens = para_tokens
|
|
120
|
-
first_group = False
|
|
121
|
-
else:
|
|
122
|
-
current.append(para)
|
|
123
|
-
current_tokens += para_tokens
|
|
124
|
+
pieces = [para] if fits([para]) else _split_text(para, max_tokens, prefix)
|
|
125
|
+
for piece in pieces:
|
|
126
|
+
if current and not fits([*current, piece]):
|
|
127
|
+
groups.append("\n\n".join(current))
|
|
128
|
+
current = []
|
|
129
|
+
current.append(piece)
|
|
124
130
|
|
|
125
131
|
if current:
|
|
126
132
|
groups.append("\n\n".join(current))
|
|
@@ -168,15 +174,19 @@ def _merge_small(sections: list[dict], max_tokens: int) -> list[dict]:
|
|
|
168
174
|
|
|
169
175
|
|
|
170
176
|
def _absorb_undersized(sections: list[dict], max_tokens: int) -> list[dict]:
|
|
171
|
-
"""Absorb chunks smaller than 15% of max_tokens into neighbors
|
|
177
|
+
"""Absorb chunks smaller than 15% of max_tokens into neighbors, when the
|
|
178
|
+
result still fits within max_tokens."""
|
|
172
179
|
threshold = max_tokens * 0.15
|
|
173
180
|
result: list[dict] = []
|
|
174
181
|
|
|
182
|
+
def fits(first: str, second: str) -> bool:
|
|
183
|
+
return count_tokens(first + "\n\n" + second) <= max_tokens
|
|
184
|
+
|
|
175
185
|
for section in sections:
|
|
176
186
|
text = _section_text(section)
|
|
177
187
|
tokens = count_tokens(text)
|
|
178
188
|
|
|
179
|
-
if tokens < threshold and result:
|
|
189
|
+
if tokens < threshold and result and fits(_section_text(result[-1]), text):
|
|
180
190
|
# Append to previous
|
|
181
191
|
prev = result[-1]
|
|
182
192
|
prev_text = _section_text(prev)
|
|
@@ -192,7 +202,7 @@ def _absorb_undersized(sections: list[dict], max_tokens: int) -> list[dict]:
|
|
|
192
202
|
# Check if first chunk is undersized and can be prepended to next
|
|
193
203
|
if len(result) >= 2:
|
|
194
204
|
first_text = _section_text(result[0])
|
|
195
|
-
if count_tokens(first_text) < threshold:
|
|
205
|
+
if count_tokens(first_text) < threshold and fits(first_text, _section_text(result[1])):
|
|
196
206
|
second = result[1]
|
|
197
207
|
second_text = _section_text(second)
|
|
198
208
|
result[1] = {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: Kannon For Deep Tech
|
|
2
2
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
4
6
|
from dot_parser import Chunk, chunk
|
|
7
|
+
from dot_parser.tokens import count_tokens
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class TestChunkBasic:
|
|
@@ -62,6 +65,28 @@ class TestSplitOversized:
|
|
|
62
65
|
assert c.section_path == ["# Big"]
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
class TestMaxTokensBound:
|
|
69
|
+
@pytest.mark.parametrize("merge", [True, False])
|
|
70
|
+
def test_an_oversized_paragraph_among_others_is_split(self, merge):
|
|
71
|
+
md = "# Big\n\nIntro.\n\n" + "word " * 2000 + "\n\nOutro."
|
|
72
|
+
chunks = chunk(md, max_tokens=64, merge=merge)
|
|
73
|
+
assert all(count_tokens(c.content) <= 64 for c in chunks)
|
|
74
|
+
assert chunks[0].heading == "# Big"
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize("merge", [True, False])
|
|
77
|
+
def test_a_table_without_blank_lines_is_split(self, merge):
|
|
78
|
+
table = "\n".join(f"| row {i} | 0.{i:04d} | 12.5 |" for i in range(500))
|
|
79
|
+
md = f"# Results\n\nThe table below.\n\n{table}\n\nEnd."
|
|
80
|
+
chunks = chunk(md, max_tokens=64, merge=merge)
|
|
81
|
+
assert all(count_tokens(c.content) <= 64 for c in chunks)
|
|
82
|
+
|
|
83
|
+
def test_a_run_of_heading_only_sections_stays_within_the_bound(self):
|
|
84
|
+
md = "# Index\n\n" + "\n\n".join(f"## Entry {i}" for i in range(300))
|
|
85
|
+
chunks = chunk(md, max_tokens=64)
|
|
86
|
+
assert all(count_tokens(c.content) <= 64 for c in chunks)
|
|
87
|
+
assert "## Entry 299" in chunks[-1].content
|
|
88
|
+
|
|
89
|
+
|
|
65
90
|
class TestMerge:
|
|
66
91
|
def test_small_sections_merged(self):
|
|
67
92
|
md = "# A\n\n## B\n\nShort.\n\n## C\n\nAlso short."
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_broken_encoding.md
RENAMED
|
File without changes
|
|
File without changes
|
{dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_equations.md
RENAMED
|
File without changes
|
{dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_simple.md
RENAMED
|
File without changes
|
{dot_parser-2.1.1 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_tables.md
RENAMED
|
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
|
|
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
|