chunklet-py 2.3.1__tar.gz → 2.4.0__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 (64) hide show
  1. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/LICENSE +1 -1
  2. {chunklet_py-2.3.1/src/chunklet_py.egg-info → chunklet_py-2.4.0}/PKG-INFO +21 -13
  3. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/README.md +15 -4
  4. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/pyproject.toml +10 -20
  5. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/base_chunker.py +1 -2
  6. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/cli.py +2 -2
  7. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/code_chunker/_code_structure_extractor.py +21 -10
  8. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/code_chunker/code_chunker.py +77 -38
  9. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/deprecation.py +2 -1
  10. chunklet_py-2.4.0/src/chunklet/common/dotdict.py +176 -0
  11. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/path_utils.py +1 -2
  12. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/validation.py +12 -11
  13. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/_plain_text_chunker.py +7 -5
  14. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/rst_2_md.py +1 -3
  15. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/table_2_md.py +2 -17
  16. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/document_chunker.py +20 -8
  17. chunklet_py-2.4.0/src/chunklet/document_chunker/md_table.py +36 -0
  18. chunklet_py-2.4.0/src/chunklet/document_chunker/processors/eml_processor.py +117 -0
  19. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/epub_processor.py +1 -1
  20. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/odt_processor.py +0 -1
  21. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/pdf_processor.py +1 -3
  22. chunklet_py-2.4.0/src/chunklet/document_chunker/processors/pptx_processor.py +326 -0
  23. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/span_finder.py +9 -11
  24. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/_universal_splitter.py +8 -10
  25. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/languages.py +10 -40
  26. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/sentence_splitter.py +13 -20
  27. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/terminators.py +0 -2
  28. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/index.html +2 -1
  29. {chunklet_py-2.3.1 → chunklet_py-2.4.0/src/chunklet_py.egg-info}/PKG-INFO +21 -13
  30. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/SOURCES.txt +4 -0
  31. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/requires.txt +4 -7
  32. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/tests/test_document_chunking.py +3 -0
  33. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/tests/test_plain_text_chunking.py +5 -3
  34. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/tests/test_sentence_splitting.py +21 -28
  35. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/tests/test_visualization.py +4 -4
  36. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/setup.cfg +0 -0
  37. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/__init__.py +0 -0
  38. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/code_chunker/__init__.py +0 -0
  39. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/code_chunker/patterns.py +0 -0
  40. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/code_chunker/utils.py +0 -0
  41. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/__init__.py +0 -0
  42. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/batch_runner.py +0 -0
  43. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/logging_utils.py +0 -0
  44. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/common/token_utils.py +0 -0
  45. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/__init__.py +0 -0
  46. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/__init__.py +0 -0
  47. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/html_2_md.py +0 -0
  48. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/latex_2_md.py +0 -0
  49. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/__init__.py +0 -0
  50. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/base_processor.py +0 -0
  51. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/docx_processor.py +1 -1
  52. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/document_chunker/registry.py +0 -0
  53. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/exceptions.py +0 -0
  54. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/__init__.py +0 -0
  55. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/registry.py +0 -0
  56. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/visualizer/__init__.py +0 -0
  57. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/css/style.css +0 -0
  58. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/js/app.js +0 -0
  59. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/js/utils.js +0 -0
  60. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet/visualizer/visualizer.py +0 -0
  61. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/dependency_links.txt +0 -0
  62. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/entry_points.txt +0 -0
  63. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/top_level.txt +0 -0
  64. {chunklet_py-2.3.1 → chunklet_py-2.4.0}/tests/test_code_chunking.py +0 -0
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chunklet-py
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Summary: High-fidelity context-aware chunking and interactive visualization for RAG. Advanced segmentation for code and documents, because your LLM is only as smart as the fragments you feed it.
5
5
  Author-email: speedyk_005 <speedy40115719@gmail.com>
6
6
  License-Expression: MIT
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/speedyk-005/chunklet-py
9
9
  Project-URL: Documentation, https://speedyk-005.github.io/chunklet-py/
10
10
  Project-URL: Issues, https://github.com/speedyk-005/chunklet-py/issues
11
11
  Project-URL: Changelog, https://github.com/speedyk-005/chunklet-py/blob/main/CHANGELOG.md
