Coverage for presidio_analyzer / predefined_recognizers / generic / phone_recognizer.py: 100%

34 statements  

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

1from typing import List, Optional 

2 

3import phonenumbers 

4from phonenumbers.phonenumberutil import NumberParseException 

5 

6from presidio_analyzer import ( 

7 AnalysisExplanation, 

8 EntityRecognizer, 

9 LocalRecognizer, 

10 RecognizerResult, 

11) 

12from presidio_analyzer.nlp_engine import NlpArtifacts 

13 

14 

15class PhoneRecognizer(LocalRecognizer): 

16 """Recognize multi-regional phone numbers. 

17 

18 Using python-phonenumbers, along with fixed and regional context words. 

19 :param context: Base context words for enhancing the assurance scores. 

20 :param supported_language: Language this recognizer supports 

21 :param supported_regions: The regions for phone number matching and validation 

22 :param leniency: The strictness level of phone number formats. 

23 Accepts values from 0 to 3, where 0 is the lenient and 3 is the most strictest. 

24 """ 

25 

26 SCORE = 0.4 

27 CONTEXT = ["phone", "number", "telephone", "cell", "cellphone", "mobile", "call"] 

28 DEFAULT_SUPPORTED_REGIONS = ("US", "UK", "DE", "FE", "IL", "IN", "CA", "BR") 

29 

30 def __init__( 

31 self, 

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

33 supported_language: str = "en", 

34 # For all regions, use phonenumbers.SUPPORTED_REGIONS 

35 supported_regions=DEFAULT_SUPPORTED_REGIONS, 

36 leniency: Optional[int] = 1, 

37 name: Optional[str] = None, 

38 ): 

39 context = context if context else self.CONTEXT 

40 self.supported_regions = supported_regions 

41 self.leniency = leniency 

42 super().__init__( 

43 supported_entities=self.get_supported_entities(), 

44 supported_language=supported_language, 

45 context=context, 

46 name=name, 

47 ) 

48 

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

50 pass 

51 

52 def get_supported_entities(self): # noqa: D102 

53 return ["PHONE_NUMBER"] 

54 

55 def analyze( 

56 self, text: str, entities: List[str], nlp_artifacts: NlpArtifacts = None 

57 ) -> List[RecognizerResult]: 

58 """Analyzes text to detect phone numbers using python-phonenumbers. 

59 

60 Iterates over entities, fetching regions, then matching regional 

61 phone numbers patterns against the text. 

62 :param text: Text to be analyzed 

63 :param entities: Entities this recognizer can detect 

64 :param nlp_artifacts: Additional metadata from the NLP engine 

65 :return: List of phone numbers RecognizerResults 

66 """ 

67 results = [] 

68 for region in self.supported_regions: 

69 for match in phonenumbers.PhoneNumberMatcher( 

70 text, region, leniency=self.leniency 

71 ): 

72 try: 

73 parsed_number = phonenumbers.parse(text[match.start : match.end]) 

74 region = phonenumbers.region_code_for_number(parsed_number) 

75 results += [ 

76 self._get_recognizer_result(match, text, region, nlp_artifacts) 

77 ] 

78 except NumberParseException: 

79 results += [ 

80 self._get_recognizer_result(match, text, region, nlp_artifacts) 

81 ] 

82 

83 return EntityRecognizer.remove_duplicates(results) 

84 

85 def _get_recognizer_result(self, match, text, region, nlp_artifacts): 

86 result = RecognizerResult( 

87 entity_type="PHONE_NUMBER", 

88 start=match.start, 

89 end=match.end, 

90 score=self.SCORE, 

91 analysis_explanation=self._get_analysis_explanation(region), 

92 recognition_metadata={ 

93 RecognizerResult.RECOGNIZER_NAME_KEY: self.name, 

94 RecognizerResult.RECOGNIZER_IDENTIFIER_KEY: self.id, 

95 }, 

96 ) 

97 

98 return result 

99 

100 def _get_analysis_explanation(self, region): 

101 return AnalysisExplanation( 

102 recognizer=PhoneRecognizer.__name__, 

103 original_score=self.SCORE, 

104 textual_explanation=f"Recognized as {region} region phone number, " 

105 f"using PhoneRecognizer", 

106 )