chunklet-py 2.3.2__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.2/src/chunklet_py.egg-info → chunklet_py-2.4.0}/PKG-INFO +21 -12
  2. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/README.md +15 -4
  3. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/pyproject.toml +10 -19
  4. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/base_chunker.py +0 -1
  5. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/cli.py +2 -2
  6. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/code_chunker/_code_structure_extractor.py +21 -10
  7. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/code_chunker/code_chunker.py +77 -38
  8. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/deprecation.py +2 -1
  9. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/dotdict.py +72 -2
  10. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/path_utils.py +1 -2
  11. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/validation.py +12 -11
  12. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/_plain_text_chunker.py +7 -5
  13. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/rst_2_md.py +1 -3
  14. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/table_2_md.py +2 -17
  15. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/document_chunker.py +20 -8
  16. chunklet_py-2.4.0/src/chunklet/document_chunker/md_table.py +36 -0
  17. chunklet_py-2.4.0/src/chunklet/document_chunker/processors/eml_processor.py +117 -0
  18. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/epub_processor.py +1 -1
  19. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/odt_processor.py +0 -1
  20. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/pdf_processor.py +1 -3
  21. chunklet_py-2.4.0/src/chunklet/document_chunker/processors/pptx_processor.py +326 -0
  22. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/span_finder.py +9 -11
  23. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/_universal_splitter.py +8 -10
  24. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/languages.py +10 -40
  25. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/sentence_splitter.py +13 -20
  26. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/terminators.py +0 -2
  27. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/index.html +2 -1
  28. {chunklet_py-2.3.2 → chunklet_py-2.4.0/src/chunklet_py.egg-info}/PKG-INFO +21 -12
  29. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/SOURCES.txt +3 -0
  30. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/requires.txt +4 -6
  31. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/tests/test_document_chunking.py +3 -0
  32. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/tests/test_plain_text_chunking.py +5 -3
  33. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/tests/test_sentence_splitting.py +21 -28
  34. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/tests/test_visualization.py +4 -4
  35. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/LICENSE +0 -0
  36. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/setup.cfg +0 -0
  37. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/__init__.py +0 -0
  38. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/code_chunker/__init__.py +0 -0
  39. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/code_chunker/patterns.py +0 -0
  40. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/code_chunker/utils.py +0 -0
  41. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/__init__.py +0 -0
  42. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/batch_runner.py +0 -0
  43. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/logging_utils.py +0 -0
  44. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/common/token_utils.py +0 -0
  45. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/__init__.py +0 -0
  46. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/__init__.py +0 -0
  47. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/html_2_md.py +0 -0
  48. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/converters/latex_2_md.py +0 -0
  49. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/__init__.py +0 -0
  50. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/base_processor.py +0 -0
  51. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/processors/docx_processor.py +1 -1
  52. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/document_chunker/registry.py +0 -0
  53. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/exceptions.py +0 -0
  54. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/__init__.py +0 -0
  55. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/sentence_splitter/registry.py +0 -0
  56. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/visualizer/__init__.py +0 -0
  57. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/css/style.css +0 -0
  58. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/js/app.js +0 -0
  59. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/visualizer/static/js/utils.js +0 -0
  60. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet/visualizer/visualizer.py +0 -0
  61. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/dependency_links.txt +0 -0
  62. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/entry_points.txt +0 -0
  63. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/src/chunklet_py.egg-info/top_level.txt +0 -0
  64. {chunklet_py-2.3.2 → chunklet_py-2.4.0}/tests/test_code_chunking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chunklet-py
3
- Version: 2.3.2
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,8 +26,7 @@ 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
@@ -38,7 +37,6 @@ Requires-Dist: regex>=2025.7.29
38
37
  Requires-Dist: pydantic<3.0,>=2.11.0
39
38
  Requires-Dist: typer<1.0,>=0.19.0
40
39
  Requires-Dist: charset-normalizer<4.0,>=3.4.2
