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

25 statements  

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

1from typing import List, Optional, Tuple 

2 

3from presidio_analyzer import EntityRecognizer, Pattern, PatternRecognizer 

4 

5 

6class CreditCardRecognizer(PatternRecognizer): 

7 """ 

8 Recognize common credit card numbers using regex + checksum. 

9 

10 :param patterns: List of patterns to be used by this recognizer 

11 :param context: List of context words to increase confidence in detection 

12 :param supported_language: Language this recognizer supports 

13 :param supported_entity: The entity this recognizer can detect 

14 :param replacement_pairs: List of tuples with potential replacement values 

15 for different strings to be used during pattern matching. 

16 This can allow a greater variety in input, for example by removing dashes or spaces. 

17 """ 

18 

19 PATTERNS = [ 

20 Pattern( 

21 "All Credit Cards (weak)", 

22 r"\b(?!1\d{12}(?!\d))((4\d{3})|(5[0-5]\d{2})|(6\d{3})|(1\d{3})|(3\d{3}))[- ]?(\d{3,4})[- ]?(\d{3,4})[- ]?(\d{3,5})\b", # noqa: E501 

23 0.3, 

24 ), 

25 ] 

26 

27 CONTEXT = [ 

28 "credit", 

29 "card", 

30 "visa", 

31 "mastercard", 

32 "cc ", 

33 "amex", 

34 "discover", 

35 "jcb", 

36 "diners", 

37 "maestro", 

38 "instapayment", 

39 ] 

40 

41 def __init__( 

42 self, 

43 patterns: Optional[List[Pattern]] = None, 

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

45 supported_language: str = "en", 

46 supported_entity: str = "CREDIT_CARD", 

47 replacement_pairs: Optional[List[Tuple[str, str]]] = None, 

48 name: Optional[str] = None, 

49 ): 

50 self.replacement_pairs = ( 

51 replacement_pairs if replacement_pairs else [("-", ""), (" ", "")] 

52 ) 

53 patterns = patterns if patterns else self.PATTERNS 

54 context = context if context else self.CONTEXT 

55 super().__init__( 

56 supported_entity=supported_entity, 

57 patterns=patterns, 

58 context=context, 

59 supported_language=supported_language, 

60 name=name, 

61 ) 

62 

63 def validate_result(self, pattern_text: str) -> bool: # noqa: D102 

64 sanitized_value = EntityRecognizer.sanitize_value( 

65 pattern_text, self.replacement_pairs 

66 ) 

67 checksum = self.__luhn_checksum(sanitized_value) 

68 

69 return checksum 

70 

71 @staticmethod 

72 def __luhn_checksum(sanitized_value: str) -> bool: 

73 def digits_of(n: str) -> List[int]: 

74 return [int(dig) for dig in str(n)] 

75 

76 digits = digits_of(sanitized_value) 

77 odd_digits = digits[-1::-2] 

78 even_digits = digits[-2::-2] 

79 checksum = sum(odd_digits) 

80 for d in even_digits: 

81 checksum += sum(digits_of(str(d * 2))) 

82 return checksum % 10 == 0