Coverage for presidio_analyzer / batch_analyzer_engine.py: 98%
52 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
1import logging
2from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
4from presidio_analyzer import AnalyzerEngine, DictAnalyzerResult, RecognizerResult
5from presidio_analyzer.nlp_engine import NlpArtifacts
7logger = logging.getLogger("presidio-analyzer")
10class BatchAnalyzerEngine:
11 """
12 Batch analysis of documents (tables, lists, dicts).
14 Wrapper class to run Presidio Analyzer Engine on multiple values,
15 either lists/iterators of strings, or dictionaries.
17 :param analyzer_engine: AnalyzerEngine instance to use
18 for handling the values in those collections.
19 """
21 def __init__(self, analyzer_engine: Optional[AnalyzerEngine] = None):
22 self.analyzer_engine = analyzer_engine
23 if not analyzer_engine:
24 self.analyzer_engine = AnalyzerEngine()
26 def analyze_iterator(
27 self,
28 texts: Iterable[Union[str, bool, float, int]],
29 language: str,
30 batch_size: int = 1,
31 n_process: int = 1,
32 **kwargs,
33 ) -> List[List[RecognizerResult]]:
34 """
35 Analyze an iterable of strings.
37 :param texts: An list containing strings to be analyzed.
38 :param language: Input language
39 :param batch_size: Batch size to process in a single iteration
40 :param n_process: Number of processors to use. Defaults to `1`
41 :param kwargs: Additional parameters for the `AnalyzerEngine.analyze` method.
42 (default value depends on the nlp engine implementation)
43 """
45 # validate types
46 texts = self._validate_types(texts)
48 # Process the texts as batch for improved performance
49 nlp_artifacts_batch: Iterator[Tuple[str, NlpArtifacts]] = (
50 self.analyzer_engine.nlp_engine.process_batch(
51 texts=texts,
52 language=language,
53 batch_size=batch_size,
54 n_process=n_process,
55 )
56 )
58 list_results = []
59 for text, nlp_artifacts in nlp_artifacts_batch:
60 results = self.analyzer_engine.analyze(
61 text=str(text), nlp_artifacts=nlp_artifacts, language=language, **kwargs
62 )
64 list_results.append(results)
66 return list_results
68 def analyze_dict(
69 self,
70 input_dict: Dict[str, Union[Any, Iterable[Any]]],
71 language: str,
72 keys_to_skip: Optional[List[str]] = None,
73 batch_size: int = 1,
74 n_process: int = 1,
75 **kwargs,
76 ) -> Iterator[DictAnalyzerResult]:
77 """
78 Analyze a dictionary of keys (strings) and values/iterable of values.
80 Non-string values are returned as is.
82 :param input_dict: The input dictionary for analysis
83 :param language: Input language
84 :param keys_to_skip: Keys to ignore during analysis
85 :param batch_size: Batch size to process in a single iteration
86 :param n_process: Number of processors to use. Defaults to `1`
88 :param kwargs: Additional keyword arguments
89 for the `AnalyzerEngine.analyze` method.
90 Use this to pass arguments to the analyze method,
91 such as `ad_hoc_recognizers`, `context`, `return_decision_process`.
92 See `AnalyzerEngine.analyze` for the full list.
93 """
95 context = []
96 if "context" in kwargs:
97 context = kwargs["context"]
98 del kwargs["context"]
100 if not keys_to_skip:
101 keys_to_skip = []
103 for key, value in input_dict.items():
104 if not value or key in keys_to_skip:
105 yield DictAnalyzerResult(key=key, value=value, recognizer_results=[])
106 continue # skip this key as requested
108 # Add the key as an additional context
109 specific_context = context[:]
110 specific_context.append(key)
112 if type(value) in (str, int, bool, float):
113 results: List[RecognizerResult] = self.analyzer_engine.analyze(
114 text=str(value), language=language, context=[key], **kwargs
115 )
116 elif isinstance(value, dict):
117 new_keys_to_skip = self._get_nested_keys_to_skip(key, keys_to_skip)
118 results = self.analyze_dict(
119 input_dict=value,
120 language=language,
121 context=specific_context,
122 keys_to_skip=new_keys_to_skip,
123 **kwargs,
124 )
125 elif isinstance(value, Iterable):
126 # Recursively iterate nested dicts
128 results: List[List[RecognizerResult]] = self.analyze_iterator(
129 texts=value,
130 language=language,
131 context=specific_context,
132 n_process=n_process,
133 batch_size=batch_size,
134 **kwargs,
135 )
136 else:
137 raise ValueError(f"type {type(value)} is unsupported.")
139 yield DictAnalyzerResult(key=key, value=value, recognizer_results=results)
141 @staticmethod
142 def _validate_types(value_iterator: Iterable[Any]) -> Iterator[Any]:
143 for val in value_iterator:
144 if val and type(val) not in (int, float, bool, str):
145 err_msg = (
146 "Analyzer.analyze_iterator only works "
147 "on primitive types (int, float, bool, str). "
148 "Lists of objects are not yet supported."
149 )
150 logger.error(err_msg)
151 raise ValueError(err_msg)
152 yield val
154 @staticmethod
155 def _get_nested_keys_to_skip(key, keys_to_skip):
156 new_keys_to_skip = [
157 k.replace(f"{key}.", "") for k in keys_to_skip if k.startswith(key)
158 ]
159 return new_keys_to_skip