41
- Requires-Dist: setuptools<81
42
40
  Provides-Extra: structured-document
43
41
  Requires-Dist: pdfminer.six>=20250324; extra == "structured-document"
44
42
  Requires-Dist: python-docx<2.0,>=1.2.0; extra == "structured-document"
@@ -47,10 +45,11 @@ Requires-Dist: ebooklib<1.0,>=0.19; extra == "structured-document"
47
45
  Requires-Dist: docutils<1.0,>=0.21.2; extra == "structured-document"
48
46
  Requires-Dist: markdownify<2.0,>=1.1.0; extra == "structured-document"
49
47
  Requires-Dist: striprtf<1.0,>=0.0.29; extra == "structured-document"
50
- 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"
51
51
  Requires-Dist: odfpy<2.0,>=1.4.1; extra == "structured-document"
52
52
  Requires-Dist: openpyxl<4.0,>=3.1.5; extra == "structured-document"
53
- Requires-Dist: tabulate2<2.0,>=1.10.2; extra == "structured-document"
54
53
  Provides-Extra: document
55
54
  Requires-Dist: chunklet-py[structured-document]; extra == "document"
56
55
  Provides-Extra: code
@@ -70,7 +69,6 @@ Provides-Extra: dev
70
69
  Requires-Dist: pytest>=8.3.5; extra == "dev"
71
70
  Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
72
71
  Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
73
- Requires-Dist: pytest-timeout>=2.3.1; extra == "dev"
74
72
  Requires-Dist: ruff>=0.14.14; extra == "dev"
75
73
  Provides-Extra: docs
76
74
  Requires-Dist: mike>=2.1.2; extra == "docs"
@@ -128,7 +126,7 @@ Dumb splitting causes problems:
128
126
  Smart chunking solves this by:
129
127
 
130
128
  - **Smart limits** — Respects both natural boundaries (sentences, paragraphs, sections) AND configurable limits (tokens, lines, functions)
131
- - **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)
132
130
  - **Context preservation** — Overlap between chunks, rich metadata (source, span, document structure)
133
131
 
134
132
  ## 🤔 So What's Chunklet-py Anyway? (And Why Should You Care?)
@@ -157,7 +155,7 @@ Perfect for prepping data for LLMs, building RAG systems, or powering AI search
157
155
  | 🪶 **Featherlight Footprint** | Designed to be lightweight and memory-efficient, ensuring optimal performance without unnecessary overhead. |
158
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. |
159
157
  | 🔧 **Infinitely Customizable** | Offers extensive customization options, from pluggable token counters to custom sentence splitters and processors. |
160
- | 🌐 **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. |
161
159
  | 🧑‍💻 **Code-Aware Intelligence** | Language-agnostic code chunking that understands and preserves the structural integrity of your source code. |
162
160
  | 🎯 **Precision Chunking** | Flexible chunking with configurable limits based on sentences, tokens, sections, lines, and functions. |
163
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`. |
@@ -184,19 +182,21 @@ The simplest way to get started is with pip:
184
182
 
185
183
  ```bash
186
184
  # Install and check it's working
187
- pip install chunklet-py
185
+ pip install chunklet-py -U
188
186
  chunklet --version
189
187
  ```
190
188
 
191
189
  > [!TIP]
192
190
  > <b>Termux (Android)</b>
193
191
  >
194
- > 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:
195
194
  >
196
195
  > ```bash
197
196
  > pip install typing-extensions
198
197
  > pip install pydantic-core --index-url https://termux-user-repository.github.io/pypi/
199
198
  > pip install "pydantic>=2.12.4,<2.13"
199
+ > pkg install python-cryptography
200
200
  > ```
201
201
 
202
202
  That's it! You're all set to start chunking.
@@ -216,6 +216,8 @@ Want to unlock more Chunklet-py superpowers? Add these optional dependencies bas
216
216
  * **Visualization:** For the interactive web-based chunk visualizer:
217
217
  ```bash
218
218
  pip install "chunklet-py[visualization]"
