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
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1from typing import List, Optional
3import phonenumbers
4from phonenumbers.phonenumberutil import NumberParseException
6from presidio_analyzer import (
7 AnalysisExplanation,
8 EntityRecognizer,
9 LocalRecognizer,
10 RecognizerResult,
11)
12from presidio_analyzer.nlp_engine import NlpArtifacts
15class PhoneRecognizer(LocalRecognizer):
16 """Recognize multi-regional phone numbers.
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 """
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")
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 )
49 def load(self) -> None: # noqa: D102
50 pass
52 def get_supported_entities(self): # noqa: D102
53 return ["PHONE_NUMBER"]
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.
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 ]
83 return EntityRecognizer.remove_duplicates(results)
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 )
98 return result
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 )