Feat: add class_name to allow multiple recognizers from same class (#1819)

* fix: Rename method to get_recognizer_class_name for clarity and update usage

* fix: Clarify comments regarding excluded recognizer attributes in RecognizerListLoader

* feat: Add class_name parameter to BaseRecognizerConfig for improved recognizer identification

* fix: Include 'class_name' in custom recognizers exclusion list for improved configuration handling

* feat: Enhance Ollama recognizer to support custom instance names and update configuration handling

* Enhance recognizers to accept additional keyword arguments

- Updated various recognizers across different countries (India, Italy, Korea, Poland, Singapore, Spain, Thailand, UK, US) to accept **kwargs in their constructors.
- This change allows for more flexible configuration of recognizers without modifying their signatures.
- Adjusted the recognizer loading mechanism to handle the new **kwargs parameter appropriately.

* refactor: Simplify Ollama recognizer loading verification and assertions

* test: Update Ollama recognizer loading verification to ensure single instance retrieval

* feat: Enhance recognizer class name logic in RecognizerListLoader

* Refactor recognizers to explicitly handle 'name' parameter in __init__ methods

- Updated various recognizers across different countries (Italy, Korea, Poland, Singapore, Spain, Thailand, UK, US) to include an optional 'name' parameter in their constructors.
- Adjusted super() calls to pass the 'name' parameter appropriately.
- Ensured that the 'Optional' type is imported where necessary.
- Added a script to automate the updates for recognizers that were missing the 'name' parameter.

* fix: Update Stanza and Transformers recognizers to handle additional kwargs in __init__ methods

* fix: Correct the import order for constants in methods.py

* refactor: Remove update_recognizers_name.py script as its functionality is no longer needed

* check

* fix: Remove unnecessary comments and clean up recognizer configuration code

* Refactor recognizer constructors to remove unused **kwargs parameter

- Updated multiple recognizer classes across various countries (Australia, Finland, India, Italy, Korea, Poland, Singapore, Spain, Thailand, UK, US) to remove the **kwargs parameter from their constructors.
- Simplified constructor signatures for better clarity and maintainability.

* refactor: Remove unused **kwargs parameter from recognizer initializers

* refactor: Remove unused **kwargs parameter from recognizer constructors

* fix ci

* refactor: format parameters in recognizer constructors for consistency

* refactor: format parameters in recognizer constructors for consistency
This commit is contained in:
Ron Shakutai
2026-01-05 11:36:07 +02:00
committed by GitHub
parent 15acbc9562
commit 1fd62dc5de
54 changed files with 158 additions and 71 deletions

View File

@@ -5,8 +5,8 @@ import os
import requests
from common.constants import (
ANONYMIZER_BASE_URL,
ANALYZER_BASE_URL,
ANONYMIZER_BASE_URL,
IMAGE_REDACTOR_BASE_URL,
)

View File

@@ -200,9 +200,10 @@ recognizers:
type: predefined
enabled: false
- name: OllamaLangExtractRecognizer
- name: e2eollama
supported_languages:
- en
type: predefined
class_name: OllamaLangExtractRecognizer
enabled: true
config_path: e2e-tests/resources/ollama_test_config.yaml

View File

@@ -71,16 +71,18 @@ def test_given_text_with_pii_using_ollama_recognizer_then_detects_entities(tmp_p
text_to_test = "Patient John Smith, SSN 123-45-6789, email john@example.com, phone 555-123-4567, lives at 123 Main St, works at Acme Corp"
# Use pre-configured config file with small model (qwen2.5:1.5b)
import os
config_path = os.path.join(
os.path.dirname(__file__), "..", "resources", "ollama_test_config.yaml"
)
# Create Ollama recognizer with custom config
ollama_recognizer = OllamaLangExtractRecognizer(config_path=config_path)
ollama_recognizer = OllamaLangExtractRecognizer(
config_path=config_path, name="e2eollama"
)
assert ollama_recognizer.name == "e2eollama", \
f"Expected recognizer name to be 'e2eollama', got '{ollama_recognizer.name}'"
# Create analyzer with ONLY Ollama recognizer (no NLP engine, no default recognizers)
from presidio_analyzer.recognizer_registry import RecognizerRegistry
registry = RecognizerRegistry()
registry.add_recognizer(ollama_recognizer)
@@ -90,13 +92,10 @@ def test_given_text_with_pii_using_ollama_recognizer_then_detects_entities(tmp_p
supported_languages=["en"]
)
# Analyze text
results = analyzer.analyze(text_to_test, language="en")
# Verify at least some entities were detected
assert len(results) > 0, "Expected to detect at least one PII entity"
# Check which recognizers participated in detection
recognizers_used = set()
langextract_detected_at_least_one = False
@@ -108,12 +107,11 @@ def test_given_text_with_pii_using_ollama_recognizer_then_detects_entities(tmp_p
recognizers_used.add(recognizer_name)
langextract_detected_at_least_one |= (
recognizer_name == "Ollama LangExtract PII"
recognizer_name == "e2eollama"
)
# Verify that Ollama LangExtract recognizer participated in detection
assert langextract_detected_at_least_one, \
f"Expected 'Ollama LangExtract PII' recognizer to detect at least one entity. Recognizers used: {recognizers_used}"
f"Expected 'e2eollama' recognizer to detect at least one entity. Recognizers used: {recognizers_used}"
@pytest.mark.package
@@ -133,7 +131,6 @@ def test_ollama_recognizer_loads_from_yaml_configuration_when_enabled():
if not OLLAMA_RECOGNIZER_AVAILABLE:
pytest.skip("LangExtract not installed")
# Check if Ollama is available
import os
try:
import requests
@@ -144,7 +141,6 @@ def test_ollama_recognizer_loads_from_yaml_configuration_when_enabled():
except Exception:
pytest.skip("Ollama service not available")
# Load recognizer registry from YAML config with Ollama enabled
from presidio_analyzer.recognizer_registry import RecognizerRegistryProvider
config_path = os.path.join(
@@ -155,33 +151,25 @@ def test_ollama_recognizer_loads_from_yaml_configuration_when_enabled():
provider = RecognizerRegistryProvider(conf_file=config_path)
registry = provider.create_recognizer_registry()
# Verify Ollama recognizer was loaded
ollama_recognizers = [r for r in registry.recognizers if "Ollama" in r.name]
ollama_recognizers = [r for r in registry.recognizers if r.name == "e2eollama"]
assert len(ollama_recognizers) == 1, \
f"Expected exactly 1 Ollama recognizer, found {len(ollama_recognizers)}"
f"Expected exactly 1 recognizer with name 'e2eollama', found {len(ollama_recognizers)}"
ollama_rec = ollama_recognizers[0]
assert ollama_rec.name == "Ollama LangExtract PII"
assert ollama_rec.supported_language == "en"
assert len(ollama_rec.supported_entities) > 0
ollama_recognizer = ollama_recognizers[0]
assert ollama_recognizer.__class__.__name__ == "OllamaLangExtractRecognizer", \
f"Expected class OllamaLangExtractRecognizer, got {ollama_recognizer.__class__.__name__}"
assert ollama_recognizer.supported_language == "en"
assert len(ollama_recognizer.supported_entities) > 0
# Test functionality: analyze text with the loaded recognizer
analyzer = AnalyzerEngine(registry=registry, supported_languages=["en"])
text_to_test = "Patient John Smith, SSN 123-45-6789, email john@example.com, phone 555-123-4567, lives at 123 Main St, works at Acme Corp"
results = analyzer.analyze(text_to_test, language="en")
# Should detect entities
assert len(results) > 0, "Expected to detect at least one PII entity"
# Check if Ollama recognizer detected anything
ollama_detected = any(
r.recognition_metadata and
"Ollama" in r.recognition_metadata.get(RecognizerResult.RECOGNIZER_NAME_KEY, "")
for r in results
)
# At minimum, other recognizers should detect common entities
entity_types = {r.entity_type for r in results}
expected_entities = {"EMAIL_ADDRESS", "PERSON", "PHONE_NUMBER", "US_SSN"}
detected_expected = entity_types & expected_entities
@@ -189,6 +177,6 @@ def test_ollama_recognizer_loads_from_yaml_configuration_when_enabled():
assert len(detected_expected) >= 2, \
f"Expected at least 2 entities from {expected_entities}, detected: {entity_types}"
print(f"\n✓ Ollama recognizer loaded successfully from YAML config")
print(f"\n✓ Ollama recognizer 'e2eollama' loaded successfully from YAML config")
print(f" Class: {ollama_recognizer.__class__.__name__}")
print(f" Detected entities: {entity_types}")
print(f" Ollama participated: {ollama_detected}")

View File

@@ -8,6 +8,7 @@ recognizers:
# For predefined:
# - If only a recognizer name is provided, a predefined recognizer with this name and default parameters will be loaded.
# - If a parameter isn't provided, the default one would be loaded.
# - Use 'class_name' to specify the Python class when using a custom 'name' for display/metadata
# For custom:
# - See an example configuration here: https://github.com/microsoft/presidio/blob/main/presidio-analyzer/presidio_analyzer/conf/example_recognizers.yaml
# - Custom pattern recognizers with this configuration can be added to this file, with type: custom
@@ -206,8 +207,9 @@ recognizers:
- en
type: predefined
enabled: false
- name: OllamaLangExtractRecognizer
- name: OllamaRecognizer
class_name: OllamaLangExtractRecognizer
supported_languages:
- en
type: predefined

View File

@@ -34,7 +34,8 @@ class LanguageContextConfig(BaseModel):
class BaseRecognizerConfig(BaseModel):
"""Base validation for all recognizer configuration types.
:param name: Name of the recognizer
:param name: Instance name used in analysis results. Defaults to class name.
:param class_name: Python class name for lookup. If not provided, uses 'name'.
:param enabled: Whether the recognizer is enabled
:param type: Type of recognizer (predefined/custom)
:param supported_language: Single supported language (legacy)
@@ -50,7 +51,14 @@ class BaseRecognizerConfig(BaseModel):
:param supported_entities: List of supported entities for this recognizer.
"""
name: str = Field(..., description="Name of the recognizer")
name: str = Field(..., description="Instance name for the recognizer")
class_name: Optional[str] = Field(
default=None,
description=(
"Python class name for predefined recognizers "
"(if different from instance name)"
),
)
enabled: bool = Field(default=True, description="Whether the recognizer is enabled")
type: Optional[str] = Field(
default="predefined", description="Type of recognizer (predefined/custom)"
@@ -136,11 +144,12 @@ class PredefinedRecognizerConfig(BaseRecognizerConfig):
@model_validator(mode="after")
def validate_predefined_recognizer_exists(self):
"""Validate that the predefined recognizer class actually exists."""
recognizer_class_name = self.class_name if self.class_name else self.name
try:
RecognizerListLoader.get_existing_recognizer_cls(self.name)
RecognizerListLoader.get_existing_recognizer_cls(recognizer_class_name)
except PredefinedRecognizerNotFoundError as e:
raise ValueError(
f"Predefined recognizer '{self.name}' not found: {str(e)}"
f"Predefined recognizer '{recognizer_class_name}' not found: {str(e)}"
) from e
return self
@@ -201,8 +210,6 @@ class CustomRecognizerConfig(BaseRecognizerConfig):
f"for your custom recognizer."
)
except PredefinedRecognizerNotFoundError:
# Name is not a predefined recognizer,
# which is fine for custom recognizers
pass
return data
@@ -328,7 +335,6 @@ class RecognizerRegistryConfig(BaseModel):
parsed_recognizers = []
for recognizer in recognizers:
if isinstance(recognizer, str):
# Simple string recognizer name - treat as predefined
parsed_recognizers.append(recognizer)
continue
@@ -346,7 +352,6 @@ class RecognizerRegistryConfig(BaseModel):
f"Either use type: 'custom' or remove these fields."
)
# Auto-detect type if not provided
if not recognizer_type:
if "patterns" in recognizer or "deny_list" in recognizer:
recognizer_type = "custom"
@@ -357,7 +362,6 @@ class RecognizerRegistryConfig(BaseModel):
recognizer_type = "predefined"
recognizer["type"] = recognizer_type
# Final append based on resolved type (only once)
if recognizer_type == "predefined":
parsed_recognizers.append(PredefinedRecognizerConfig(**recognizer))
elif recognizer_type == "custom":
@@ -369,7 +373,6 @@ class RecognizerRegistryConfig(BaseModel):
)
continue
# Fallback: unrecognized structure, keep as-is
parsed_recognizers.append(recognizer)
return parsed_recognizers
@@ -378,7 +381,6 @@ class RecognizerRegistryConfig(BaseModel):
def __check_if_predefined(cls, recognizer_name: Optional[Any]) -> None:
try:
RecognizerListLoader.get_existing_recognizer_cls(recognizer_name)
# If we reach here, it IS a predefined recognizer, so raise an error
raise ValueError(
f"Recognizer '{recognizer_name}' conflicts with a predefined "
f"recognizer. "
@@ -388,7 +390,6 @@ class RecognizerRegistryConfig(BaseModel):
f"for your custom recognizer."
)
except PredefinedRecognizerNotFoundError:
# Name is not a predefined recognizer, which is fine for custom recognizers
pass
@model_validator(mode="after")
@@ -401,12 +402,10 @@ class RecognizerRegistryConfig(BaseModel):
custom_without_language_present = False
for r in self.recognizers:
if isinstance(r, (PredefinedRecognizerConfig, CustomRecognizerConfig)):
# Track if any language is defined
if (r.supported_language and r.supported_language.strip()) or (
r.supported_languages and len(r.supported_languages) > 0
):
any_language_defined = True
# Track custom recognizers lacking language info
if (
isinstance(r, CustomRecognizerConfig)
and not r.supported_language

View File

@@ -31,13 +31,13 @@ class LMRecognizer(RemoteRecognizer, ABC):
self,
supported_entities: Optional[List[str]] = None,
supported_language: str = "en",
name: str = "Language Model PII Recognizer",
name: Optional[str] = None,
version: str = "1.0.0",
model_id: Optional[str] = None,
temperature: Optional[float] = None,
min_score: float = 0.5,
labels_to_ignore: Optional[List[str]] = None,
enable_generic_consolidation: bool = True
enable_generic_consolidation: bool = True,
):
"""Initialize LM recognizer.

View File

@@ -50,6 +50,7 @@ class AuAbnRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "AU_ABN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -61,6 +62,7 @@ class AuAbnRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -47,6 +47,7 @@ class AuAcnRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "AU_ACN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -58,6 +59,7 @@ class AuAcnRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -47,6 +47,7 @@ class AuMedicareRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "AU_MEDICARE",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -58,6 +59,7 @@ class AuMedicareRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -53,6 +53,7 @@ class AuTfnRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "AU_TFN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -64,6 +65,7 @@ class AuTfnRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -34,6 +34,7 @@ class FiPersonalIdentityCodeRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "fi",
supported_entity: str = "FI_PERSONAL_IDENTITY_CODE",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -42,6 +43,7 @@ class FiPersonalIdentityCodeRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> Optional[bool]:

View File

@@ -41,6 +41,7 @@ class InAadhaarRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "IN_AADHAAR",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
) -> None:
self.replacement_pairs = (
replacement_pairs
@@ -54,6 +55,7 @@ class InAadhaarRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -60,6 +60,7 @@ class InGstinRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "IN_GSTIN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -71,6 +72,7 @@ class InGstinRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
self.supported_entity = supported_entity

View File

@@ -53,6 +53,7 @@ class InPanRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "IN_PAN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -64,4 +65,5 @@ class InPanRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -34,6 +34,7 @@ class InPassportRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "IN_PASSPORT",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -42,4 +43,5 @@ class InPassportRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -331,6 +331,7 @@ class InVehicleRegistrationRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "IN_VEHICLE_REGISTRATION",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs
@@ -344,6 +345,7 @@ class InVehicleRegistrationRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -43,6 +43,7 @@ class InVoterRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "IN_VOTER",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -51,4 +52,5 @@ class InVoterRecognizer(PatternRecognizer):
context=context,
supported_language=supported_language,
supported_entity=supported_entity,
name=name,
)

View File

@@ -31,6 +31,7 @@ class ItDriverLicenseRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "it",
supported_entity: str = "IT_DRIVER_LICENSE",
name: Optional[str] = None,
) -> None:
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -39,4 +40,5 @@ class ItDriverLicenseRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -36,6 +36,7 @@ class ItFiscalCodeRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "it",
supported_entity: str = "IT_FISCAL_CODE",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -44,6 +45,7 @@ class ItFiscalCodeRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> Optional[bool]:

View File

@@ -59,6 +59,7 @@ class ItIdentityCardRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "it",
supported_entity: str = "IT_IDENTITY_CARD",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -67,4 +68,5 @@ class ItIdentityCardRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -38,6 +38,7 @@ class ItPassportRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "it",
supported_entity: str = "IT_PASSPORT",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -46,4 +47,5 @@ class ItPassportRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -37,6 +37,8 @@ class ItVatCodeRecognizer(PatternRecognizer):
supported_language: str = "it",
supported_entity: str = "IT_VAT_CODE",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
version: str = "0.0.1",
):
self.replacement_pairs = (
replacement_pairs
@@ -50,6 +52,8 @@ class ItVatCodeRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
version=version,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -56,6 +56,7 @@ class KrRrnRecognizer(PatternRecognizer):
supported_language: str = "ko",
supported_entity: str = "KR_RRN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = replacement_pairs if replacement_pairs else [("-", "")]
@@ -66,6 +67,7 @@ class KrRrnRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> Union[bool, None]:

View File

@@ -31,6 +31,7 @@ class PlPeselRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "pl",
supported_entity: str = "PL_PESEL",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -39,6 +40,7 @@ class PlPeselRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool: # noqa: D102

View File

@@ -30,6 +30,7 @@ class SgFinRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "SG_NRIC_FIN",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -38,4 +39,5 @@ class SgFinRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -83,6 +83,7 @@ class SgUenRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "SG_UEN",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -91,6 +92,7 @@ class SgUenRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> Optional[bool]:

View File

@@ -38,6 +38,7 @@ class EsNieRecognizer(PatternRecognizer):
supported_language: str = "es",
supported_entity: str = "ES_NIE",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -49,6 +50,7 @@ class EsNieRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -33,6 +33,7 @@ class EsNifRecognizer(PatternRecognizer):
supported_language: str = "es",
supported_entity: str = "ES_NIF",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -44,6 +45,7 @@ class EsNifRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool: # noqa: D102

View File

@@ -68,6 +68,7 @@ class ThTninRecognizer(PatternRecognizer):
supported_language: str = "th",
supported_entity: str = "TH_TNIN",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = replacement_pairs if replacement_pairs else []
@@ -78,6 +79,7 @@ class ThTninRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> Union[bool, None]:

View File

@@ -38,6 +38,7 @@ class NhsRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "UK_NHS",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -49,6 +50,7 @@ class NhsRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -29,6 +29,7 @@ class UkNinoRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "UK_NINO",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -37,4 +38,5 @@ class UkNinoRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -47,6 +47,7 @@ class AbaRoutingRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "ABA_ROUTING_NUMBER",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = replacement_pairs or [("-", "")]
patterns = patterns if patterns else self.PATTERNS
@@ -56,6 +57,7 @@ class AbaRoutingRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool: # noqa: D102

View File

@@ -36,6 +36,7 @@ class MedicalLicenseRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "MEDICAL_LICENSE",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -47,6 +48,7 @@ class MedicalLicenseRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool: # noqa: D102

View File

@@ -39,6 +39,7 @@ class UsBankRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "US_BANK_NUMBER",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -47,4 +48,5 @@ class UsBankRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -55,6 +55,7 @@ class UsLicenseRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "US_DRIVER_LICENSE",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -63,4 +64,5 @@ class UsLicenseRecognizer(PatternRecognizer):
supported_language=supported_language,
patterns=patterns,
context=context,
name=name,
)

View File

@@ -39,6 +39,7 @@ class UsItinRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "US_ITIN",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -47,4 +48,5 @@ class UsItinRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -26,6 +26,7 @@ class UsPassportRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "US_PASSPORT",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -34,4 +35,5 @@ class UsPassportRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -38,6 +38,7 @@ class UsSsnRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "US_SSN",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -46,6 +47,7 @@ class UsSsnRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def invalidate_result(self, pattern_text: str) -> bool:

View File

@@ -45,6 +45,7 @@ class CreditCardRecognizer(PatternRecognizer):
supported_language: str = "en",
supported_entity: str = "CREDIT_CARD",
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = (
replacement_pairs if replacement_pairs else [("-", ""), (" ", "")]
@@ -56,6 +57,7 @@ class CreditCardRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool: # noqa: D102

View File

@@ -39,6 +39,7 @@ class CryptoRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "CRYPTO",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -47,6 +48,7 @@ class CryptoRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str) -> bool:

View File

@@ -89,6 +89,7 @@ class DateRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "DATE_TIME",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -97,4 +98,5 @@ class DateRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -31,6 +31,7 @@ class EmailRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "EMAIL_ADDRESS",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -39,6 +40,7 @@ class EmailRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def validate_result(self, pattern_text: str): # noqa: D102

View File

@@ -74,6 +74,7 @@ class IbanRecognizer(PatternRecognizer):
bos_eos: Tuple[str, str] = (BOS, EOS),
regex_flags: int = re.DOTALL | re.MULTILINE,
replacement_pairs: Optional[List[Tuple[str, str]]] = None,
name: Optional[str] = None,
):
self.replacement_pairs = replacement_pairs or [("-", ""), (" ", "")]
self.exact_match = exact_match
@@ -86,6 +87,7 @@ class IbanRecognizer(PatternRecognizer):
context=context,
supported_language=supported_language,
global_regex_flags=regex_flags,
name=name,
)
def validate_result(self, pattern_text: str): # noqa: D102

View File

@@ -40,6 +40,7 @@ class IpRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "IP_ADDRESS",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -48,6 +49,7 @@ class IpRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)
def invalidate_result(self, pattern_text: str) -> bool:

View File

@@ -34,6 +34,7 @@ class PhoneRecognizer(LocalRecognizer):
# For all regions, use phonenumbers.SUPPORTED_REGIONS
supported_regions=DEFAULT_SUPPORTED_REGIONS,
leniency: Optional[int] = 1,
name: Optional[str] = None,
):
context = context if context else self.CONTEXT
self.supported_regions = supported_regions
@@ -42,6 +43,7 @@ class PhoneRecognizer(LocalRecognizer):
supported_entities=self.get_supported_entities(),
supported_language=supported_language,
context=context,
name=name,
)
def load(self) -> None: # noqa: D102

View File

@@ -37,6 +37,7 @@ class UrlRecognizer(PatternRecognizer):
context: Optional[List[str]] = None,
supported_language: str = "en",
supported_entity: str = "URL",
name: Optional[str] = None,
):
patterns = patterns if patterns else self.PATTERNS
context = context if context else self.CONTEXT
@@ -45,4 +46,5 @@ class UrlRecognizer(PatternRecognizer):
patterns=patterns,
context=context,
supported_language=supported_language,
name=name,
)

View File

@@ -42,6 +42,7 @@ class SpacyRecognizer(LocalRecognizer):
default_explanation: Optional[str] = None,
check_label_groups: Optional[List[Tuple[Set, Set]]] = None,
context: Optional[List[str]] = None,
name: Optional[str] = None,
):
"""Initialize the SpaCy recognizer.
@@ -69,6 +70,7 @@ class SpacyRecognizer(LocalRecognizer):
supported_entities=supported_entities,
supported_language=supported_language,
context=context,
name=name,
)
def load(self) -> None: # noqa: D102

View File

@@ -1,3 +1,5 @@
from typing import Optional
from presidio_analyzer.predefined_recognizers.nlp_engine_recognizers.spacy_recognizer import ( # noqa: E501
SpacyRecognizer,
)
@@ -12,6 +14,6 @@ class StanzaRecognizer(SpacyRecognizer):
Stanza's interface with spaCy's
"""
def __init__(self, **kwargs):
def __init__(self, name: Optional[str] = None, **kwargs):
self.DEFAULT_EXPLANATION = self.DEFAULT_EXPLANATION.replace("Spacy", "Stanza")
super().__init__(**kwargs)
super().__init__(name=name, **kwargs)

View File

@@ -1,4 +1,5 @@
import logging
from typing import Optional
from presidio_analyzer.predefined_recognizers.nlp_engine_recognizers.spacy_recognizer import ( # noqa: E501
SpacyRecognizer,
@@ -29,8 +30,8 @@ class TransformersRecognizer(SpacyRecognizer):
"PHONE_NUMBER",
]
def __init__(self, **kwargs):
def __init__(self, name: Optional[str] = None, **kwargs):
self.DEFAULT_EXPLANATION = self.DEFAULT_EXPLANATION.replace(
"Spacy", "Transformers"
)
super().__init__(**kwargs)
super().__init__(name=name, **kwargs)

View File

@@ -34,7 +34,8 @@ class AzureHealthDeidRecognizer(RemoteRecognizer):
self,
supported_entities: Optional[List[str]] = None,
supported_language: str = "en",
client: Optional[DeidentificationClient] = None
client: Optional[DeidentificationClient] = None,
name: Optional[str] = None,
):
"""
Wrap PHI detection using Azure Health Data Services de-identification.
@@ -46,7 +47,7 @@ class AzureHealthDeidRecognizer(RemoteRecognizer):
super().__init__(
supported_entities=supported_entities,
supported_language=supported_language,
name="Azure Health Data Services Deidentification",
name=name if name else "Azure Health Data Services Deidentification",
version="1.0.0",
)

View File

@@ -24,7 +24,7 @@ class AzureAILanguageRecognizer(RemoteRecognizer):
supported_language: str = "en",
ta_client: Optional["TextAnalyticsClient"] = None,
azure_ai_key: Optional[str] = None,
azure_ai_endpoint: Optional[str] = None
azure_ai_endpoint: Optional[str] = None,
):
"""
Wrap the PII detection in Azure AI Language.

View File

@@ -59,6 +59,7 @@ class AzureOpenAILangExtractRecognizer(LangExtractRecognizer):
api_key: Optional[str] = None,
api_version: Optional[str] = None,
supported_language: str = "en",
name: str = "Azure OpenAI LangExtract PII",
):
"""
Initialize Azure OpenAI LangExtract recognizer for PII/PHI detection.
@@ -111,14 +112,14 @@ class AzureOpenAILangExtractRecognizer(LangExtractRecognizer):
# Initialize parent class (loads config, sets self.model_id from config)
super().__init__(
config_path=actual_config_path,
name="Azure OpenAI LangExtract PII",
name=name,
supported_language=supported_language,
extract_params={
"extract": {
"fence_output": True,
"use_schema_constraints": False,
},
}
},
)
# Override model_id if provided as parameter (deployment name)