219
+ # Or
220
+ pip install "chunklet-py[viz]"
219
221
  ```
220
222
  * **All Extras:** To install all optional dependencies:
221
223
  ```bash
@@ -351,6 +353,13 @@ splitter = SentenceSplitter()
351
353
  sentences = splitter.split_text(text, lang="auto")
352
354
  ```
353
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
+
354
363
  **CLI (Command Line Interface)**
355
364
 
356
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.2"
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')",
@@ -50,9 +49,6 @@ dependencies = [
50
49
  "pydantic>=2.11.0,<3.0",
51
50
  "typer>=0.19.0,<1.0",
52
51
  "charset-normalizer>=3.4.2,<4.0",
53
-
54
- # sentsplit depends on deprecated pkg_resources removed in setuptools 81.0.0
55
- "setuptools<81",
56
52
  ]
57
53
 
58
54
  [project.optional-dependencies]
@@ -64,13 +60,14 @@ structured-document = [
64
60
  "docutils>=0.21.2,<1.0",
65
61
  "markdownify>=1.1.0,<2.0",
66
62
  "striprtf>=0.0.29,<1.0",
63
+ "mailparse>=1.0.1,<2.0",
64
+ "python-pptx>=1.0.0,<2.0",
67
65
 
68
66
  # Lastest version of 2.x.x series
69
- "pylatexenc==2.10",
67
+ "pylatexenc==2.11",
70
68
 
71
69
  "odfpy>=1.4.1,<2.0",
72
70
  "openpyxl>=3.1.5,<4.0",
73
- "tabulate2>=1.10.2,<2.0",
74
71
  ]
75
72
 
76
73
  # Kept for compatibility
@@ -99,7 +96,6 @@ dev = [
99
96
  "pytest>=8.3.5",
100
97
  "pytest-cov>=6.2.1",
101
98
  "pytest-mock>=3.14.1",
102
- "pytest-timeout>=2.3.1",
103
99
  "ruff>=0.14.14",
104
100
  ]
105
101
 
@@ -151,14 +147,13 @@ chunklet = ["visualizer/static/**/*"]
151
147
  minversion = "7.4"
152
148
  filterwarnings = [
153
149
  "ignore::DeprecationWarning:websockets.*",
154
- "ignore::DeprecationWarning:pkg_resources.*",
150
+ "ignore::DeprecationWarning:multiprocessing.*",
155
151
  ]
156
152
 
157
153
  # disable coverage report to prevent racing with multiprocessing
158
154
  # addopts = "--cov=chunklet --cov-report=term-missing:skip-covered --durations=10"
159
- addopts = "--timeout=60"
160
-
161
- testpaths = ["tests"]
155
+ addopts = "--doctest-modules"
156
+ testpaths = ["tests", "src/chunklet/common/dotdict.py"]
162
157
  python_files = "test_*.py"
163
158
 
164
159
  [tool.coverage.run]
@@ -169,7 +164,6 @@ omit = [
169
164
  "src/chunklet/**/__init__.py",
170
165
  ]
171
166
 
172
-
173
167
  [tool.ruff]
174
168
  target-version = "py310"
175
169
  line-length = 88
@@ -193,9 +187,6 @@ ignore = ["E501", "E203"]
193
187
  # Ignore warnings about File(...)
194
188
  "src/chunklet/visualizer/visualizer.py" = ["B008"]
195
189
 
196
- # Ignore E402 - warnings.filterwarnings() must be called before imports to suppress pkg_resources
197
- "src/chunklet/sentence_splitter/sentence_splitter.py" = ["E402"]
198
-
199
190
  [tool.ruff.format]
200
191
  # Replaces Black exclude logic
201
192
  exclude = [
@@ -8,7 +8,6 @@ from abc import ABC, abstractmethod
8
8
  from collections.abc import Generator
9
9
 
10
10
  from chunklet.common.dotdict import DotDict
11
- from loguru import logger
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 chunklet.common.dotdict 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