12
- Keywords: nlp,chunking,text-splitting,sentence-splitting,llm,rag,ai,multilingual,text processing,natural language processing,data processing,information retrieval,semantic search,document processing,document-chunker,code chunking,code-chunker,source code analysis,programming languages,code structure,chunk-visualization
12
+ Keywords: nlp,chunking,chunker,text-chunking,text-splitting,sentence-splitting,llm,rag,ai,multilingual,text processing,natural language processing,data processing,information retrieval,semantic search,document processing,document-chunker,code chunking,code-chunker,programming languages,code structure,chunk-visualization
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Intended Audience :: Science/Research
@@ -26,20 +26,17 @@ Requires-Python: >=3.11
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: loguru<1.0,>=0.7.3
29
- Requires-Dist: pysbd<1.0,>=0.3.4
30
- Requires-Dist: sentsplit<2.0,>=1.0.8
29
+ Requires-Dist: yasbd-lib<2.0,>=0.12.0
31
30
  Requires-Dist: sentencex<=0.6.1; sys_platform == "linux" and (platform_machine == "aarch64" or platform_machine == "armv7l")
32
31
  Requires-Dist: sentencex<2.0,>=1.0.0; sys_platform != "linux" or (platform_machine != "aarch64" and platform_machine != "armv7l")
33
32
  Requires-Dist: indic-nlp-library<1.0,>=0.92
34
33
  Requires-Dist: py3langid<1.0,>=0.3.0
35
34
  Requires-Dist: mpire<3.0,>=2.10.2
36
- Requires-Dist: dotdict3<2.0,>=1.1.0
37
35
  Requires-Dist: more-itertools<12.0,>=10.6.0
38
36
  Requires-Dist: regex>=2025.7.29
39
37
  Requires-Dist: pydantic<3.0,>=2.11.0
40
38
  Requires-Dist: typer<1.0,>=0.19.0
41
39
  Requires-Dist: charset-normalizer<4.0,>=3.4.2
42
- Requires-Dist: setuptools<81
43
40
  Provides-Extra: structured-document
44
41
  Requires-Dist: pdfminer.six>=20250324; extra == "structured-document"
45
42
  Requires-Dist: python-docx<2.0,>=1.2.0; extra == "structured-document"
@@ -48,10 +45,11 @@ Requires-Dist: ebooklib<1.0,>=0.19; extra == "structured-document"
48
45
  Requires-Dist: docutils<1.0,>=0.21.2; extra == "structured-document"
49
46
  Requires-Dist: markdownify<2.0,>=1.1.0; extra == "structured-document"
50
47
  Requires-Dist: striprtf<1.0,>=0.0.29; extra == "structured-document"
51
- Requires-Dist: pylatexenc==2.10; extra == "structured-document"
48
+ Requires-Dist: mailparse<2.0,>=1.0.1; extra == "structured-document"
49
+ Requires-Dist: python-pptx<2.0,>=1.0.0; extra == "structured-document"
50
+ Requires-Dist: pylatexenc==2.11; extra == "structured-document"
52
51
  Requires-Dist: odfpy<2.0,>=1.4.1; extra == "structured-document"
53
52
  Requires-Dist: openpyxl<4.0,>=3.1.5; extra == "structured-document"
54
- Requires-Dist: tabulate2<2.0,>=1.10.2; extra == "structured-document"
55
53
  Provides-Extra: document
56
54
  Requires-Dist: chunklet-py[structured-document]; extra == "document"
57
55
  Provides-Extra: code
@@ -71,7 +69,6 @@ Provides-Extra: dev
71
69
  Requires-Dist: pytest>=8.3.5; extra == "dev"
72
70
  Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
73
71
  Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
74
- Requires-Dist: pytest-timeout>=2.3.1; extra == "dev"
75
72
  Requires-Dist: ruff>=0.14.14; extra == "dev"
76
73
  Provides-Extra: docs
77
74
  Requires-Dist: mike>=2.1.2; extra == "docs"
@@ -129,7 +126,7 @@ Dumb splitting causes problems:
129
126
  Smart chunking solves this by:
130
127
 
131
128
  - **Smart limits** — Respects both natural boundaries (sentences, paragraphs, sections) AND configurable limits (tokens, lines, functions)
132
- - **Language-aware** — Detects language automatically and applies the right rules (50+ languages supported)
129
+ - **Language-aware** — Detects language automatically and applies the right rules (60+ languages supported)
133
130
  - **Context preservation** — Overlap between chunks, rich metadata (source, span, document structure)
134
131
 
135
132
  ## 🤔 So What's Chunklet-py Anyway? (And Why Should You Care?)