View File

@@ -20,6 +20,7 @@ class OllamaLangExtractRecognizer(LangExtractRecognizer):
config_path: Optional[str] = None,
supported_language: str = "en",
context: Optional[list] = None,
name: str = "Ollama LangExtract PII",
):
"""Initialize Ollama LangExtract recognizer."""
actual_config_path = (
@@ -28,7 +29,7 @@ class OllamaLangExtractRecognizer(LangExtractRecognizer):
super().__init__(
config_path=actual_config_path,
name="Ollama LangExtract PII",
name=name,
supported_language=supported_language,
extract_params={
"extract": {
@@ -40,7 +41,7 @@ class OllamaLangExtractRecognizer(LangExtractRecognizer):
"timeout": 240,
"num_ctx": 8192,
}
}
},
)
model_config = self.config.get("model", {})

View File

@@ -117,12 +117,23 @@ class RecognizerListLoader:
@staticmethod
def get_recognizer_name(recognizer_conf: Union[Dict[str, Any], str]) -> str:
"""Get the name of a recognizer in the configuration.
"""Get the class name for recognizer instantiation.
Uses 'class_name' if present, otherwise 'name'.
Logic:
- If only 'name' exists: Use 'name' as both class name (for instantiation)
and instance name (passed to __init__)
- If 'class_name' exists: Use 'class_name' for instantiation and 'name'
as the instance name (passed to __init__)
:param recognizer_conf: The recognizer configuration.
"""
if isinstance(recognizer_conf, str):
return recognizer_conf
class_name = recognizer_conf.get("class_name")
if class_name:
return class_name
return recognizer_conf["name"]
@staticmethod
@@ -296,12 +307,10 @@ class RecognizerListLoader:
recognizer_instances = []
predefined, custom = RecognizerListLoader._split_recognizers(recognizers)
predefined_to_exclude = {"enabled", "type", "supported_languages", "name"}
# For custom recognizers, we keep 'supported_languages'
# and don't exclude 'supported_entity'
# because PatternRecognizer needs it
custom_to_exclude = {"enabled", "type"}
predefined_to_exclude = {
"enabled", "type", "supported_languages", "class_name"
}
custom_to_exclude = {"enabled", "type", "class_name"}
for recognizer_conf in predefined:
for language_conf in RecognizerListLoader._get_recognizer_languages(
recognizer_conf=recognizer_conf, supported_languages=supported_languages
@@ -318,8 +327,6 @@ class RecognizerListLoader:
recognizer_name=recognizer_name
)
# Prepare kwargs, converting supported_entities
# to supported_entity if needed
kwargs = RecognizerListLoader._prepare_recognizer_kwargs(
new_conf, language_conf, recognizer_cls
)