SqueakyCleanText 0.5.2__tar.gz → 0.6.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 (34) hide show
  1. {squeakycleantext-0.5.2/SqueakyCleanText.egg-info → squeakycleantext-0.6.0}/PKG-INFO +213 -36
  2. squeakycleantext-0.5.2/PKG-INFO → squeakycleantext-0.6.0/README.md +175 -75
  3. squeakycleantext-0.5.2/README.md → squeakycleantext-0.6.0/SqueakyCleanText.egg-info/PKG-INFO +252 -6
  4. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/SqueakyCleanText.egg-info/SOURCES.txt +4 -0
  5. squeakycleantext-0.6.0/SqueakyCleanText.egg-info/requires.txt +58 -0
  6. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/pyproject.toml +41 -29
  7. squeakycleantext-0.6.0/sct/__init__.py +13 -0
  8. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/config.py +171 -26
  9. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/sct.py +119 -27
  10. squeakycleantext-0.6.0/sct/utils/anonymization_map.py +89 -0
  11. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/contact.py +18 -0
  12. squeakycleantext-0.6.0/sct/utils/gliclass_adapter.py +114 -0
  13. squeakycleantext-0.6.0/sct/utils/gliner_adapter.py +191 -0
  14. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/ner.py +113 -15
  15. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/normtext.py +2 -1
  16. squeakycleantext-0.6.0/sct/utils/process_result.py +67 -0
  17. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/resources.py +45 -10
  18. squeakycleantext-0.6.0/sct/utils/synthetic.py +125 -0
  19. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/tests/test_sct.py +755 -3
  20. squeakycleantext-0.5.2/SqueakyCleanText.egg-info/requires.txt +0 -46
  21. squeakycleantext-0.5.2/sct/__init__.py +0 -7
  22. squeakycleantext-0.5.2/sct/utils/gliner_adapter.py +0 -110
  23. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/LICENSE +0 -0
  24. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/SqueakyCleanText.egg-info/dependency_links.txt +0 -0
  25. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/SqueakyCleanText.egg-info/top_level.txt +0 -0
  26. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/__init__.py +0 -0
  27. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/constants.py +0 -0
  28. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/datetime.py +0 -0
  29. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/onnx_pipeline.py +0 -0
  30. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/special.py +0 -0
  31. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/stopwords.py +0 -0
  32. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/sct/utils/torch_pipeline.py +0 -0
  33. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/setup.cfg +0 -0
  34. {squeakycleantext-0.5.2 → squeakycleantext-0.6.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SqueakyCleanText
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Text preprocessing & PII anonymization pipeline for NLP/ML: ONNX NER ensemble, language detection, stopword removal, and configurable token replacement.
5
5
  Author: Rehan Fazal
6
6
  License: MIT
@@ -27,44 +27,52 @@ Classifier: Operating System :: OS Independent
27
27
  Requires-Python: >=3.11
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE
30
- Requires-Dist: lingua-language-detector>=2.0.2
30
+ Requires-Dist: lingua-language-detector>=2.1.0
31
31
  Requires-Dist: stop-words>=2025.11.4
32
- Requires-Dist: emoji>=2.8
33
- Requires-Dist: ftfy>=6.1
34
- Requires-Dist: Unidecode>=1.3
35
- Requires-Dist: beautifulsoup4>=4.12
36
- Requires-Dist: onnxruntime>=1.24.1
37
- Requires-Dist: tokenizers>=0.22.0
38
- Requires-Dist: huggingface-hub>=1.4.0
39
- Requires-Dist: numpy>=2.0.0
40
- Requires-Dist: presidio_anonymizer>=2.2.355
41
- Requires-Dist: regex>=2024.0.0
32
+ Requires-Dist: emoji>=2.14
33
+ Requires-Dist: ftfy>=6.3
34
+ Requires-Dist: Unidecode>=1.4
35
+ Requires-Dist: beautifulsoup4>=4.14
36
+ Requires-Dist: onnxruntime>=1.24.2
37
+ Requires-Dist: tokenizers>=0.22.2
38
+ Requires-Dist: huggingface-hub>=1.5.0
39
+ Requires-Dist: numpy>=2.2.0
40
+ Requires-Dist: presidio_anonymizer>=2.2.360
41
+ Requires-Dist: regex>=2024.11.6
42
42
  Provides-Extra: gpu
43
- Requires-Dist: onnxruntime-gpu>=1.24.1; extra == "gpu"
43
+ Requires-Dist: onnxruntime-gpu>=1.24.2; extra == "gpu"
44
44
  Provides-Extra: fuzzy
45
- Requires-Dist: rapidfuzz>=3.0; extra == "fuzzy"
45
+ Requires-Dist: rapidfuzz>=3.12; extra == "fuzzy"
46
46
  Provides-Extra: torch
47
- Requires-Dist: torch>=2.0.0; extra == "torch"
48
- Requires-Dist: transformers>=4.30; extra == "torch"
47
+ Requires-Dist: torch>=2.6.0; extra == "torch"
48
+ Requires-Dist: transformers>=4.48; extra == "torch"
49
49
  Provides-Extra: gliner
50
- Requires-Dist: gliner>=1.0; extra == "gliner"
50
+ Requires-Dist: gliner<1.0,>=0.2.25; extra == "gliner"
51
51
  Provides-Extra: gliner2
52
- Requires-Dist: gliner2>=0.0.1; extra == "gliner2"
52
+ Requires-Dist: gliner2<2.0,>=1.0; extra == "gliner2"
53
+ Provides-Extra: synthetic
54
+ Requires-Dist: faker>=33.0; extra == "synthetic"
55
+ Provides-Extra: presidio
56
+ Requires-Dist: presidio-analyzer>=2.2.360; extra == "presidio"
57
+ Provides-Extra: classify
58
+ Requires-Dist: gliclass>=0.1.16; extra == "classify"
59
+ Provides-Extra: classify-onnx
60
+ Requires-Dist: onnxruntime>=1.24.2; extra == "classify-onnx"
53
61
  Provides-Extra: all-ner
54
- Requires-Dist: torch>=2.0.0; extra == "all-ner"
55
- Requires-Dist: transformers>=4.30; extra == "all-ner"
56
- Requires-Dist: gliner>=1.0; extra == "all-ner"
57
- Requires-Dist: gliner2>=0.0.1; extra == "all-ner"
62
+ Requires-Dist: torch>=2.6.0; extra == "all-ner"
63
+ Requires-Dist: transformers>=4.48; extra == "all-ner"
64
+ Requires-Dist: gliner<1.0,>=0.2.25; extra == "all-ner"
65
+ Requires-Dist: gliner2<2.0,>=1.0; extra == "all-ner"
58
66
  Provides-Extra: dev
59
- Requires-Dist: hypothesis>=6.82; extra == "dev"
60
- Requires-Dist: faker>=20.0; extra == "dev"
61
- Requires-Dist: ruff>=0.4; extra == "dev"
62
- Requires-Dist: pytest>=8.0; extra == "dev"
63
- Requires-Dist: pytest-timeout>=2.2; extra == "dev"
64
- Requires-Dist: rapidfuzz>=3.0; extra == "dev"
67
+ Requires-Dist: hypothesis>=6.130; extra == "dev"
68
+ Requires-Dist: faker>=33.0; extra == "dev"
69
+ Requires-Dist: ruff>=0.11; extra == "dev"
70
+ Requires-Dist: pytest>=8.3; extra == "dev"
71
+ Requires-Dist: pytest-timeout>=2.3; extra == "dev"
72
+ Requires-Dist: rapidfuzz>=3.12; extra == "dev"
65
73
  Provides-Extra: test
66
- Requires-Dist: coverage>=7.3; extra == "test"
67
- Requires-Dist: pytest-cov>=4.1; extra == "test"
74
+ Requires-Dist: coverage>=7.8; extra == "test"
75
+ Requires-Dist: pytest-cov>=6.0; extra == "test"
68
76
  Dynamic: license-file
69
77
 
70
78
  <div align="center">
@@ -168,6 +176,9 @@ The base install uses **ONNX Runtime** for NER inference - no PyTorch or Transfo
168
176
  | PyTorch NER | `pip install SqueakyCleanText[torch]` | PyTorch/Transformers NER backend |
169
177
  | GLiNER | `pip install SqueakyCleanText[gliner]` | [GLiNER](https://github.com/urchade/GLiNER) zero-shot NER |
170
178
  | GLiNER2 | `pip install SqueakyCleanText[gliner2]` | [GLiNER2](https://github.com/Knowledgator/GLiNER) (knowledgator) backend |
179
+ | Synthetic | `pip install SqueakyCleanText[synthetic]` | Faker-based synthetic replacement (realistic fake values instead of `<TAG>` tokens) |
180
+ | Presidio | `pip install SqueakyCleanText[presidio]` | Presidio-analyzer for `presidio_gliner` backend |
181
+ | Classify | `pip install SqueakyCleanText[classify]` | GLiClass document-level pre-classification |
171
182
  | All NER | `pip install SqueakyCleanText[all-ner]` | All NER backends combined |
172
183
  | Development | `pip install SqueakyCleanText[dev]` | Testing and linting tools |
173
184
 
@@ -212,7 +223,7 @@ cfg = TextCleanerConfig(
212
223
  replace_with_url="<URL>",
213
224
  replace_with_email="<EMAIL>",
214
225
  replace_with_phone_numbers="<PHONE>",
215
- language="ENGLISH", # Skip auto-detection
226
+ language="en", # Pin to English (also accepts 'ENGLISH', 'eng')
216
227
  )
217
228
 
218
229
  # Initialize with config
@@ -262,6 +273,144 @@ cleaner = TextCleaner(cfg=cfg)
262
273
  lm_text, stat_text, lang = cleaner.process("Angela Merkel visited the Bundestag in Berlin.")
263
274
  ```
264
275
 
276
+ ### PII Detection Mode
277
+
278
+ Automatically configure GLiNER for comprehensive PII detection with 60+ entity types (personal, financial, healthcare, identity, digital):
279
+
280
+ ```python
281
+ from sct import TextCleaner, TextCleanerConfig
282
+
283
+ cfg = TextCleanerConfig(ner_mode='pii')
284
+
285
+ cleaner = TextCleaner(cfg=cfg)
286
+ lm_text, stat_text, lang = cleaner.process(
287
+ "John Smith's SSN is 123-45-6789, email john@example.com, DOB 1990-01-15"
288
+ )
289
+ # Entities are anonymized: names, SSNs, emails, dates of birth, and 50+ more PII types
290
+ ```
291
+
292
+ PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets threshold to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
293
+
294
+ **Alternative PII models** (pass as `gliner_model`):
295
+
296
+ | Model | Type | Size | Labels | F1 |
297
+ |-------|------|------|--------|-----|
298
+ | [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0) | Uni-encoder | 330MB (ONNX FP16) | 60+ | 80.99% |
299
+ | [`nvidia/gliner-PII`](https://huggingface.co/nvidia/gliner-PII) | Bi-encoder | 570MB | 55+ | — |
300
+ | [`gretelai/gretel-gliner-bi-base-v1.0`](https://huggingface.co/gretelai/gretel-gliner-bi-base-v1.0) | Bi-encoder | ~800MB | 40+ | 95% |
301
+ | [`urchade/gliner_multi_pii-v1`](https://huggingface.co/urchade/gliner_multi_pii-v1) | Multilingual | — | — | — |
302
+
303
+ ### Synthetic Replacement
304
+
305
+ Replace detected entities with realistic fake values (via [Faker](https://faker.readthedocs.io/)) instead of `<TAG>` placeholder tokens:
306
+
307
+ ```python
308
+ from sct import TextCleaner, TextCleanerConfig
309
+
310
+ cfg = TextCleanerConfig(
311
+ ner_mode='pii',
312
+ replacement_mode='synthetic', # pip install squeakycleantext[synthetic]
313
+ )
314
+
315
+ cleaner = TextCleaner(cfg=cfg)
316
+ lm_text, stat_text, lang = cleaner.process(
317
+ "Contact John Smith at john.smith@company.com or +1-555-0123"
318
+ )
319
+ # Output: "Contact Jennifer Williams at lisa45@example.net or +1-555-0198"
320
+ # Same entity always maps to same fake value within a document
321
+ ```
322
+
323
+ > **Note**: Synthetic replacement preserves data utility for downstream ML tasks but is NOT GDPR-compliant anonymization. Same-document consistency is maintained (same entity text always maps to the same fake value).
324
+
325
+ ### Reversible Anonymization
326
+
327
+ Replace entities with indexed placeholders (`<PERSON_0>`, `<LOCATION_1>`) and get a mapping for round-trip deanonymization:
328
+
329
+ ```python
330
+ from sct import TextCleaner, TextCleanerConfig
331
+
332
+ cfg = TextCleanerConfig(
333
+ ner_mode='pii',
334
+ replacement_mode='reversible',
335
+ )
336
+
337
+ cleaner = TextCleaner(cfg=cfg)
338
+ result = cleaner.process("John Smith works at Google in London.")
339
+
340
+ print(result.lm_text)
341
+ # "<PERSON_0> works at <ORGANISATION_0> in <LOCATION_0>."
342
+
343
+ # Access the anonymization map via metadata
344
+ anon_map = result.metadata['anon_map']
345
+ restored = anon_map.deanonymize(result.lm_text)
346
+ # "John Smith works at Google in London."
347
+
348
+ # Serialize the map for storage
349
+ import json
350
+ json.dumps(anon_map.to_dict())
351
+ ```
352
+
353
+ > **Note**: `ProcessResult` from `process()` unpacks as a 3-tuple (`lm_text, stat_text, language`) for backward compatibility, but also exposes `.metadata` for reversible maps and document classification.
354
+
355
+ ### Document Classification (GLiClass)
356
+
357
+ Classify documents before processing using zero-shot classification with [GLiClass](https://github.com/Knowledgator/GLiClass):
358
+
359
+ ```python
360
+ from sct import TextCleaner, TextCleanerConfig
361
+
362
+ cfg = TextCleanerConfig(
363
+ check_classify_document=True,
364
+ gliclass_labels=('email', 'code', 'legal', 'medical'),
365
+ # gliclass_model defaults to 'knowledgator/gliclass-edge-v3.0' (32.7M params)
366
+ )
367
+
368
+ cleaner = TextCleaner(cfg=cfg) # pip install squeakycleantext[classify]
369
+ result = cleaner.process("Dear Sir, please find attached the contract...")
370
+
371
+ # Classification results in metadata
372
+ print(result.metadata['classes'])
373
+ # [{"label": "email", "score": 0.92}, {"label": "legal", "score": 0.78}]
374
+ ```
375
+
376
+ ### Bi-Encoder GLiNER Models
377
+
378
+ Bi-encoder models (ModernBERT, etc.) are auto-detected and leverage pre-computed label embeddings for faster inference with larger context windows:
379
+
380
+ ```python
381
+ from sct import TextCleaner, TextCleanerConfig
382
+
383
+ cfg = TextCleanerConfig(
384
+ ner_backend='gliner',
385
+ gliner_model='knowledgator/gliner-bi-base-v2.0',
386
+ gliner_labels=('person', 'organization', 'location'),
387
+ )
388
+
389
+ cleaner = TextCleaner(cfg=cfg)
390
+ # Auto-detects bi-encoder → caches label embeddings → uses 2048+ token context window
391
+ ```
392
+
393
+ ### Entity Description Labels (ZERONER-Style)
394
+
395
+ Provide natural-language descriptions for labels to improve zero-shot recognition accuracy:
396
+
397
+ ```python
398
+ from sct import TextCleaner, TextCleanerConfig
399
+
400
+ cfg = TextCleanerConfig(
401
+ ner_backend='gliner',
402
+ gliner_model='knowledgator/gliner-bi-base-v2.0',
403
+ gliner_label_descriptions={
404
+ 'person': "a person's full legal name",
405
+ 'location': "a geographical place or address",
406
+ 'organization': "a company, institution, or government body",
407
+ },
408
+ )
409
+
410
+ cleaner = TextCleaner(cfg=cfg)
411
+ # Descriptions are used for inference, results are mapped back to original label names
412
+ ```
413
+
265
414
  ### Batch Processing
266
415
 
267
416
  ```python
@@ -318,15 +467,16 @@ cleaner = sct.TextCleaner()
318
467
 
319
468
  ## NER Backends
320
469
 
321
- SqueakyCleanText supports five NER backends, selectable via the `ner_backend` config field:
470
+ SqueakyCleanText supports six NER backends, selectable via the `ner_backend` config field:
322
471
 
323
472
  | Backend | Description | Dependencies | Best for |
324
473
  |---------|-------------|-------------|----------|
325
474
  | `onnx` (default) | ONNX Runtime inference with quantized XLM-RoBERTa models | Base install | Production: fast, torch-free |
326
475
  | `torch` | PyTorch/Transformers pipeline with full XLM-RoBERTa models | `[torch]` extra | Compatibility with existing PyTorch workflows |
327
- | `gliner` | GLiNER zero-shot NER with custom entity labels | `[gliner]` or `[gliner2]` extra | Custom entity types (PRODUCT, SKILL, EVENT, etc.) |
476
+ | `gliner` | GLiNER zero-shot NER with custom entity labels | `[gliner]` or `[gliner2]` extra | Custom entity types, PII detection, bi-encoder models |
328
477
  | `ensemble_onnx` | ONNX + GLiNER ensemble voting | `[gliner]` extra | Maximum recall with custom entities |
329
478
  | `ensemble_torch` | Torch + GLiNER ensemble voting | `[torch,gliner]` extra | Maximum recall with PyTorch |
479
+ | `presidio_gliner` | Presidio + GLiNER recognizer (beta) | `presidio-analyzer`, `[gliner]` | Context-aware NER via Presidio's pipeline |
330
480
 
331
481
  ### Default NER Models (ONNX)
332
482
 
@@ -339,6 +489,17 @@ SqueakyCleanText supports five NER backends, selectable via the `ner_backend` co
339
489
  | French / Portuguese / Italian | [`rhnfzl/wikineural-multilingual-ner-onnx`](https://huggingface.co/rhnfzl/wikineural-multilingual-ner-onnx) (shared session) |
340
490
  | Multilingual (fallback) | [`rhnfzl/wikineural-multilingual-ner-onnx`](https://huggingface.co/rhnfzl/wikineural-multilingual-ner-onnx) |
341
491
 
492
+ ### GLiNER Model Recommendations
493
+
494
+ | Model | Architecture | Context | Languages | Best for |
495
+ |-------|-------------|---------|-----------|----------|
496
+ | `knowledgator/gliner-bi-base-v2.0` | Bi-encoder (ModernBERT) | 2048 | Multi | General NER, long documents |
497
+ | `knowledgator/gliner-pii-base-v1.0` | Bi-encoder | 2048 | Multi | PII detection (60+ entity types) |
498
+ | `urchade/gliner_large-v2.1` | Uni-encoder (DeBERTa) | 512 | Multi | Legacy, high accuracy on short texts |
499
+ | `MatteoFasulo/ModernBERT-base-NER` | ModernBERT | 8192 | English | English-only, very long context |
500
+
501
+ > **GLiNER2 note**: `pip install squeakycleantext[gliner2]` installs [Knowledgator's gliner2 package](https://github.com/Knowledgator/GLiNER), not Fastino AI's GLiNER2 from EMNLP 2025 (different API).
502
+
342
503
  ### GLiNER Label Mapping
343
504
 
344
505
  GLiNER uses lowercase free-text labels (e.g., `'person'`, `'product'`). To map them to standard NER tags used by the anonymizer, use `gliner_label_map`:
@@ -449,7 +610,9 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
449
610
 
450
611
  | Field | Default | Description |
451
612
  |-------|---------|-------------|
452
- | `ner_backend` | `'onnx'` | Backend: `onnx`, `torch`, `gliner`, `ensemble_onnx`, `ensemble_torch` |
613
+ | `ner_backend` | `'onnx'` | Backend: `onnx`, `torch`, `gliner`, `ensemble_onnx`, `ensemble_torch`, `presidio_gliner` |
614
+ | `ner_mode` | `'standard'` | `'standard'` or `'pii'` (auto-configures GLiNER for PII detection) |
615
+ | `replacement_mode` | `'placeholder'` | `'placeholder'`, `'synthetic'` (Faker), or `'reversible'` (indexed placeholders + deanonymize map) |
453
616
  | `positional_tags` | `('PER', 'LOC', 'ORG', 'MISC')` | Entity types to recognize |
454
617
  | `ner_confidence_threshold` | `0.85` | Minimum confidence score |
455
618
  | `ner_batch_size` | `8` | Inference batch size (must be >= 1) |
@@ -460,6 +623,7 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
460
623
  | `gliner_labels` | `('person', 'organization', 'location')` | GLiNER entity labels |
461
624
  | `gliner_label_map` | `None` | Maps GLiNER labels to NER tags |
462
625
  | `gliner_threshold` | `0.4` | GLiNER confidence threshold |
626
+ | `gliner_label_descriptions` | `None` | ZERONER-style: `{label: "description"}` for improved zero-shot accuracy |
463
627
  | `fuzzy_date_score_cutoff` | `85` | Fuzzy matching threshold (0-100) for misspelled months |
464
628
  | `custom_pipeline_steps` | `()` | Tuple of `(text: str) -> str` callables appended after all built-in steps |
465
629
 
@@ -467,8 +631,8 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
467
631
 
468
632
  | Field | Default | Description |
469
633
  |-------|---------|-------------|
470
- | `language` | `None` | Pin language (skip detection) |
471
- | `extra_languages` | `()` | Additional language names for detection |
634
+ | `language` | `None` | Pin language (`'en'`), restrict detection to a set (`('en','nl')`), or `None` for auto-detect. Accepts Lingua names, ISO 639-1, ISO 639-3 codes. |
635
+ | `extra_languages` | `()` | Additional language names/codes for detection |
472
636
  | `custom_stopwords` | `None` | `{LANG: frozenset({...})}` custom stopword sets |
473
637
  | `custom_month_names` | `None` | `{LANG: ('Jan', 'Feb', ...)}` for date detection |
474
638
 
@@ -511,6 +675,19 @@ Each step is toggled by a `TextCleanerConfig` field. The pipeline is built once
511
675
 
512
676
  ## What's New
513
677
 
678
+ **v0.6.0**
679
+ - **PII detection mode** (`ner_mode='pii'`): auto-configures GLiNER with 60+ PII entity labels (personal, financial, healthcare, identity, digital)
680
+ - **Synthetic replacement** (`replacement_mode='synthetic'`): Faker-generated realistic values instead of `<TAG>` placeholders, with per-document consistency
681
+ - **Reversible anonymization** (`replacement_mode='reversible'`): indexed placeholders (`<PERSON_0>`) with `AnonymizationMap` for round-trip deanonymization
682
+ - **Document classification** (`check_classify_document=True`): zero-shot GLiClass pre-classification before text processing
683
+ - **ProcessResult**: `process()` returns `ProcessResult` (backward-compatible 3-tuple) with `.metadata` for anonymization maps and classification results
684
+ - **GLiNER ONNX mode** (`gliner_onnx=True`): load GLiNER with pre-built ONNX weights from HuggingFace Hub (auto-set for PII + ONNX backend)
685
+ - **Bi-encoder support**: auto-detects ModernBERT and other bi-encoder GLiNER models, caches label embeddings, dynamic context windows (2048-8192 tokens)
686
+ - **Entity description labels**: ZERONER-style natural-language descriptions for improved zero-shot accuracy
687
+ - **Presidio GLiNER backend** (beta): opt-in `ner_backend='presidio_gliner'` for Presidio's context-aware recognition pipeline
688
+ - **ModernBERT ONNX export**: updated export script with ModernBERT support (English, 8192 token context)
689
+ - **Dynamic chunk sizing**: GLiNER chunk size adapts to model's actual context window instead of hardcoded 384
690
+
514
691
  **v0.5.x**
515
692
  - `aprocess_batch()`: async batch processing for FastAPI / aiohttp integrations
516
693
  - `warmup(languages)`: pre-load NER models at startup to eliminate first-request latency
@@ -1,72 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: SqueakyCleanText
3
- Version: 0.5.2
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: 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.11
23
- Classifier: Programming Language :: Python :: 3.12
24
- Classifier: Programming Language :: Python :: 3.13
25
- Classifier: License :: OSI Approved :: MIT License
26
- Classifier: Operating System :: OS Independent
27
- Requires-Python: >=3.11
28
- Description-Content-Type: text/markdown
29
- License-File: LICENSE
30
- Requires-Dist: lingua-language-detector>=2.0.2
31
- Requires-Dist: stop-words>=2025.11.4
32
- Requires-Dist: emoji>=2.8
33
- Requires-Dist: ftfy>=6.1
34
- Requires-Dist: Unidecode>=1.3
35
- Requires-Dist: beautifulsoup4>=4.12
36
- Requires-Dist: onnxruntime>=1.24.1
37
- Requires-Dist: tokenizers>=0.22.0
38
- Requires-Dist: huggingface-hub>=1.4.0
39
- Requires-Dist: numpy>=2.0.0
40
- Requires-Dist: presidio_anonymizer>=2.2.355
41
- Requires-Dist: regex>=2024.0.0
42
- Provides-Extra: gpu
43
- Requires-Dist: onnxruntime-gpu>=1.24.1; extra == "gpu"
44
- Provides-Extra: fuzzy
45
- Requires-Dist: rapidfuzz>=3.0; extra == "fuzzy"
46
- Provides-Extra: torch
47
- Requires-Dist: torch>=2.0.0; extra == "torch"
48
- Requires-Dist: transformers>=4.30; extra == "torch"
49
- Provides-Extra: gliner
50
- Requires-Dist: gliner>=1.0; extra == "gliner"
51
- Provides-Extra: gliner2
52
- Requires-Dist: gliner2>=0.0.1; extra == "gliner2"
53
- Provides-Extra: all-ner
54
- Requires-Dist: torch>=2.0.0; extra == "all-ner"
55
- Requires-Dist: transformers>=4.30; extra == "all-ner"
56
- Requires-Dist: gliner>=1.0; extra == "all-ner"
57
- Requires-Dist: gliner2>=0.0.1; extra == "all-ner"
58
- Provides-Extra: dev
59
- Requires-Dist: hypothesis>=6.82; extra == "dev"
60
- Requires-Dist: faker>=20.0; extra == "dev"
61
- Requires-Dist: ruff>=0.4; extra == "dev"
62
- Requires-Dist: pytest>=8.0; extra == "dev"
63
- Requires-Dist: pytest-timeout>=2.2; extra == "dev"
64
- Requires-Dist: rapidfuzz>=3.0; extra == "dev"
65
- Provides-Extra: test
66
- Requires-Dist: coverage>=7.3; extra == "test"
67
- Requires-Dist: pytest-cov>=4.1; extra == "test"
68
- Dynamic: license-file
69
-
70
1
  <div align="center">
71
2
 
72
3
  # SqueakyCleanText
@@ -168,6 +99,9 @@ The base install uses **ONNX Runtime** for NER inference - no PyTorch or Transfo
168
99
  | PyTorch NER | `pip install SqueakyCleanText[torch]` | PyTorch/Transformers NER backend |
169
100
  | GLiNER | `pip install SqueakyCleanText[gliner]` | [GLiNER](https://github.com/urchade/GLiNER) zero-shot NER |
170
101
  | GLiNER2 | `pip install SqueakyCleanText[gliner2]` | [GLiNER2](https://github.com/Knowledgator/GLiNER) (knowledgator) backend |
102
+ | Synthetic | `pip install SqueakyCleanText[synthetic]` | Faker-based synthetic replacement (realistic fake values instead of `<TAG>` tokens) |
103
+ | Presidio | `pip install SqueakyCleanText[presidio]` | Presidio-analyzer for `presidio_gliner` backend |
104
+ | Classify | `pip install SqueakyCleanText[classify]` | GLiClass document-level pre-classification |
171
105
  | All NER | `pip install SqueakyCleanText[all-ner]` | All NER backends combined |
172
106
  | Development | `pip install SqueakyCleanText[dev]` | Testing and linting tools |
173
107
 
@@ -212,7 +146,7 @@ cfg = TextCleanerConfig(
212
146
  replace_with_url="<URL>",
213
147
  replace_with_email="<EMAIL>",
214
148
  replace_with_phone_numbers="<PHONE>",
215
- language="ENGLISH", # Skip auto-detection
149
+ language="en", # Pin to English (also accepts 'ENGLISH', 'eng')
216
150
  )
217
151
 
218
152
  # Initialize with config
@@ -262,6 +196,144 @@ cleaner = TextCleaner(cfg=cfg)
262
196
  lm_text, stat_text, lang = cleaner.process("Angela Merkel visited the Bundestag in Berlin.")
263
197
  ```
264
198
 
199
+ ### PII Detection Mode
200
+
201
+ Automatically configure GLiNER for comprehensive PII detection with 60+ entity types (personal, financial, healthcare, identity, digital):
202
+
203
+ ```python
204
+ from sct import TextCleaner, TextCleanerConfig
205
+
206
+ cfg = TextCleanerConfig(ner_mode='pii')
207
+
208
+ cleaner = TextCleaner(cfg=cfg)
209
+ lm_text, stat_text, lang = cleaner.process(
210
+ "John Smith's SSN is 123-45-6789, email john@example.com, DOB 1990-01-15"
211
+ )
212
+ # Entities are anonymized: names, SSNs, emails, dates of birth, and 50+ more PII types
213
+ ```
214
+
215
+ PII mode auto-configures: `ner_backend='gliner'`, uses [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0), sets threshold to 0.3 (recall-focused), and expands positional tags. User-provided values always take priority.
216
+
217
+ **Alternative PII models** (pass as `gliner_model`):
218
+
219
+ | Model | Type | Size | Labels | F1 |
220
+ |-------|------|------|--------|-----|
221
+ | [`knowledgator/gliner-pii-base-v1.0`](https://huggingface.co/knowledgator/gliner-pii-base-v1.0) | Uni-encoder | 330MB (ONNX FP16) | 60+ | 80.99% |
222
+ | [`nvidia/gliner-PII`](https://huggingface.co/nvidia/gliner-PII) | Bi-encoder | 570MB | 55+ | — |
223
+ | [`gretelai/gretel-gliner-bi-base-v1.0`](https://huggingface.co/gretelai/gretel-gliner-bi-base-v1.0) | Bi-encoder | ~800MB | 40+ | 95% |
224
+ | [`urchade/gliner_multi_pii-v1`](https://huggingface.co/urchade/gliner_multi_pii-v1) | Multilingual | — | — | — |
225
+
226
+ ### Synthetic Replacement
227
+
228
+ Replace detected entities with realistic fake values (via [Faker](https://faker.readthedocs.io/)) instead of `<TAG>` placeholder tokens:
229
+
230
+ ```python
231
+ from sct import TextCleaner, TextCleanerConfig
232
+
233
+ cfg = TextCleanerConfig(
234
+ ner_mode='pii',
235
+ replacement_mode='synthetic', # pip install squeakycleantext[synthetic]
236
+ )
237
+
238
+ cleaner = TextCleaner(cfg=cfg)
239
+ lm_text, stat_text, lang = cleaner.process(
240
+ "Contact John Smith at john.smith@company.com or +1-555-0123"
241
+ )
242
+ # Output: "Contact Jennifer Williams at lisa45@example.net or +1-555-0198"
243
+ # Same entity always maps to same fake value within a document
244
+ ```
245
+
246
+ > **Note**: Synthetic replacement preserves data utility for downstream ML tasks but is NOT GDPR-compliant anonymization. Same-document consistency is maintained (same entity text always maps to the same fake value).
247
+
248
+ ### Reversible Anonymization
249
+
250
+ Replace entities with indexed placeholders (`<PERSON_0>`, `<LOCATION_1>`) and get a mapping for round-trip deanonymization:
251
+
252
+ ```python
253
+ from sct import TextCleaner, TextCleanerConfig
254
+
255
+ cfg = TextCleanerConfig(
256
+ ner_mode='pii',
257
+ replacement_mode='reversible',
258
+ )
259
+
260
+ cleaner = TextCleaner(cfg=cfg)
261
+ result = cleaner.process("John Smith works at Google in London.")
262
+
263
+ print(result.lm_text)
264
+ # "<PERSON_0> works at <ORGANISATION_0> in <LOCATION_0>."
265
+
266
+ # Access the anonymization map via metadata
267
+ anon_map = result.metadata['anon_map']
268
+ restored = anon_map.deanonymize(result.lm_text)
269
+ # "John Smith works at Google in London."
270
+
271
+ # Serialize the map for storage
272
+ import json
273
+ json.dumps(anon_map.to_dict())
274
+ ```
275
+
276
+ > **Note**: `ProcessResult` from `process()` unpacks as a 3-tuple (`lm_text, stat_text, language`) for backward compatibility, but also exposes `.metadata` for reversible maps and document classification.
277
+
278
+ ### Document Classification (GLiClass)
279
+
280
+ Classify documents before processing using zero-shot classification with [GLiClass](https://github.com/Knowledgator/GLiClass):
281
+
282
+ ```python
283
+ from sct import TextCleaner, TextCleanerConfig
284
+
285
+ cfg = TextCleanerConfig(
286
+ check_classify_document=True,
287
+ gliclass_labels=('email', 'code', 'legal', 'medical'),
288
+ # gliclass_model defaults to 'knowledgator/gliclass-edge-v3.0' (32.7M params)
289
+ )
290
+
291
+ cleaner = TextCleaner(cfg=cfg) # pip install squeakycleantext[classify]
292
+ result = cleaner.process("Dear Sir, please find attached the contract...")
293
+
294
+ # Classification results in metadata
295
+ print(result.metadata['classes'])
296
+ # [{"label": "email", "score": 0.92}, {"label": "legal", "score": 0.78}]
297
+ ```
298
+
299
+ ### Bi-Encoder GLiNER Models
300
+
301
+ Bi-encoder models (ModernBERT, etc.) are auto-detected and leverage pre-computed label embeddings for faster inference with larger context windows:
302
+
303
+ ```python
304
+ from sct import TextCleaner, TextCleanerConfig
305
+
306
+ cfg = TextCleanerConfig(
307
+ ner_backend='gliner',
308
+ gliner_model='knowledgator/gliner-bi-base-v2.0',
309
+ gliner_labels=('person', 'organization', 'location'),
310
+ )
311
+
312
+ cleaner = TextCleaner(cfg=cfg)
313
+ # Auto-detects bi-encoder → caches label embeddings → uses 2048+ token context window
314
+ ```
315
+
316
+ ### Entity Description Labels (ZERONER-Style)
317
+
318
+ Provide natural-language descriptions for labels to improve zero-shot recognition accuracy:
319
+
320
+ ```python
321
+ from sct import TextCleaner, TextCleanerConfig
322
+
323
+ cfg = TextCleanerConfig(
324
+ ner_backend='gliner',
325
+ gliner_model='knowledgator/gliner-bi-base-v2.0',
326
+ gliner_label_descriptions={
327
+ 'person': "a person's full legal name",
328
+ 'location': "a geographical place or address",
329
+ 'organization': "a company, institution, or government body",
330
+ },
331
+ )
332
+
333
+ cleaner = TextCleaner(cfg=cfg)
334
+ # Descriptions are used for inference, results are mapped back to original label names
335
+ ```
336
+
265
337
  ### Batch Processing
266
338
 
267
339
  ```python
@@ -318,15 +390,16 @@ cleaner = sct.TextCleaner()
318
390
 
319
391
  ## NER Backends
320
392
 
321
- SqueakyCleanText supports five NER backends, selectable via the `ner_backend` config field:
393
+ SqueakyCleanText supports six NER backends, selectable via the `ner_backend` config field:
322
394
 
323
395
  | Backend | Description | Dependencies | Best for |
324
396
  |---------|-------------|-------------|----------|
325
397
  | `onnx` (default) | ONNX Runtime inference with quantized XLM-RoBERTa models | Base install | Production: fast, torch-free |
326
398
  | `torch` | PyTorch/Transformers pipeline with full XLM-RoBERTa models | `[torch]` extra | Compatibility with existing PyTorch workflows |
327
- | `gliner` | GLiNER zero-shot NER with custom entity labels | `[gliner]` or `[gliner2]` extra | Custom entity types (PRODUCT, SKILL, EVENT, etc.) |
399
+ | `gliner` | GLiNER zero-shot NER with custom entity labels | `[gliner]` or `[gliner2]` extra | Custom entity types, PII detection, bi-encoder models |
328
400
  | `ensemble_onnx` | ONNX + GLiNER ensemble voting | `[gliner]` extra | Maximum recall with custom entities |
329
401
  | `ensemble_torch` | Torch + GLiNER ensemble voting | `[torch,gliner]` extra | Maximum recall with PyTorch |
402
+ | `presidio_gliner` | Presidio + GLiNER recognizer (beta) | `presidio-analyzer`, `[gliner]` | Context-aware NER via Presidio's pipeline |
330
403
 
331
404
  ### Default NER Models (ONNX)
332
405
 
@@ -339,6 +412,17 @@ SqueakyCleanText supports five NER backends, selectable via the `ner_backend` co
339
412
  | French / Portuguese / Italian | [`rhnfzl/wikineural-multilingual-ner-onnx`](https://huggingface.co/rhnfzl/wikineural-multilingual-ner-onnx) (shared session) |
340
413
  | Multilingual (fallback) | [`rhnfzl/wikineural-multilingual-ner-onnx`](https://huggingface.co/rhnfzl/wikineural-multilingual-ner-onnx) |
341
414
 
415
+ ### GLiNER Model Recommendations
416
+
417
+ | Model | Architecture | Context | Languages | Best for |
418
+ |-------|-------------|---------|-----------|----------|
419
+ | `knowledgator/gliner-bi-base-v2.0` | Bi-encoder (ModernBERT) | 2048 | Multi | General NER, long documents |
420
+ | `knowledgator/gliner-pii-base-v1.0` | Bi-encoder | 2048 | Multi | PII detection (60+ entity types) |
421
+ | `urchade/gliner_large-v2.1` | Uni-encoder (DeBERTa) | 512 | Multi | Legacy, high accuracy on short texts |
422
+ | `MatteoFasulo/ModernBERT-base-NER` | ModernBERT | 8192 | English | English-only, very long context |
423
+
424
+ > **GLiNER2 note**: `pip install squeakycleantext[gliner2]` installs [Knowledgator's gliner2 package](https://github.com/Knowledgator/GLiNER), not Fastino AI's GLiNER2 from EMNLP 2025 (different API).
425
+
342
426
  ### GLiNER Label Mapping
343
427
 
344
428
  GLiNER uses lowercase free-text labels (e.g., `'person'`, `'product'`). To map them to standard NER tags used by the anonymizer, use `gliner_label_map`:
@@ -449,7 +533,9 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
449
533
 
450
534
  | Field | Default | Description |
451
535
  |-------|---------|-------------|
452
- | `ner_backend` | `'onnx'` | Backend: `onnx`, `torch`, `gliner`, `ensemble_onnx`, `ensemble_torch` |
536
+ | `ner_backend` | `'onnx'` | Backend: `onnx`, `torch`, `gliner`, `ensemble_onnx`, `ensemble_torch`, `presidio_gliner` |
537
+ | `ner_mode` | `'standard'` | `'standard'` or `'pii'` (auto-configures GLiNER for PII detection) |
538
+ | `replacement_mode` | `'placeholder'` | `'placeholder'`, `'synthetic'` (Faker), or `'reversible'` (indexed placeholders + deanonymize map) |
453
539
  | `positional_tags` | `('PER', 'LOC', 'ORG', 'MISC')` | Entity types to recognize |
454
540
  | `ner_confidence_threshold` | `0.85` | Minimum confidence score |
455
541
  | `ner_batch_size` | `8` | Inference batch size (must be >= 1) |
@@ -460,6 +546,7 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
460
546
  | `gliner_labels` | `('person', 'organization', 'location')` | GLiNER entity labels |
461
547
  | `gliner_label_map` | `None` | Maps GLiNER labels to NER tags |
462
548
  | `gliner_threshold` | `0.4` | GLiNER confidence threshold |
549
+ | `gliner_label_descriptions` | `None` | ZERONER-style: `{label: "description"}` for improved zero-shot accuracy |
463
550
  | `fuzzy_date_score_cutoff` | `85` | Fuzzy matching threshold (0-100) for misspelled months |
464
551
  | `custom_pipeline_steps` | `()` | Tuple of `(text: str) -> str` callables appended after all built-in steps |
465
552
 
@@ -467,8 +554,8 @@ new_cfg = dataclasses.replace(cfg, check_ner_process=False)
467
554
 
468
555
  | Field | Default | Description |
469
556
  |-------|---------|-------------|
470
- | `language` | `None` | Pin language (skip detection) |
471
- | `extra_languages` | `()` | Additional language names for detection |
557
+ | `language` | `None` | Pin language (`'en'`), restrict detection to a set (`('en','nl')`), or `None` for auto-detect. Accepts Lingua names, ISO 639-1, ISO 639-3 codes. |
558
+ | `extra_languages` | `()` | Additional language names/codes for detection |
472
559
  | `custom_stopwords` | `None` | `{LANG: frozenset({...})}` custom stopword sets |
473
560
  | `custom_month_names` | `None` | `{LANG: ('Jan', 'Feb', ...)}` for date detection |
474
561
 
@@ -511,6 +598,19 @@ Each step is toggled by a `TextCleanerConfig` field. The pipeline is built once
511
598
 
512
599
  ## What's New
513
600
 
601
+ **v0.6.0**
602
+ - **PII detection mode** (`ner_mode='pii'`): auto-configures GLiNER with 60+ PII entity labels (personal, financial, healthcare, identity, digital)
603
+ - **Synthetic replacement** (`replacement_mode='synthetic'`): Faker-generated realistic values instead of `<TAG>` placeholders, with per-document consistency
604
+ - **Reversible anonymization** (`replacement_mode='reversible'`): indexed placeholders (`<PERSON_0>`) with `AnonymizationMap` for round-trip deanonymization
605
+ - **Document classification** (`check_classify_document=True`): zero-shot GLiClass pre-classification before text processing
606
+ - **ProcessResult**: `process()` returns `ProcessResult` (backward-compatible 3-tuple) with `.metadata` for anonymization maps and classification results
607
+ - **GLiNER ONNX mode** (`gliner_onnx=True`): load GLiNER with pre-built ONNX weights from HuggingFace Hub (auto-set for PII + ONNX backend)
608
+ - **Bi-encoder support**: auto-detects ModernBERT and other bi-encoder GLiNER models, caches label embeddings, dynamic context windows (2048-8192 tokens)
609
+ - **Entity description labels**: ZERONER-style natural-language descriptions for improved zero-shot accuracy
610
+ - **Presidio GLiNER backend** (beta): opt-in `ner_backend='presidio_gliner'` for Presidio's context-aware recognition pipeline
611
+ - **ModernBERT ONNX export**: updated export script with ModernBERT support (English, 8192 token context)
612
+ - **Dynamic chunk sizing**: GLiNER chunk size adapts to model's actual context window instead of hardcoded 384
613
+
514
614
  **v0.5.x**
515
615
  - `aprocess_batch()`: async batch processing for FastAPI / aiohttp integrations
516
616
  - `warmup(languages)`: pre-load NER models at startup to eliminate first-request latency