@@ -158,7 +155,7 @@ Perfect for prepping data for LLMs, building RAG systems, or powering AI search
158
155
  | 🪶 **Featherlight Footprint** | Designed to be lightweight and memory-efficient, ensuring optimal performance without unnecessary overhead. |
159
156
  | 🗂️ **Rich Metadata for RAG** | Enriches chunks with valuable, context-aware metadata (source, span, document properties, code AST details) crucial for advanced RAG and LLM applications. |
160
157
  | 🔧 **Infinitely Customizable** | Offers extensive customization options, from pluggable token counters to custom sentence splitters and processors. |
161
- | 🌐 **Multilingual Mastery** | Supports over 50 natural languages for text and document chunking with intelligent detection and language-specific algorithms. |
158
+ | 🌐 **Multilingual Mastery** | Supports over 60 natural languages for text and document chunking with intelligent detection and language-specific algorithms. |
162
159
  | 🧑‍💻 **Code-Aware Intelligence** | Language-agnostic code chunking that understands and preserves the structural integrity of your source code. |
163
160
  | 🎯 **Precision Chunking** | Flexible chunking with configurable limits based on sentences, tokens, sections, lines, and functions. |
164
161
  | 📄 **Document Format Mastery** | Processes a wide array of document formats including `.pdf`, `.docx`, `.epub`, `.txt`, `.tex`, `.html`, `.hml`, `.md`, `.rst`, `.rtf`, `.odt`, `.csv`, and `.xlsx`. |
@@ -185,19 +182,21 @@ The simplest way to get started is with pip:
185
182
 
186
183
  ```bash
187
184
  # Install and check it's working
188
- pip install chunklet-py
185
+ pip install chunklet-py -U
189
186
  chunklet --version
190
187
  ```
191
188
 
192
189
  > [!TIP]
193
190
  > <b>Termux (Android)</b>
194
191
  >
195
- > No rust toolchain on Termux (especially python 3.13)? Install pydantic-core pre-built wheels first then retry installing chunklet-py:
192
+ > No rust toolchain on Termux (especially python 3.13)? Pydantic-core and cryptography won't build from source.
193
+ > Install pre-built wheels and dependencies:
196
194
  >
197
195
  > ```bash
198
196
  > pip install typing-extensions
199
197
  > pip install pydantic-core --index-url https://termux-user-repository.github.io/pypi/
200
198
  > pip install "pydantic>=2.12.4,<2.13"
199
+ > pkg install python-cryptography
201
200
  > ```
202
201
 
203
202
  That's it! You're all set to start chunking.
@@ -217,6 +216,8 @@ Want to unlock more Chunklet-py superpowers? Add these optional dependencies bas
217
216
  * **Visualization:** For the interactive web-based chunk visualizer:
218
217
  ```bash
219
218
  pip install "chunklet-py[visualization]"
219
+ # Or
220
+ pip install "chunklet-py[viz]"
220
221
  ```
221
222
  * **All Extras:** To install all optional dependencies:
222
223
  ```bash
@@ -352,6 +353,13 @@ splitter = SentenceSplitter()
352
353
  sentences = splitter.split_text(text, lang="auto")
353
354
  ```
354
355
 
