SqueakyCleanText 0.7.1__tar.gz → 0.8.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 (154) hide show
  1. squeakycleantext-0.8.0/PKG-INFO +366 -0
  2. squeakycleantext-0.8.0/README.md +278 -0
  3. squeakycleantext-0.8.0/SqueakyCleanText.egg-info/PKG-INFO +366 -0
  4. squeakycleantext-0.8.0/SqueakyCleanText.egg-info/SOURCES.txt +138 -0
  5. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/SqueakyCleanText.egg-info/entry_points.txt +1 -0
  6. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/SqueakyCleanText.egg-info/requires.txt +16 -1
  7. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/pyproject.toml +18 -3
  8. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/__init__.py +3 -2
  9. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/adapters.py +64 -70
  10. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/browser.py +9 -6
  11. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/cli.py +19 -3
  12. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/config.py +197 -38
  13. squeakycleantext-0.8.0/sct/integrations/__init__.py +1 -0
  14. squeakycleantext-0.8.0/sct/integrations/_runtime.py +169 -0
  15. squeakycleantext-0.8.0/sct/integrations/datatrove.py +53 -0
  16. squeakycleantext-0.8.0/sct/integrations/haystack.py +72 -0
  17. squeakycleantext-0.8.0/sct/integrations/hf.py +87 -0
  18. squeakycleantext-0.8.0/sct/integrations/langchain.py +32 -0
  19. squeakycleantext-0.8.0/sct/mcp_server.py +297 -0
  20. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/ocr.py +1 -1
  21. squeakycleantext-0.8.0/sct/presets.py +54 -0
  22. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/privacy.py +2 -2
  23. squeakycleantext-0.8.0/sct/report_cli.py +138 -0
  24. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/risk.py +10 -1
  25. squeakycleantext-0.8.0/sct/score.py +304 -0
  26. squeakycleantext-0.8.0/sct/sct.py +1030 -0
  27. squeakycleantext-0.8.0/sct/signals.py +105 -0
  28. squeakycleantext-0.8.0/sct/streaming.py +151 -0
  29. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/anonymization_map.py +9 -1
  30. squeakycleantext-0.8.0/sct/utils/constants.py +597 -0
  31. squeakycleantext-0.8.0/sct/utils/contact.py +140 -0
  32. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/datetime.py +6 -3
  33. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/gliclass_adapter.py +3 -1
  34. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/gliner_adapter.py +38 -14
  35. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/ner.py +563 -156
  36. squeakycleantext-0.8.0/sct/utils/normtext.py +363 -0
  37. squeakycleantext-0.8.0/sct/utils/onnx_pipeline.py +569 -0
  38. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/process_result.py +17 -4
  39. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/resources.py +17 -0
  40. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/special.py +17 -6
  41. squeakycleantext-0.8.0/sct/utils/synthetic.py +347 -0
  42. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/torch_pipeline.py +3 -3
  43. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_adapters.py +51 -48
  44. squeakycleantext-0.8.0/tests/test_address_leftovers.py +93 -0
  45. squeakycleantext-0.8.0/tests/test_batching.py +185 -0
  46. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_benchmark_release.py +23 -3
  47. squeakycleantext-0.8.0/tests/test_chunking.py +365 -0
  48. squeakycleantext-0.8.0/tests/test_cjk_width.py +50 -0
  49. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_cli.py +12 -0
  50. squeakycleantext-0.8.0/tests/test_codex_full_round1.py +166 -0
  51. squeakycleantext-0.8.0/tests/test_codex_full_round13.py +74 -0
  52. squeakycleantext-0.8.0/tests/test_codex_full_round14.py +73 -0
  53. squeakycleantext-0.8.0/tests/test_codex_full_round15.py +154 -0
  54. squeakycleantext-0.8.0/tests/test_codex_full_round16.py +90 -0
  55. squeakycleantext-0.8.0/tests/test_codex_full_round17.py +78 -0
  56. squeakycleantext-0.8.0/tests/test_codex_full_round18.py +79 -0
  57. squeakycleantext-0.8.0/tests/test_codex_full_round19.py +32 -0
  58. squeakycleantext-0.8.0/tests/test_codex_full_round2.py +81 -0
  59. squeakycleantext-0.8.0/tests/test_codex_full_round20.py +87 -0
  60. squeakycleantext-0.8.0/tests/test_codex_full_round21.py +168 -0
  61. squeakycleantext-0.8.0/tests/test_codex_full_round22.py +52 -0
  62. squeakycleantext-0.8.0/tests/test_codex_full_round23.py +54 -0
  63. squeakycleantext-0.8.0/tests/test_codex_full_round24.py +82 -0
  64. squeakycleantext-0.8.0/tests/test_codex_full_round25.py +72 -0
  65. squeakycleantext-0.8.0/tests/test_codex_full_round27.py +85 -0
  66. squeakycleantext-0.8.0/tests/test_codex_full_round28.py +28 -0
  67. squeakycleantext-0.8.0/tests/test_codex_full_round29.py +44 -0
  68. squeakycleantext-0.8.0/tests/test_codex_full_round3.py +104 -0
  69. squeakycleantext-0.8.0/tests/test_codex_full_round30.py +53 -0
  70. squeakycleantext-0.8.0/tests/test_codex_full_round31.py +82 -0
  71. squeakycleantext-0.8.0/tests/test_codex_full_round32.py +52 -0
  72. squeakycleantext-0.8.0/tests/test_codex_full_round35.py +59 -0
  73. squeakycleantext-0.8.0/tests/test_codex_full_round37.py +41 -0
  74. squeakycleantext-0.8.0/tests/test_codex_full_round38.py +57 -0
  75. squeakycleantext-0.8.0/tests/test_codex_full_round4.py +44 -0
  76. squeakycleantext-0.8.0/tests/test_codex_full_round5.py +106 -0
  77. squeakycleantext-0.8.0/tests/test_codex_full_round6.py +80 -0
  78. squeakycleantext-0.8.0/tests/test_codex_full_round7.py +66 -0
  79. squeakycleantext-0.8.0/tests/test_codex_full_round8.py +35 -0
  80. squeakycleantext-0.8.0/tests/test_codex_full_round9.py +58 -0
  81. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_contracts.py +8 -1
  82. squeakycleantext-0.8.0/tests/test_dates_cjk_styled.py +83 -0
  83. squeakycleantext-0.8.0/tests/test_dates_default.py +79 -0
  84. squeakycleantext-0.8.0/tests/test_device_provenance.py +146 -0
  85. squeakycleantext-0.8.0/tests/test_escaped_markup.py +72 -0
  86. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_evaluation.py +1 -1
  87. squeakycleantext-0.8.0/tests/test_final_check_fixes.py +119 -0
  88. squeakycleantext-0.8.0/tests/test_full_test_round2.py +145 -0
  89. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_functional.py +21 -8
  90. squeakycleantext-0.8.0/tests/test_fuzz_round1.py +72 -0
  91. squeakycleantext-0.8.0/tests/test_fuzz_round2.py +31 -0
  92. squeakycleantext-0.8.0/tests/test_fuzz_round3.py +52 -0
  93. squeakycleantext-0.8.0/tests/test_fuzz_round5.py +51 -0
  94. squeakycleantext-0.8.0/tests/test_fuzz_round6.py +51 -0
  95. squeakycleantext-0.8.0/tests/test_fuzz_styled_pii.py +230 -0
  96. squeakycleantext-0.8.0/tests/test_integrations_frameworks.py +54 -0
  97. squeakycleantext-0.8.0/tests/test_integrations_hf.py +70 -0
  98. squeakycleantext-0.8.0/tests/test_integrations_runtime.py +121 -0
  99. squeakycleantext-0.8.0/tests/test_keep_scripts.py +67 -0
  100. squeakycleantext-0.8.0/tests/test_language_all.py +253 -0
  101. squeakycleantext-0.8.0/tests/test_linux_workers.py +146 -0
  102. squeakycleantext-0.8.0/tests/test_mcp_path.py +128 -0
  103. squeakycleantext-0.8.0/tests/test_mcp_server.py +237 -0
  104. squeakycleantext-0.8.0/tests/test_multilingual_eval_set.py +47 -0
  105. squeakycleantext-0.8.0/tests/test_ner_sees_tags.py +104 -0
  106. squeakycleantext-0.8.0/tests/test_onnx_concurrency.py +151 -0
  107. squeakycleantext-0.8.0/tests/test_onnx_external_data.py +276 -0
  108. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_privacy.py +38 -14
  109. squeakycleantext-0.8.0/tests/test_public_view.py +74 -0
  110. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_regex_boundaries.py +14 -4
  111. squeakycleantext-0.8.0/tests/test_regex_counts.py +106 -0
  112. squeakycleantext-0.8.0/tests/test_report_cli.py +109 -0
  113. squeakycleantext-0.8.0/tests/test_review_round1.py +159 -0
  114. squeakycleantext-0.8.0/tests/test_review_round2.py +69 -0
  115. squeakycleantext-0.8.0/tests/test_review_round3.py +101 -0
  116. squeakycleantext-0.8.0/tests/test_review_round4.py +68 -0
  117. squeakycleantext-0.8.0/tests/test_score.py +179 -0
  118. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_sct.py +38 -12
  119. squeakycleantext-0.8.0/tests/test_security_leftovers.py +113 -0
  120. squeakycleantext-0.8.0/tests/test_stream_guard_shared_view.py +92 -0
  121. squeakycleantext-0.8.0/tests/test_styled_pii.py +57 -0
  122. squeakycleantext-0.8.0/tests/test_synthetic_per_document.py +194 -0
  123. squeakycleantext-0.8.0/tests/test_training_preset.py +94 -0
  124. squeakycleantext-0.8.0/tests/test_type_names.py +77 -0
  125. squeakycleantext-0.7.1/PKG-INFO +0 -879
  126. squeakycleantext-0.7.1/README.md +0 -800
  127. squeakycleantext-0.7.1/SqueakyCleanText.egg-info/PKG-INFO +0 -879
  128. squeakycleantext-0.7.1/SqueakyCleanText.egg-info/SOURCES.txt +0 -57
  129. squeakycleantext-0.7.1/sct/mcp_server.py +0 -104
  130. squeakycleantext-0.7.1/sct/sct.py +0 -686
  131. squeakycleantext-0.7.1/sct/streaming.py +0 -87
  132. squeakycleantext-0.7.1/sct/utils/constants.py +0 -413
  133. squeakycleantext-0.7.1/sct/utils/contact.py +0 -75
  134. squeakycleantext-0.7.1/sct/utils/normtext.py +0 -102
  135. squeakycleantext-0.7.1/sct/utils/onnx_pipeline.py +0 -357
  136. squeakycleantext-0.7.1/sct/utils/synthetic.py +0 -125
  137. squeakycleantext-0.7.1/tests/test_chunking.py +0 -170
  138. squeakycleantext-0.7.1/tests/test_mcp_server.py +0 -145
  139. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/LICENSE +0 -0
  140. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/SqueakyCleanText.egg-info/dependency_links.txt +0 -0
  141. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/SqueakyCleanText.egg-info/top_level.txt +0 -0
  142. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/evaluation.py +0 -0
  143. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/model_benchmark.py +0 -0
  144. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/model_candidates.py +0 -0
  145. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/__init__.py +0 -0
  146. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/sct/utils/stopwords.py +0 -0
  147. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/setup.cfg +0 -0
  148. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/setup.py +0 -0
  149. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_browser.py +0 -0
  150. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_config_state.py +0 -0
  151. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_model_benchmarks.py +0 -0
  152. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_ocr.py +0 -0
  153. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_risk.py +0 -0
  154. {squeakycleantext-0.7.1 → squeakycleantext-0.8.0}/tests/test_streaming.py +0 -0
