Coverage for presidio_analyzer / recognizer_registry / recognizer_registry_provider.py: 98%
63 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1from __future__ import annotations
3import logging
4import warnings
5from pathlib import Path
6from typing import Any, Dict, List, Optional, Union
8from presidio_analyzer import EntityRecognizer
9from presidio_analyzer.input_validation import ConfigurationValidator
10from presidio_analyzer.nlp_engine import NlpEngine
11from presidio_analyzer.predefined_recognizers import SpacyRecognizer
12from presidio_analyzer.recognizer_registry import RecognizerRegistry
13from presidio_analyzer.recognizer_registry.recognizers_loader_utils import (
14 RecognizerConfigurationLoader,
15 RecognizerListLoader,
16)
18logger = logging.getLogger("presidio-analyzer")
21class RecognizerRegistryProvider:
22 r"""
23 Utility class for loading Recognizer Registry.
25 Use this class to load recognizer registry from a yaml file
27 :param conf_file: Path to yaml file containing registry configuration
28 :param registry_configuration: Dict containing registry configuration
29 :example:
30 {
31 "supported_languages": ["de", "es"],
32 "recognizers": [
33 {
34 "name": "Zip code Recognizer",
35 "supported_language": "en",
36 "patterns": [
37 {
38 "name": "zip code (weak)",
39 "regex": "(\\b\\d{5}(?:\\-\\d{4})?\\b)",
40 "score": 0.01,
41 }
42 ],
43 "context": ["zip", "code"],
44 "supported_entity": "ZIP",
45 }
46 ]
47 }
48 """
50 def __init__(
51 self,
52 conf_file: Optional[Union[Path, str]] = None,
53 registry_configuration: Optional[Dict] = None,
54 nlp_engine: Optional[NlpEngine] = None,
55 ):
56 self.configuration = RecognizerConfigurationLoader.get(
57 conf_file=conf_file, registry_configuration=registry_configuration
58 )
60 self.configuration = (
61 ConfigurationValidator.validate_recognizer_registry_configuration(
62 self.configuration
63 )
64 )
65 self.nlp_engine = nlp_engine
67 def create_recognizer_registry(self) -> RecognizerRegistry:
68 """Create a recognizer registry according to configuration loaded previously."""
69 supported_languages = self.configuration.get("supported_languages")
70 global_regex_flags = self.configuration.get("global_regex_flags")
71 recognizers_conf = self.configuration.get("recognizers")
72 recognizers = RecognizerListLoader.get(
73 recognizers_conf,
74 supported_languages,
75 global_regex_flags,
76 )
78 recognizers = list(recognizers)
80 self.__update_based_on_nlp_recognizer_conf(
81 recognizers, recognizers_conf, supported_languages
82 )
84 registry = RecognizerRegistry(
85 recognizers=recognizers,
86 supported_languages=supported_languages,
87 global_regex_flags=global_regex_flags,
88 )
90 return registry
92 def __update_based_on_nlp_recognizer_conf(
93 self,
94 recognizers: List[EntityRecognizer],
95 recognizers_conf: Optional[Dict],
96 supported_languages: List[str],
97 ) -> None:
98 """Update the list of recognizers based on the NLP recognizer configuration.
100 The method adds the NLP recognizer to the list of recognizers
101 if it is not already present,
102 or removes it if it is not enabled in the configuration.
103 Furthermore, it checks if there are
104 any inconsistencies in configuration. For example:
105 - Multiple enabled NLP recognizers in the configuration for one language.
106 - The NLP recognizer in the configuration does not match the Nlp Engine.
108 :param recognizers: List of recognizers to update.
109 :param recognizers_conf: Configuration of the recognizers from the YAML file
110 :param supported_languages: List of supported languages.
112 :raises ValueError: If there are multiple enabled NLP recognizers
113 in the configuration.
114 :raises ValueError: If the NLP recognizer
115 in the configuration does not match the Nlp Engine.
116 """
117 nlp_engine = self.nlp_engine
119 if not nlp_engine:
120 return
122 for language in nlp_engine.get_supported_languages():
123 self.__update_based_on_nlp_recognizer_conf_and_lang(
124 recognizers=recognizers, nlp_engine=nlp_engine, language=language
125 )
126 self.__remove_disabled_nlp_recognizers(
127 recognizers=recognizers,
128 recognizers_conf=recognizers_conf,
129 language=language,
130 )
132 @staticmethod
133 def __update_based_on_nlp_recognizer_conf_and_lang(
134 recognizers: List[EntityRecognizer],
135 nlp_engine: NlpEngine,
136 language: str,
137 ):
138 """
139 Update the list of recognizers with nlp recognizers.
141 Update based on the NLP recognizer configuration for a specific language.
142 """
144 nlp_recognizers = [
145 rec
146 for rec in recognizers
147 if isinstance(rec, SpacyRecognizer) and rec.supported_language == language
148 ]
150 # Case 1: NLP recognizer is not in the list of recognizers
151 if not nlp_recognizers:
152 warning_text = (
153 f"NLP recognizer (e.g. SpacyRecognizer, StanzaRecognizer) "
154 f"is not in the list of recognizers "
155 f"for language {language}. "
156 f"Adding the default recognizer to the list."
157 f"If you wish to remove the NLP recognizer, "
158 f"define it as `enabled=false`."
159 )
160 logger.warning(warning_text)
161 warnings.warn(warning_text)
162 if nlp_engine:
163 nlp_recognizer_cls = RecognizerRegistry.get_nlp_recognizer(
164 nlp_engine=nlp_engine
165 )
166 recognizers.append(
167 nlp_recognizer_cls(
168 supported_language=language,
169 supported_entities=nlp_engine.get_supported_entities(),
170 )
171 )
172 else:
173 recognizers.append(SpacyRecognizer(supported_language=language))
174 return
176 # Case 2: There are multiple NLP recognizers for this language, throw error
177 if len(nlp_recognizers) > 1:
178 raise ValueError(
179 f"Multiple NLP recognizers for language {language} "
180 f"found in the configuration. "
181 f"Please remove the duplicates."
182 )
184 # Case 3: There is a mismatch between the NLP Engine and the NLP Recognizer
185 nlp_recognizer = nlp_recognizers[0]
186 expected_nlp_recognizer_cls = RecognizerRegistry.get_nlp_recognizer(nlp_engine)
187 if nlp_recognizer.__class__ != expected_nlp_recognizer_cls:
188 raise ValueError(
189 f"There is a mismatch between the NLP Engine defined "
190 f"({nlp_engine.__class__.__name__}),"
191 f"and the configured NLP recognizer "
192 f"({nlp_recognizer.__class__.__name__})."
193 f"Make sure the NLP recognizer is aligned with the "
194 f"NLP engine and that all others are removed/disabled."
195 )
197 @staticmethod
198 def __remove_disabled_nlp_recognizers(
199 recognizers: List[EntityRecognizer],
200 recognizers_conf: Dict[str, Any],
201 language: str,
202 ):
203 """
204 Remove recognizers that are disabled in the configuration.
206 Goes through the recognizer conf provided by the user,
207 and checks if a recognizer for a given language is disabled.
208 If yes, it removes it from the recognizers list
209 (as some are not removed in the previous step).
210 """
212 disabled = [
213 rec_conf
214 for rec_conf in recognizers_conf
215 if not RecognizerListLoader.is_recognizer_enabled(rec_conf)
216 ]
218 disabled_rec_names = [
219 RecognizerListLoader.get_recognizer_name(rec) for rec in disabled
220 ]
222 if not disabled:
223 return
225 disabled_rec_classes = [
226 cls
227 for cls in RecognizerListLoader.get_all_existing_recognizers()
228 if cls.__name__ in disabled_rec_names
229 ]
231 lang_recognizers = [
232 rec for rec in recognizers if rec.supported_language == language
233 ]
235 for recognizer in lang_recognizers:
236 if type(recognizer) in disabled_rec_classes:
237 recognizers.remove(recognizer)
238 logger.info(
239 f"Disabled {recognizer.__class__.__name__} "
240 f"recognizer for language {language}."
241 )