356
+ > [!TIP]
357
+ > **Advanced text splitting**
358
+ >
359
+ > Chunklet-py quietly delegates sentence splitting to [yasbd-lib](https://github.com/speedyk-005/yasbd-lib), our high-accuracy SBD supporting **39 languages**.
360
+ >
361
+ > Curious why we built it? Read **[yasbd-lib vs PySBD: Two Philosophies of Sentence Boundary Detection](https://dev.to/speed_k_7e1b449706e59e433/yasbd-lib-vs-pysbd-two-philosophies-of-sentence-boundary-detection-i88)**.
362
+
355
363
  **CLI (Command Line Interface)**
356
364
 
357
365
  If you prefer the terminal to an IDE, the CLI is packed with features. Just ask for help.
@@ -44,7 +44,7 @@ Dumb splitting causes problems:
44
44
  Smart chunking solves this by:
45
45
 
46
46
  - **Smart limits** — Respects both natural boundaries (sentences, paragraphs, sections) AND configurable limits (tokens, lines, functions)
47
- - **Language-aware** — Detects language automatically and applies the right rules (50+ languages supported)
47
+ - **Language-aware** — Detects language automatically and applies the right rules (60+ languages supported)
48
48
  - **Context preservation** — Overlap between chunks, rich metadata (source, span, document structure)
49
49
 
50
50
  ## 🤔 So What's Chunklet-py Anyway? (And Why Should You Care?)
@@ -73,7 +73,7 @@ Perfect for prepping data for LLMs, building RAG systems, or powering AI search
73
73
  | 🪶 **Featherlight Footprint** | Designed to be lightweight and memory-efficient, ensuring optimal performance without unnecessary overhead. |
74
74
  | 🗂️ **Rich Metadata for RAG** | Enriches chunks with valuable, context-aware metadata (source, span, document properties, code AST details) crucial for advanced RAG and LLM applications. |
75
75
  | 🔧 **Infinitely Customizable** | Offers extensive customization options, from pluggable token counters to custom sentence splitters and processors. |
76
- | 🌐 **Multilingual Mastery** | Supports over 50 natural languages for text and document chunking with intelligent detection and language-specific algorithms. |
76
+ | 🌐 **Multilingual Mastery** | Supports over 60 natural languages for text and document chunking with intelligent detection and language-specific algorithms. |
77
77
  | 🧑‍💻 **Code-Aware Intelligence** | Language-agnostic code chunking that understands and preserves the structural integrity of your source code. |
78
78
  | 🎯 **Precision Chunking** | Flexible chunking with configurable limits based on sentences, tokens, sections, lines, and functions. |
79
79
  | 📄 **Document Format Mastery** | Processes a wide array of document formats including `.pdf`, `.docx`, `.epub`, `.txt`, `.tex`, `.html`, `.hml`, `.md`, `.rst`, `.rtf`, `.odt`, `.csv`, and `.xlsx`. |
@@ -100,19 +100,21 @@ The simplest way to get started is with pip:
100
100
 
101
101
  ```bash
102
102
  # Install and check it's working
103
- pip install chunklet-py
103
+ pip install chunklet-py -U
104
104
  chunklet --version
105
105
  ```
106
106
 
107
107
  > [!TIP]
108
108
  > <b>Termux (Android)</b>
109
109
  >
110
- > No rust toolchain on Termux (especially python 3.13)? Install pydantic-core pre-built wheels first then retry installing chunklet-py:
110
+ > No rust toolchain on Termux (especially python 3.13)? Pydantic-core and cryptography won't build from source.
111
+ > Install pre-built wheels and dependencies:
111
112
  >
112
113
  > ```bash
113
114
  > pip install typing-extensions
114
115
  > pip install pydantic-core --index-url https://termux-user-repository.github.io/pypi/
115
116
  > pip install "pydantic>=2.12.4,<2.13"
117
+ > pkg install python-cryptography
116
118
  > ```
117
119
 
118
120
  That's it! You're all set to start chunking.
@@ -132,6 +134,8 @@ Want to unlock more Chunklet-py superpowers? Add these optional dependencies bas
132
134
  * **Visualization:** For the interactive web-based chunk visualizer:
133
135
  ```bash
134
136
  pip install "chunklet-py[visualization]"
137
+ # Or
138
+ pip install "chunklet-py[viz]"
135
139
  ```
136
140
  * **All Extras:** To install all optional dependencies:
137
141
  ```bash
@@ -267,6 +271,13 @@ splitter = SentenceSplitter()
267
271
  sentences = splitter.split_text(text, lang="auto")
268
272
  ```
269
273
 
274
+ > [!TIP]
275
+ > **Advanced text splitting**
276
+ >
277
+ > Chunklet-py quietly delegates sentence splitting to [yasbd-lib](https://github.com/speedyk-005/yasbd-lib), our high-accuracy SBD supporting **39 languages**.
278
+ >
279
+ > Curious why we built it? Read **[yasbd-lib vs PySBD: Two Philosophies of Sentence Boundary Detection](https://dev.to/speed_k_7e1b449706e59e433/yasbd-lib-vs-pysbd-two-philosophies-of-sentence-boundary-detection-i88)**.
280
+
270
281
  **CLI (Command Line Interface)**
271
282
 
272
283
  If you prefer the terminal to an IDE, the CLI is packed with features. Just ask for help.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chunklet-py"
3
- version = "2.3.1"
3
+ version = "2.4.0"
4
4
  description = "High-fidelity context-aware chunking and interactive visualization for RAG. Advanced segmentation for code and documents, because your LLM is only as smart as the fragments you feed it."
5
5
  authors = [
6
6
  { name = "speedyk_005", email = "speedy40115719@gmail.com" }
@@ -26,17 +26,16 @@ classifiers = [
26
26
  ]
27
27
 
28
28
  keywords = [
29
- "nlp", "chunking", "text-splitting", "sentence-splitting", "llm", "rag", "ai", "multilingual",
29
+ "nlp", "chunking", "chunker", "text-chunking", "text-splitting", "sentence-splitting", "llm", "rag", "ai", "multilingual",
30
30
  "text processing", "natural language processing", "data processing",
31
31
  "information retrieval", "semantic search", "document processing", "document-chunker",
32
- "code chunking", "code-chunker", "source code analysis", "programming languages",
32
+ "code chunking", "code-chunker", "programming languages",
33
33
  "code structure", "chunk-visualization",
34
34
  ]
35
35
 
36
36
  dependencies = [
37
37
  "loguru>=0.7.3,<1.0",
38
- "pysbd>=0.3.4,<1.0",
39
- "sentsplit>=1.0.8,<2.0",
38
+ "yasbd-lib>=0.12.0,<2.0",
40
39
 
41
40
  # Rust binding (1.0+) for faster performance on supported platforms, legacy for Android temporarily.
42
41
  "sentencex<=0.6.1; sys_platform == 'linux' and (platform_machine == 'aarch64' or platform_machine == 'armv7l')",
@@ -45,15 +44,11 @@ dependencies = [
45
44
  "indic-nlp-library>=0.92,<1.0",
46
45
  "py3langid>=0.3.0,<1.0",
47
46
  "mpire>=2.10.2,<3.0",
48
- "dotdict3>=1.1.0,<2.0",
49
47
  "more-itertools>=10.6.0,<12.0",
50
48
  "regex>=2025.7.29",
51
49
  "pydantic>=2.11.0,<3.0",
52
50
  "typer>=0.19.0,<1.0",
53
51
  "charset-normalizer>=3.4.2,<4.0",
54
-
55
- # sentsplit depends on deprecated pkg_resources removed in setuptools 81.0.0
56
- "setuptools<81",
57
52
  ]
58
53
 
59
54
  [project.optional-dependencies]
@@ -65,13 +60,14 @@ structured-document = [
65
60
  "docutils>=0.21.2,<1.0",
66
61
  "markdownify>=1.1.0,<2.0",
67
62
  "striprtf>=0.0.29,<1.0",
63
+ "mailparse>=1.0.1,<2.0",
64
+ "python-pptx>=1.0.0,<2.0",
68
65
 
69
66
  # Lastest version of 2.x.x series
70
- "pylatexenc==2.10",
67
+ "pylatexenc==2.11",
71
68
 
72
69
  "odfpy>=1.4.1,<2.0",
73
70
  "openpyxl>=3.1.5,<4.0",
74
- "tabulate2>=1.10.2,<2.0",
75
71
  ]
76
72
 
77
73
  # Kept for compatibility
@@ -100,7 +96,6 @@ dev = [
100
96
  "pytest>=8.3.5",
101
97
  "pytest-cov>=6.2.1",
102
98
  "pytest-mock>=3.14.1",
103
- "pytest-timeout>=2.3.1",
104
99
  "ruff>=0.14.14",
105
100
  ]
106
101
 
@@ -152,14 +147,13 @@ chunklet = ["visualizer/static/**/*"]
152
147
  minversion = "7.4"
153
148
  filterwarnings = [
154
149
  "ignore::DeprecationWarning:websockets.*",
155
- "ignore::DeprecationWarning:pkg_resources.*",
150
+ "ignore::DeprecationWarning:multiprocessing.*",
156
151
  ]
157
152
 
158
153
  # disable coverage report to prevent racing with multiprocessing
159
154
  # addopts = "--cov=chunklet --cov-report=term-missing:skip-covered --durations=10"
160
- addopts = "--timeout=60"
161
-
162
- testpaths = ["tests"]
155
+ addopts = "--doctest-modules"
156
+ testpaths = ["tests", "src/chunklet/common/dotdict.py"]
163
157
  python_files = "test_*.py"
164
158
 
165
159
  [tool.coverage.run]
@@ -170,7 +164,6 @@ omit = [
170
164
  "src/chunklet/**/__init__.py",
171
165
  ]
172
166
 
173
-
174
167
  [tool.ruff]
175
168
  target-version = "py310"
176
169
  line-length = 88
@@ -194,9 +187,6 @@ ignore = ["E501", "E203"]
194
187
  # Ignore warnings about File(...)
195
188
  "src/chunklet/visualizer/visualizer.py" = ["B008"]
196
189
 
197
- # Ignore E402 - warnings.filterwarnings() must be called before imports to suppress pkg_resources
198
- "src/chunklet/sentence_splitter/sentence_splitter.py" = ["E402"]
199
-
200
190
  [tool.ruff.format]
201
191
  # Replaces Black exclude logic
202
192
  exclude = [
@@ -7,8 +7,7 @@ Defines the interface for chunkers.
7
7
  from abc import ABC, abstractmethod
8
8
  from collections.abc import Generator
9
9
 
10
- from dotdict3 import DotDict
11
- from loguru import logger
10
+ from chunklet.common.dotdict import DotDict
12
11
 
13
12
 
14
13
  class BaseChunker(ABC):
@@ -328,7 +328,7 @@ def split_command(
328
328
  destination.write_text(output_str, encoding="utf-8")
329
329
  typer.echo(
330
330
  f"Successfully split and wrote {len(sentences)} sentences "
331
- f"{source_display} to {destination} (Confidence: {confidence})",
331
+ f"{source_display} to {destination} (Lang: {lang_detected}, Confidence: {confidence})",
332
332
  err=True,
333
333
  )
334
334
  except Exception as e:
@@ -339,7 +339,7 @@ def split_command(
339
339
 
340
340
  typer.echo(
341
341
  f"--- Sentences ({len(sentences)}): "
342
- f" [{source_display} | Lang: {lang.upper()} | Confidence: {confidence}] ---"
342
+ f" [{source_display} | Lang: {lang_detected} | Confidence: {confidence}] ---"
343
343
  )
344
344
 
345
345
  for sentence in sentences:
@@ -19,7 +19,6 @@ try:
19
19
  except ImportError: # pragma: no cover
20
20
  Node, ET = None, None
21
21
 
22
- from loguru import logger
23
22
 
24
23
  from chunklet.code_chunker.patterns import (
25
24
  ALL_SINGLE_LINE_COMM,
@@ -34,9 +33,8 @@ from chunklet.code_chunker.patterns import (
34
33
  NAMESPACE_DECLARATION,
35
34
  OPENER,
36
35
  )
37
- from chunklet.common.validation import validate_input
38
36
  from chunklet.common.logging_utils import log_info
39
-
37
+ from chunklet.common.validation import validate_input
40
38
 
41
39
  CodeLine = namedtuple(
42
40
  "CodeLine", ["line_number", "content", "indent_level", "func_partial_signature"]
@@ -316,7 +314,9 @@ class CodeStructureExtractor:
316
314
  if not (curr_struct or annotated_lines_buffer):
317
315
  return
318
316
 
319
- candidates = [entry for v in annotated_lines_buffer.values() for entry in v] + curr_struct
317
+ candidates = [
318
+ entry for v in annotated_lines_buffer.values() for entry in v
319
+ ] + curr_struct
320
320
  sorted_candidates = sorted(candidates, key=lambda x: x.line_number)
321
321
 
322
322
  if not sorted_candidates:
@@ -372,13 +372,18 @@ class CodeStructureExtractor:
372
372
  indent_level = len(deannotated_line) - len(deannotated_line.lstrip())
373
373
  first_metadata = tag == "META" and not annotated_lines_buffer["META"]
374
374
  consecutive_docstrings = (
375
- annotated_lines_buffer["DOC"] and annotated_lines_buffer["DOC"][-1].line_number == line_no - 1
375
+ annotated_lines_buffer["DOC"]
376
+ and annotated_lines_buffer["DOC"][-1].line_number == line_no - 1
376
377
  )
377
378
 
378
379
  if first_metadata or not consecutive_docstrings:
379
- self._flush_snippet(state["curr_struct"], state["snippet_dicts"], annotated_lines_buffer)
380
+ self._flush_snippet(
381
+ state["curr_struct"], state["snippet_dicts"], annotated_lines_buffer
382
+ )
380
383
 
381
- annotated_lines_buffer[tag].append(CodeLine(line_no, deannotated_line, indent_level, None))
384
+ annotated_lines_buffer[tag].append(
385
+ CodeLine(line_no, deannotated_line, indent_level, None)
386
+ )
382
387
 
383
388
  def _handle_block_start(
384
389
  self,
@@ -431,12 +436,16 @@ class CodeStructureExtractor:
431
436
  if state["curr_struct"]:
432
437
  if is_python_code:
433
438
  self._flush_snippet(
434
- state["curr_struct"], state["snippet_dicts"], annotated_lines_buffer
439
+ state["curr_struct"],
440
+ state["snippet_dicts"],
441
+ annotated_lines_buffer,
435
442
  )
436
443
  else:
437
444
  doc = annotated_lines_buffer.pop("DOC", [])
438
445
  self._flush_snippet(
439
- state["curr_struct"], state["snippet_dicts"], annotated_lines_buffer
446
+ state["curr_struct"],
447
+ state["snippet_dicts"],
448
+ annotated_lines_buffer,
440
449
  )
441
450
  annotated_lines_buffer.clear()
442
451
  annotated_lines_buffer["doc"] = doc
@@ -536,7 +545,9 @@ class CodeStructureExtractor:
536
545
 
537
546
  # Append last snippet
538
547
  if state["curr_struct"]:
539
- self._flush_snippet(state["curr_struct"], state["snippet_dicts"], annotated_lines_buffer)
548
+ self._flush_snippet(
549
+ state["curr_struct"], state["snippet_dicts"], annotated_lines_buffer
550
+ )
540
551
 
541
552
  snippet_dicts = self._post_processing(state["snippet_dicts"])
542
553
  log_info(
@@ -29,10 +29,11 @@ from itertools import chain
29
29
  from pathlib import Path
30
30
  from typing import Annotated, Any, Callable, Generator, Literal
31
31
 
32
- from dotdict3 import DotDict
33
32
  from more_itertools import unique_everseen
34
33
  from pydantic import Field
35
34
 
35
+ from chunklet.common.dotdict import DotDict
36
+
36
37
  try:
37
38
  import defusedxml.ElementTree as ET
38
39
  from charset_normalizer import from_path
@@ -50,7 +51,7 @@ from chunklet.common.deprecation import deprecated_callable
50
51
  from chunklet.common.logging_utils import log_info
51
52
  from chunklet.common.path_utils import is_path_like, read_text_file
52
53
  from chunklet.common.token_utils import count_tokens
53
- from chunklet.common.validation import IterableOfStr, IterableOfPath, validate_input
54
+ from chunklet.common.validation import IterableOfPath, IterableOfStr, validate_input
54
55
  from chunklet.exceptions import (
55
56
  InvalidInputError,
56
57
  MissingTokenCounterError,
@@ -125,6 +126,7 @@ class CodeChunker(BaseChunker):
125
126
  # Deduplicate relations
126
127
  def relation_key(relation: dict):
127
128
  return tuple(sorted(relation.items()))
129
+
128
130
  unique_relations = list(unique_everseen(all_relations_flat, key=relation_key))
129
131
 
130
132
  if not unique_relations:
@@ -203,7 +205,7 @@ class CodeChunker(BaseChunker):
203
205
  }
204
206
  )
205
207
  )
206
- curr_chunk = [line] # Add the overflow line!
208
+ curr_chunk = []
207
209
  token_count = 0
208
210
  line_count = 0
209
211
 
@@ -255,7 +257,6 @@ class CodeChunker(BaseChunker):
255
257
  Args:
256
258
  box_tokens: Actual token count in the block
257
259
  max_tokens: Maximum allowed tokens
258
- box_lines: Actual line count in the block
259
260
  max_lines: Maximum allowed lines
260
261
  function_count: Actual function count in the block
261
262
  max_functions: Maximum allowed functions
@@ -278,6 +279,62 @@ class CodeChunker(BaseChunker):
278
279
  f"Content starting with: \n```\n{content_preview}...\n```"
279
280
  )
280
281
 
282
+ def _handle_oversized_snippet(
283
+ self,
284
+ snippet_dict: dict,
285
+ **kwargs: Any,
286
+ ) -> None:
287
+ """
288
+ Handle a snippet that exceeds constraints and cannot be merged.
289
+
290
+ Expects ``kwargs`` to contain the same keys as the local variables of
291
+ ``_group_by_chunk`` that are relevant to oversized handling:
292
+ ``max_tokens``, ``max_lines``, ``max_functions``, ``strict``,
293
+ ``source``, ``token_counter``, ``cumulative_lengths``,
294
+ ``result_chunks``, ``box_tokens``, ``box_lines``, ``is_func``.
295
+
296
+ Either raises a ``TokenLimitError`` (strict mode) or splits the
297
+ oversized block into sub-chunks appended to ``result_chunks``.
298
+ """
299
+ limit_msg = self._format_limit_msg(
300
+ kwargs.get("box_tokens", 0),
301
+ kwargs["max_tokens"],
302
+ kwargs.get("box_lines", 0),
303
+ kwargs["max_lines"],
304
+ int(kwargs.get("is_func", False)),
305
+ kwargs["max_functions"],
306
+ snippet_dict["content"][:100],
307
+ )
308
+
309
+ strict = kwargs["strict"]
310
+ result_chunks = kwargs["result_chunks"]
311
+
312
+ if strict:
313
+ raise TokenLimitError(
314
+ f"Structural block exceeds maximum limit.\n{limit_msg}\n"
315
+ "Reason: Prevent splitting inside interest points (function, class, region, ...)\n"
316
+ "💡Hint: Consider increasing 'max_tokens', 'max_lines', or 'max_functions', "
317
+ "refactoring the oversized block, or setting 'strict=False' to allow automatic splitting of oversized blocks."
318
+ )
319
+
320
+ logger.warning(
321
+ "Splitting oversized block into sub-chunks.\n(%s)",
322
+ limit_msg,
323
+ )
324
+
325
+ sub_chunks = self._split_oversized(
326
+ snippet_dict,
327
+ kwargs["max_tokens"],
328
+ kwargs["max_lines"],
329
+ kwargs["source"],
330
+ kwargs["token_counter"],
331
+ kwargs["cumulative_lengths"],
332
+ )
333
+
334
+ for sub_chunk in sub_chunks:
335
+ sub_chunk.metadata.chunk_num = len(result_chunks) + 1
336
+ result_chunks.append(sub_chunk)
337
+
281
338
  def _group_by_chunk(
282
339
  self,
283
340
  snippet_dicts: list[dict],
@@ -359,41 +416,23 @@ class CodeChunker(BaseChunker):
359
416
 
360
417
  elif not merged_content:
361
418
  # Too big and nothing merged yet: handle oversize
362
- limit_msg = self._format_limit_msg(
363
- box_tokens,
364
- max_tokens,
365
- box_lines,
366
- max_lines,
367
- function_count,
368
- max_functions,
369
- snippet_dict["content"][:100],
419
+ self._handle_oversized_snippet(
420
+ snippet_dict,
421
+ **{
422
+ "max_tokens": max_tokens,
423
+ "max_lines": max_lines,
424
+ "max_functions": max_functions,
425
+ "strict": strict,
426
+ "source": source,
427
+ "token_counter": token_counter,
428
+ "cumulative_lengths": cumulative_lengths,
429
+ "result_chunks": result_chunks,
430
+ "box_tokens": box_tokens,
431
+ "box_lines": box_lines,
432
+ "is_func": is_function,
433
+ },
370
434
  )
371
- if strict:
372
- raise TokenLimitError(
373
- f"Structural block exceeds maximum limit.\n{limit_msg}\n"
374
- "Reason: Prevent splitting inside interest points (function, class, region, ...)\n"
375
- "💡Hint: Consider increasing 'max_tokens', 'max_lines', or 'max_functions', "
376
- "refactoring the oversized block, or setting 'strict=False' to allow automatic splitting of oversized blocks."
377
- )
378
- else: # Else split further
379
- logger.warning(
380
- "Splitting oversized block into sub-chunks.\n(%s)",
381
- limit_msg,
382
- )
383
-
384
- sub_chunks = self._split_oversized(
385
- snippet_dict,
386
- max_tokens,
387
- max_lines,
388
- source,
389
- token_counter,
390
- cumulative_lengths,
391
- )
392
-
393
- for sub_chunk in sub_chunks:
394
- sub_chunk.metadata.chunk_num = len(result_chunks) + 1
395
- result_chunks.append(sub_chunk)
396
- index += 1
435
+ index += 1
397
436
  else:
398
437
  # Flush current merged content as a chunk
399
438
  start_span = cumulative_lengths[start_line - 1]
@@ -1,9 +1,10 @@
1
1
  import functools
2
2
  import warnings
3
3
  from importlib.metadata import PackageNotFoundError, version
4
- from packaging.version import InvalidVersion, Version
5
4
  from typing import Any, Callable
6
5
 
6
+ from packaging.version import Version
7
+
7
8
  try:
8
9
  CURRENT_VERSION = version("chunklet-py")
9
10
  except PackageNotFoundError: # pragma: no cover