Coverage for presidio_analyzer / predefined_recognizers / nlp_engine_recognizers / spacy_recognizer.py: 95%

40 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-29 09:03 +0000

1import logging 

2import warnings 

3from typing import List, Optional, Set, Tuple 

4 

5from presidio_analyzer import ( 

6 AnalysisExplanation, 

7 LocalRecognizer, 

8 RecognizerResult, 

9) 

10 

11logger = logging.getLogger("presidio-analyzer") 

12 

13 

14class SpacyRecognizer(LocalRecognizer): 

15 """ 

16 Recognize PII entities using a spaCy NLP model. 

17 

18 Since the spaCy pipeline is ran by the AnalyzerEngine/SpacyNlpEngine, 

19 this recognizer only extracts the entities from the NlpArtifacts 

20 and returns them. 

21 

22 """ 

23 

24 ENTITIES = ["DATE_TIME", "NRP", "LOCATION", "PERSON", "ORGANIZATION"] 

25 

26 DEFAULT_EXPLANATION = "Identified as {} by Spacy's Named Entity Recognition" 

27 

28 # deprecated, use MODEL_TO_PRESIDIO_MAPPING in NerModelConfiguration instead 

29 CHECK_LABEL_GROUPS = [ 

30 ({"LOCATION"}, {"GPE", "LOC"}), 

31 ({"PERSON", "PER"}, {"PERSON", "PER"}), 

32 ({"DATE_TIME"}, {"DATE", "TIME"}), 

33 ({"NRP"}, {"NORP"}), 

34 ({"ORGANIZATION"}, {"ORG"}), 

35 ] 

36 

37 def __init__( 

38 self, 

39 supported_language: str = "en", 

40 supported_entities: Optional[List[str]] = None, 

41 ner_strength: float = 0.85, 

42 default_explanation: Optional[str] = None, 

43 check_label_groups: Optional[List[Tuple[Set, Set]]] = None, 

44 context: Optional[List[str]] = None, 

45 name: Optional[str] = None, 

46 ): 

47 """Initialize the SpaCy recognizer. 

48 

49 :param supported_language: Language this recognizer supports 

50 :param supported_entities: The entities this recognizer can detect 

51 :param ner_strength: Default confidence for NER prediction 

52 :param check_label_groups: (DEPRECATED) Tuple containing Presidio entity names 

53 :param default_explanation: Default explanation for the results when using return_decision_process=True 

54 """ # noqa: E501 

55 

56 self.ner_strength = ner_strength 

57 if check_label_groups: 

58 warnings.warn( 

59 "check_label_groups is deprecated and isn't used;" 

60 "entities are mapped in NerModelConfiguration", 

61 DeprecationWarning, 

62 2, 

63 ) 

64 

65 self.default_explanation = ( 

66 default_explanation if default_explanation else self.DEFAULT_EXPLANATION 

67 ) 

68 supported_entities = supported_entities if supported_entities else self.ENTITIES 

69 super().__init__( 

70 supported_entities=supported_entities, 

71 supported_language=supported_language, 

72 context=context, 

73 name=name, 

74 ) 

75 

76 def load(self) -> None: # noqa: D102 

77 # no need to load anything as the analyze method already receives 

78 # preprocessed nlp artifacts 

79 pass 

80 

81 def build_explanation( 

82 self, original_score: float, explanation: str 

83 ) -> AnalysisExplanation: 

84 """ 

85 Create explanation for why this result was detected. 

86 

87 :param original_score: Score given by this recognizer 

88 :param explanation: Explanation string 

89 :return: 

90 """ 

91 explanation = AnalysisExplanation( 

92 recognizer=self.name, 

93 original_score=original_score, 

94 textual_explanation=explanation, 

95 ) 

96 return explanation 

97 

98 def analyze(self, text: str, entities, nlp_artifacts=None): # noqa: D102 

99 results = [] 

100 if not nlp_artifacts: 

101 logger.warning("Skipping SpaCy, nlp artifacts not provided...") 

102 return results 

103 

104 ner_entities = nlp_artifacts.entities 

105 ner_scores = nlp_artifacts.scores 

106 

107 for ner_entity, ner_score in zip(ner_entities, ner_scores): 

108 if ( 

109 ner_entity.label_ not in entities 

110 or ner_entity.label_ not in self.supported_entities 

111 ): 

112 logger.debug( 

113 f"Skipping entity {ner_entity.label_} " 

114 f"as it is not in the supported entities list" 

115 ) 

116 continue 

117 

118 textual_explanation = self.DEFAULT_EXPLANATION.format(ner_entity.label_) 

119 explanation = self.build_explanation(ner_score, textual_explanation) 

120 spacy_result = RecognizerResult( 

121 entity_type=ner_entity.label_, 

122 start=ner_entity.start_char, 

123 end=ner_entity.end_char, 

124 score=ner_score, 

125 analysis_explanation=explanation, 

126 recognition_metadata={ 

127 RecognizerResult.RECOGNIZER_NAME_KEY: self.name, 

128 RecognizerResult.RECOGNIZER_IDENTIFIER_KEY: self.id, 

129 }, 

130 ) 

131 results.append(spacy_result) 

132 

133 return results 

134 

135 @staticmethod 

136 def __check_label( 

137 entity: str, label: str, check_label_groups: Tuple[Set, Set] 

138 ) -> bool: 

139 raise DeprecationWarning("__check_label is deprecated")