@@ -0,0 +1,366 @@
1
+ Metadata-Version: 2.4
2
+ Name: SqueakyCleanText
3
+ Version: 0.8.0
4
+ Summary: Text preprocessing & PII anonymization pipeline for NLP/ML: ONNX NER ensemble, language detection, stopword removal, and configurable token replacement.
5
+ Author: Rehan Fazal
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rhnfzl/SqueakyCleanText
8
+ Project-URL: Repository, https://github.com/rhnfzl/SqueakyCleanText
9
+ Project-URL: Issues, https://github.com/rhnfzl/SqueakyCleanText/issues
10
+ Project-URL: Changelog, https://github.com/rhnfzl/SqueakyCleanText/releases
11
+ Project-URL: llms.txt, https://github.com/rhnfzl/SqueakyCleanText/blob/main/llms.txt
12
+ Keywords: text cleaning,text preprocessing,NLP,natural language processing,named entity recognition,NER,anonymization,PII removal,data cleaning,machine learning,ONNX,language detection
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
18
+ Classifier: Topic :: Text Processing :: Linguistic
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Natural Language :: English
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Operating System :: OS Independent
26
+ Requires-Python: >=3.12
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: lingua-language-detector>=2.2
30
+ Requires-Dist: stop-words>=2025.11.4
31
+ Requires-Dist: emoji>=2.14
32
+ Requires-Dist: ftfy>=6.3
33
+ Requires-Dist: Unidecode>=1.4
34
+ Requires-Dist: onnxruntime>=1.24.2
35
+ Requires-Dist: tokenizers>=0.22.2
36
+ Requires-Dist: huggingface-hub>=1.5.0
37
+ Requires-Dist: numpy>=2.2.0
38
+ Requires-Dist: presidio_anonymizer>=2.2.360
39
+ Requires-Dist: regex>=2024.11.6
40
+ Provides-Extra: gpu
41
+ Requires-Dist: onnxruntime-gpu>=1.24.2; extra == "gpu"
42
+ Provides-Extra: fuzzy
43
+ Requires-Dist: rapidfuzz>=3.12; extra == "fuzzy"
44
+ Provides-Extra: torch
45
+ Requires-Dist: torch>=2.6.0; extra == "torch"
46
+ Requires-Dist: transformers>=4.48; extra == "torch"
47
+ Provides-Extra: gliner
48
+ Requires-Dist: gliner<1.0,>=0.2.25; extra == "gliner"
49
+ Provides-Extra: gliner2
50
+ Requires-Dist: gliner2<2.0,>=1.0; extra == "gliner2"
51
+ Provides-Extra: synthetic
52
+ Requires-Dist: faker>=33.0; extra == "synthetic"
53
+ Provides-Extra: presidio
54
+ Requires-Dist: presidio-analyzer>=2.2.360; extra == "presidio"
55
+ Provides-Extra: classify
56
+ Requires-Dist: gliclass>=0.1.16; extra == "classify"
57
+ Provides-Extra: all-ner
58
+ Requires-Dist: torch>=2.6.0; extra == "all-ner"
59
+ Requires-Dist: transformers>=4.48; extra == "all-ner"
60
+ Requires-Dist: gliner<1.0,>=0.2.25; extra == "all-ner"
61
+ Requires-Dist: gliner2<2.0,>=1.0; extra == "all-ner"
62
+ Provides-Extra: dev
63
+ Requires-Dist: hypothesis>=6.130; extra == "dev"
64
+ Requires-Dist: faker>=33.0; extra == "dev"
65
+ Requires-Dist: ruff>=0.11; extra == "dev"
66
+ Requires-Dist: pytest>=8.3; extra == "dev"
67
+ Requires-Dist: pytest-timeout>=2.3; extra == "dev"
68
+ Requires-Dist: rapidfuzz>=3.12; extra == "dev"
69
+ Provides-Extra: test
70
+ Requires-Dist: coverage>=7.8; extra == "test"
71
+ Requires-Dist: pytest-cov>=6.0; extra == "test"
72
+ Provides-Extra: mcp
73
+ Requires-Dist: fastmcp>=4.0.5; extra == "mcp"
74
+ Provides-Extra: score
75
+ Requires-Dist: datasketch>=1.6; extra == "score"
76
+ Provides-Extra: datasets
77
+ Requires-Dist: datasets>=3.0; extra == "datasets"
78
+ Provides-Extra: datatrove
79
+ Requires-Dist: datatrove>=0.4; extra == "datatrove"
80
+ Provides-Extra: langchain
81
+ Requires-Dist: langchain-core>=0.3; extra == "langchain"
82
+ Provides-Extra: haystack
83
+ Requires-Dist: haystack-ai>=2.0; extra == "haystack"
84
+ Provides-Extra: ocr
85
+ Requires-Dist: pillow>=12.3.0; extra == "ocr"
86
+ Requires-Dist: pytesseract>=0.3.13; extra == "ocr"
87
+ Dynamic: license-file
88
+
89
+ # SqueakyCleanText
90
+
91
+ [![PyPI](https://img.shields.io/pypi/v/squeakycleantext.svg)](https://pypi.org/project/squeakycleantext/)
92
+ [![Python package](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml/badge.svg)](https://github.com/rhnfzl/SqueakyCleanText/actions/workflows/python-package.yml)
93
+ [![Python Versions](https://img.shields.io/badge/Python-3.12%20|%203.13%20|%203.14-blue)](https://pypi.org/project/squeakycleantext/)
94
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rhnfzl/SqueakyCleanText/blob/main/LICENSE)
95
+
96
+ Real-world text is messy: broken encoding, HTML, URLs, and personal data such as names, emails and phone numbers. SqueakyCleanText cleans that text and replaces the personal data with tags, fake values or reversible placeholders, in one call and in 75 languages. It is for teams that feed text into LLMs, training sets, search indexes or classic ML models and need it clean and safe to share first.
97
+
98
+ It finds most personal data, not all of it. Check its output before you publish sensitive data.
99
+
100
+ ## Before and after
101
+
102
+ ```python
103
+ from sct import TextCleaner
104
+
105
+ text = "<p>Hi, I’m John Smith. Mail me at john.smith@example.com or call +31 6 1234 5678.</p>"
106
+ print(TextCleaner().process(text).lm_text)
107
+ # -> <HTML>Hi, I'm <PERSON>. Mail me at <EMAIL> or call <PHONE>.<HTML>
108
+ ```
109
+
110
+ The broken `’` is repaired to an apostrophe, the HTML tags become `<HTML>` (set `replace_with_html=""` to drop them), and the name, email and phone become tags.
111
+
112
+ ## Use SCT when
113
+
114
+ - You send user text (tickets, chats, emails, scraped pages) to an LLM, a RAG index or a training set and must take the personal data out first.
115
+ - Your text comes in many languages and scripts, and you want one tool for all of them.
116
+ - You need the original values back later: reversible placeholders keep a map you store apart.
117
+ - You want to log or report what was found without logging the values (value-free `signals`).
118
+ - Do not use it alone for tables with known PII columns: drop or hash those columns, or use a tool built for structured data such as Presidio structured.
119
+
120
+ ## Install
121
+
122
+ ```sh
123
+ pip install squeakycleantext
124
+ ```
125
+
126
+ Python 3.12 or newer. The base install runs NER with ONNX Runtime; it needs no PyTorch. The extras you are most likely to need:
127
+
128
+ | Extra | Why you need it |
129
+ |---|---|
130
+ | `gpu` | Run NER on an NVIDIA GPU (CUDA ONNX Runtime) |
131
+ | `synthetic` | Replace PII with fake values (Faker) |
132
+ | `gliner` | Zero-shot NER with your own labels, and `ner_mode="pii"` (58 PII labels) |
133
+ | `datasets`, `datatrove` | Clean Hugging Face datasets or DataTrove pipelines |
134
+ | `mcp` | Run SCT as an MCP server for AI assistants |
135
+
136
+ Install with `pip install "squeakycleantext[gpu,synthetic]"`. All extras: [docs/INSTALL.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INSTALL.md).
137
+
138
+ ## Quickstart
139
+
140
+ ```python
141
+ from sct import TextCleaner
142
+
143
+ cleaner = TextCleaner()
144
+ result = cleaner.process("Contact John Doe at john.doe@company.com. Meeting on 2023-10-01.")
145
+ print(result.lm_text) # -> Contact <PERSON> at <EMAIL>. Meeting on <DATE>.
146
+ print(result.stat_text) # -> contact person email meeting date
147
+ print(result.language) # -> ENGLISH
148
+ ```
149
+
150
+ `lm_text` keeps the sentence for LLMs. `stat_text` is lower-cased, without stopwords and punctuation, for classic ML models. The result also unpacks as `lm_text, stat_text, language`.
151
+
152
+ Clean many texts at once with `process_batch`. It runs documents in threads and batches the NER calls across them:
153
+
154
+ ```python
155
+ from sct import TextCleaner
156
+
157
+ cleaner = TextCleaner()
158
+ for r in cleaner.process_batch(["Mail jan@example.nl", "Bel +31 20 123 4567 voor Jan de Vries."]):
159
+ print(r.language, r.lm_text)
160
+ # -> ENGLISH Mail <EMAIL>
161
+ # -> DUTCH Bel <PHONE> voor <PERSON>.
162
+ ```
163
+
164
+ For more PII types (IDs, bank numbers, health data and more), use the GLiNER PII model with `TextCleaner(cfg=TextCleanerConfig(ner_mode="pii"))`. It needs the `gliner` extra.
165
+
166
+ ## How it works
167
+
168
+ 1. **Detect language.** Lingua reads the raw input and picks one of 75 languages. Pin it with `language="de"` or limit it with `language=("en", "nl")`.
169
+ 2. **Fix the text.** ftfy repairs broken unicode and HTML entities, styled characters such as `𝐣𝐨𝐡𝐧` are folded, and HTML tags are replaced. Emoji removal and ASCII folding are off by default.
170
+ 3. **Regex PII.** URLs, emails, IPs, dates, phones, years and numbers become tags. Currency symbols become ISO codes by default (`€100` becomes `EUR<NUMBER>`); set `replace_with_currency_symbols="<CURRENCY>"` to get a tag. Then isolated letters and symbols, bracket content and extra whitespace are cleaned up. Each type is counted.
171
+ 4. **NER.** By default an ensemble of ONNX models per language finds persons, locations, organisations and misc entities. Long texts are cut into chunk windows (no overlap unless you set `ner_chunk_overlap`). You can switch to PyTorch, GLiNER or the GLiNER PII mode, and apply a privacy policy.
172
+ 5. **Replace.** Found values become `<TAG>`s (default), fake values (`synthetic`) or indexed placeholders such as `<PERSON_0>` with a map to restore them (`reversible`).
173
+ 6. **Output.** `lm_text`, `stat_text` and `language`, plus `metadata` and `signals`. `signals` holds counts, warnings and model ids, never raw values. `metadata` holds the same counts, and it can hold raw values: the reversible map (`anon_map`, unless you pass a `TokenStore`) and `findings` with entity text (with `include_entities=True` or a privacy policy).
174
+
175
+ <picture>
176
+ <img src="https://raw.githubusercontent.com/rhnfzl/SqueakyCleanText/main/resources/sct_flow.png" alt="SqueakyCleanText pipeline: entry points, language detection, fixes, regex PII, NER, replacement, outputs and metadata" width="900">
177
+ </picture>
178
+
179
+ <details>
180
+ <summary>The diagram as text</summary>
181
+
182
+ - Entry points: `process()`, `process_batch()`, the `sct-clean` CLI, the `sct-mcp` server, the Hugging Face `datasets`, DataTrove, LangChain and Haystack adapters, the JSON, DataFrame and RAG adapters, and the streaming redactors. All of them pass text to the same pipeline.
183
+ - 1 Detect language (Lingua, 75 languages, on the raw input) -> 2 Fix the text -> 3 Regex PII (counted in `regex_counts`) -> 4 NER (chunk windows for long text, overlap only with `ner_chunk_overlap`) -> 5 Replace (`<TAG>`, synthetic fakes, or reversible `<PERSON_0>` with a map in `metadata` or a `TokenStore`) -> 6 Output (`lm_text`, `stat_text`, `language`) and `metadata` / `signals` (`signals` has no raw values; `metadata` can hold the reversible map and `findings`).
184
+ - Across all stages: `process_batch` runs documents in threads and batches NER across them (`ner_batch_size`); NER runs on CPU or CUDA (`ner_device`); the `training` preset turns stages on and off.
185
+
186
+ </details>
187
+
188
+ Every step is a `TextCleanerConfig` field with a safe default. The source of the diagram is [resources/sct_flow.excalidraw](https://github.com/rhnfzl/SqueakyCleanText/blob/main/resources/sct_flow.excalidraw).
189
+
190
+ ## What you get back
191
+
192
+ `process` returns a `ProcessResult` with `lm_text`, `stat_text`, `language` and `metadata`:
193
+
194
+ ```python
195
+ from sct import TextCleaner
196
+
197
+ result = TextCleaner().process("Mail jan@example.nl or call +31 20 123 4567.")
198
+ print(result.metadata["regex_counts"])
199
+ # -> {'EMAIL': {'count': 1, 'chars': 14}, 'PHONE': {'count': 1, 'chars': 15}}
200
+ print(result.signals["pii_count"], result.signals["pii_types"], result.signals["sensitive"])
201
+ # -> 2 {'EMAIL': 1, 'PHONE': 1} True
202
+ ```
203
+
204
+ - `metadata` always holds `regex_counts`, `entity_counts`, `language_confidence`, `language_source`, `warnings` (such as `language_low_confidence` or `ner_fallback_models`) and `provenance` (the NER models and revisions that ran).
205
+ - `signals` is the value-free view of the same data, with the same keys for every input. It is safe to log or store next to the cleaned text.
206
+ - Entity text appears only when you ask for it (`include_entities=True` or a privacy policy adds `metadata["findings"]`) and in the reversible map. Keep those out of logs. Full field list: [docs/API.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/API.md).
207
+
208
+ ## Common recipes
209
+
210
+ **Training or RAG data.** The `training` preset only takes out PII and repairs unicode. Numbers, dates, HTML, case, line breaks and code stay as written:
211
+
212
+ ```python
213
+ from sct import TextCleaner, preset_config
214
+
215
+ cleaner = TextCleaner(cfg=preset_config("training"))
216
+ print(cleaner.process("Order 4711 from <b>Anna Berg</b> (anna@example.se) on 2024-05-01.").lm_text)
217
+ # -> Order 4711 from <b><PERSON></b> (<EMAIL>) on 2024-05-01.
218
+ ```
219
+
220
+ **Reversible placeholders.** Keep the map in a `TokenStore` and restore the values later ([docs/REPLACEMENT_MODES.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/REPLACEMENT_MODES.md), [docs/PRIVACY.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/PRIVACY.md)). `replacement_mode="synthetic"` (`synthetic` extra) puts fake values in place instead. The same document always gets the same fakes.
221
+
222
+ ```python
223
+ from sct import InMemoryTokenStore, TextCleaner, TextCleanerConfig
224
+
225
+ store = InMemoryTokenStore()
226
+ cleaner = TextCleaner(cfg=TextCleanerConfig(replacement_mode="reversible"), token_store=store)
227
+ result = cleaner.process("Maria lives in Utrecht.")
228
+ print(result.lm_text) # -> <PERSON_0> lives in <LOCATION_0>.
229
+ print(store.load(result.metadata["anon_map_reference"]).deanonymize(result.lm_text)) # -> Maria lives in Utrecht.
230
+ ```
231
+
232
+ **Privacy policy.** Decide per entity type what to keep or redact, and get a versioned audit trail ([docs/PRIVACY.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/PRIVACY.md)):
233
+
234
+ ```python
235
+ from sct import EntityRule, PrivacyPolicy, TextCleaner
236
+
237
+ policy = PrivacyPolicy(name="support-export", version="1", rules={
238
+ "PERSON": EntityRule(action="redact", min_score=0.8),
239
+ "ORGANISATION": EntityRule(action="keep"),
240
+ })
241
+ print(TextCleaner(privacy_policy=policy).process("Maria Chen from Acme Corp wrote to us.").lm_text)
242
+ # -> <REDACTED> from Acme Corp wrote to us.
243
+ ```
244
+
245
+ **GPU.** Install the `gpu` extra and set `TextCleanerConfig(ner_device="cuda", cuda_device_id=0)`. If CUDA cannot load, NER runs on CPU and each result gets the `cuda_fallback` warning ([docs/PERFORMANCE.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/PERFORMANCE.md)).
246
+
247
+ **Your own entity labels.** With the `gliner` extra, set `ner_backend="gliner"`, `gliner_model="urchade/gliner_multi-v2.1"` and `gliner_labels=("person", "product", "event")`; unmapped labels become tags such as `<PRODUCT>` ([docs/NER_BACKENDS.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/NER_BACKENDS.md)).
248
+
249
+ ## Use it in your stack
250
+
251
+ | Integration | Usage | Extra | Docs |
252
+ |---|---|---|---|
253
+ | Hugging Face `datasets` | `clean_dataset(ds, columns=["text"], num_proc=4)` | `datasets` | [INTEGRATIONS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INTEGRATIONS.md) |
254
+ | DataTrove | `SCTCleaner()` as a pipeline step | `datatrove` | [INTEGRATIONS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INTEGRATIONS.md) |
255
+ | LangChain | `SCTDocumentTransformer().transform_documents(docs)` | `langchain` | [INTEGRATIONS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INTEGRATIONS.md) |
256
+ | Haystack | `pipe.add_component("sct", SCTDocumentCleaner())` | `haystack` | [INTEGRATIONS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INTEGRATIONS.md) |
257
+ | JSON, DataFrame, RAG chunks | `process_json(cleaner, value)` | none | [ADAPTERS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/ADAPTERS.md) |
258
+ | Streams | `WindowedStreamingRedactor(cleaner)` | none | [ADAPTERS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/ADAPTERS.md) |
259
+ | Ray, Spark, Polars, DuckDB, dlt, Daft | `clean_columns(handle, batch)` | none | [RECIPES](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/RECIPES.md) |
260
+ | Command line | `printf 'Email maria@example.com' \| sct-clean` | none | [ADAPTERS](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/ADAPTERS.md) |
261
+ | MCP server | `sct-mcp` | `mcp` | [MCP](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/MCP.md) |
262
+
263
+ The framework adapters use the `training` preset unless you pass a config, build one cleaner per worker process, and reject reversible mode.
264
+
265
+ ## Performance
266
+
267
+ Default config (full multilingual NER ensemble), release benchmark documents, SCT 0.8.0, AMD Ryzen Threadripper PRO 7985WX and RTX 4090, median of 3 runs:
268
+
269
+ | Workload | Hardware | Docs/s |
270
+ |---|---|---|
271
+ | `process`, one document at a time | 16 CPU cores | 5.0 |
272
+ | `process_batch`, short documents, 16 workers | 16 CPU cores | 15.5 (0.7.1: 3.7) |
273
+ | `process_batch`, short documents, 16 workers | 64 CPU cores | 29.2 |
274
+ | `process_batch`, short documents, English only (2 models) | RTX 4090 | 646 |
275
+
276
+ Source and method: [docs/PERFORMANCE.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/PERFORMANCE.md) and [evaluation/results/comparison-0.7.1-0.8.0.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/evaluation/results/comparison-0.7.1-0.8.0.md). Measure your own machine with `python -m scripts.benchmark_release --batch 256 --batch-size 8 --ner-batch-size 8`.
277
+
278
+ ## Quality and evaluation
279
+
280
+ - Release benchmark (30 documents, 84 entities, 9 languages): exact-span F1 0.99 and 0 of 67 PII values leaked in 0.8.0. Run `python -m scripts.benchmark_release`.
281
+ - Multilingual PII eval (220 synthetic records, 11 languages): the default ensemble scores 0.93 recall and 0.95 precision. Run `python -m scripts.run_multilingual_eval` ([docs/MULTILINGUAL_EVAL.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/MULTILINGUAL_EVAL.md)).
282
+ - Score columns and data cards: `sct-report data.jsonl --text-column text --out report.json` writes counts, shares and hashes, never text ([docs/SCORE.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/SCORE.md)).
283
+ - Test your own model or data with the PII evaluation harness ([docs/EVALUATION.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/EVALUATION.md)).
284
+
285
+ ## Languages
286
+
287
+ Language detection covers 75 languages. Full NER models cover 4 (English, Dutch, German, Spanish). The rest use the multilingual WikiNEural model with the English model, or a model you bring with `ner_models`. Full list: [docs/LANGUAGES.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/LANGUAGES.md).
288
+
289
+ ## Limits and design principles
290
+
291
+ - **Safe defaults, every choice configurable.** Each behaviour is a config field; nothing is forced.
292
+ - **No guarantee that all PII is found.** Models miss entities, mostly organisations and non-Latin scripts. Review output before you publish it.
293
+ - **Reversible maps are personal data.** Anyone with the map can restore the values. Store maps apart from the cleaned data, with access control.
294
+ - **Offsets are post-regex.** Finding offsets point into the text after the regex step, not into the raw input.
295
+ - **Synthetic data is not anonymisation.** Fake values keep the data useful; they do not make it anonymous in a legal sense.
296
+
297
+ <details>
298
+ <summary>Known limits of 0.8.0 (details in <a href="CHANGELOG.md">CHANGELOG.md</a>)</summary>
299
+
300
+ - Dates: only full dates become one `<DATE>`. `1990年1月`, `1月15日`, Japanese era years and `1990.1.15.` do not; the year and number steps still change them (`1990年1月` becomes `<YEAR>年<NUMBER>月`, `生日1990.1.15谢谢` becomes `<YEAR><NUMBER>.15`). A circled number glued to a unit character (`1月⑮日`) stays a list marker. Thai month names need `custom_month_names`.
301
+ - In ensembles with GLiNER, windows are sized for the token-classification models, not for GLiNER's own word limit. GLiNER2 runs one chunk per call; torch models and GLiNER keep one lock per model, so they do not run in parallel across threads.
302
+ - Only `process_batch` is faster; `process` of one document is not batched across documents.
303
+ - `ner_chunk_overlap` (off by default) can still cut an entity longer than the overlap that starts at the very end of a chunk.
304
+ - `06-12345678` becomes `06-<PHONE>`. Adversarial strings such as `+000000000(000)0` can leave `(000)0` after phone replacement.
305
+ - Two addresses glued with no separator in a script written without spaces can be read as one address or split at the wrong place.
306
+ - ORGANISATION is the weakest NER label (0.75 recall in the multilingual eval); Bengali and Tamil name spans can be a few characters off.
307
+ - A URL with a path swallows a trailing sentence period (`see https://x.org/a.` becomes `see <URL>`). A URL path that switches from Latin letters or digits straight into a script written without spaces is replaced only up to the switch (`https://x.org/a東京secret` becomes `<URL>東京secret`). Rare styled URL hosts and mail addresses (`https://ab。⑧․cd.ex.com/...`, `INFO@COL·LEGI。CAT`) are not fully replaced.
308
+ - Score B's quality head was tested with a small ONNX graph only; GLiNER PII mode was not run end to end for this release. CI does not install the `mcp` extra; FastMCP-only tests skip there.
309
+ - An unpinned NER model makes one Hub request per worker when the parent never loaded it before forking; pin `ner_model_revisions` or set `HF_HUB_OFFLINE=1`.
310
+ - With an empty regex marker (`replace_with_email=""`), NER in synthetic or reversible mode can find other entities than in placeholder mode.
311
+ - `WindowedStreamingRedactor` can cut an HTML tag that holds a space when the tag reaches past `overlap_chars`. It raises `BufferError` when the buffer passes `max_buffer_chars` (default 4096) with no safe whitespace split; raise the limit or use `BufferedStreamingRedactor`.
312
+
313
+ </details>
314
+
315
+ ## Documentation
316
+
317
+ | Topic | Where |
318
+ |---|---|
319
+ | Install and all extras, speed and hardware | [docs/INSTALL.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INSTALL.md), [docs/PERFORMANCE.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/PERFORMANCE.md) |
320
+ | Every config field, presets, language options | [docs/CONFIG.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/CONFIG.md) |
321
+ | `TextCleaner`, `ProcessResult`, `signals` | [docs/API.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/API.md) |
322
+ | NER backends, default models, GLiNER, PII mode | [docs/NER_BACKENDS.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/NER_BACKENDS.md) |
323
+ | Synthetic and reversible replacement, GLiClass | [docs/REPLACEMENT_MODES.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/REPLACEMENT_MODES.md) |
324
+ | Privacy policies, `TokenStore`, offsets, k-anonymity | [docs/PRIVACY.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/PRIVACY.md) |
325
+ | JSON, DataFrame, RAG, CLI, streaming, OCR, browser | [docs/ADAPTERS.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/ADAPTERS.md) |
326
+ | Framework adapters, other engines, MCP server | [docs/INTEGRATIONS.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/INTEGRATIONS.md), [docs/RECIPES.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/RECIPES.md), [docs/MCP.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/MCP.md) |
327
+ | Evaluation, score columns, `sct-report` | [docs/EVALUATION.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/EVALUATION.md), [docs/SCORE.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/SCORE.md), [docs/MULTILINGUAL_EVAL.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/MULTILINGUAL_EVAL.md) |
328
+ | Languages and adding a language | [docs/LANGUAGES.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/LANGUAGES.md), [docs/ADDING_LANGUAGES.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/ADDING_LANGUAGES.md) |
329
+ | The whole API in one file, for AI coding assistants | [llms.txt](https://github.com/rhnfzl/SqueakyCleanText/blob/main/llms.txt) |
330
+
331
+ ## What's new in 0.8.0
332
+
333
+ See [CHANGELOG.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/CHANGELOG.md) (many breaking changes). Highlights of 0.8.0:
334
+
335
+ - `process_batch` is 4.2x faster on short documents on 16 CPU cores; GPU device options.
336
+ - Detection of all 75 Lingua languages, with confidence and warnings.
337
+ - `result.signals`, a value-free view for logs, score columns and data cards (`sct-report`).
338
+ - Adapters for Hugging Face `datasets`, DataTrove, LangChain and Haystack, and a `training` preset.
339
+ - A reworked MCP server and a multilingual PII eval in 11 languages. Python 3.12 or newer.
340
+
341
+ ## Related work
342
+
343
+ - [clean-text](https://github.com/jfilter/clean-text) normalises text and replaces URLs, emails and phones with rules. SCT adds NER for names and places and works in many languages.
344
+ - [ftfy](https://github.com/rspeer/python-ftfy) repairs broken unicode. SCT uses it as its first fix step.
345
+ - [Presidio](https://github.com/microsoft/presidio) is a PII detection and anonymisation framework you assemble from recognisers. SCT is one call with multilingual defaults and also cleans the text.
346
+ - [scrubadub](https://github.com/LeapBeyond/scrubadub) removes PII from free text with detectors. SCT adds text cleaning, reversible maps and value-free metadata.
347
+
348
+ ## Contributing
349
+
350
+ Issues and pull requests are welcome. Read [AGENTS.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/AGENTS.md) first: model mistakes are fixed with model-driven rules, never with word lists, and every behaviour stays configurable.
351
+
352
+ ## Citation
353
+
354
+ ```bibtex
355
+ @software{fazal_squeakycleantext,
356
+ author = {Fazal, Rehan},
357
+ title = {SqueakyCleanText},
358
+ url = {https://github.com/rhnfzl/SqueakyCleanText},
359
+ version = {0.8.0},
360
+ year = {2026}
361
+ }
362
+ ```
363
+
364
+ ## License and acknowledgements
365
+
366
+ MIT. See [LICENSE](https://github.com/rhnfzl/SqueakyCleanText/blob/main/LICENSE). The package took inspiration from [clean-text](https://github.com/jfilter/clean-text). It builds on [ftfy](https://github.com/rspeer/python-ftfy), [Lingua](https://github.com/pemistahl/lingua-py), [ONNX Runtime](https://onnxruntime.ai/) and the NER models listed in [docs/NER_BACKENDS.md](https://github.com/rhnfzl/SqueakyCleanText/blob/main/docs/NER_BACKENDS.md).