Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/validators/i18n/fr.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

40 statements  

1"""France.""" 

2 

3# standard 

4from functools import lru_cache 

5import re 

6import typing 

7 

8# local 

9from validators.utils import validator 

10 

11 

12@lru_cache 

13def _ssn_pattern(): 

14 """SSN Pattern.""" 

15 return re.compile( 

16 r"^([1,2])" # gender (1=M, 2=F) 

17 r"\s(\d{2})" # year of birth 

18 r"\s(0[1-9]|1[0-2])" # month of birth 

19 r"\s(\d{2,3}|2[A,B])" # department of birth 

20 r"\s(\d{2,3})" # town of birth 

21 r"\s(\d{3})" # registration number 

22 r"(?:\s(\d{2}))?$", # control key (may or may not be provided) 

23 re.VERBOSE, 

24 ) 

25 

26 

27@validator 

28def fr_department(value: typing.Union[str, int]): 

29 """Validate a french department number. 

30 

31 Examples: 

32 >>> fr_department(20) # can be an integer 

33 ValidationError(func=fr_department, args={'value': 20}) 

34 >>> fr_department("20") 

35 ValidationError(func=fr_department, args={'value': '20'}) 

36 >>> fr_department("971") # Guadeloupe 

37 True 

38 >>> fr_department("00") 

39 ValidationError(func=fr_department, args={'value': '00'}) 

40 >>> fr_department('2A') # Corsica 

41 True 

42 >>> fr_department('2B') 

43 True 

44 >>> fr_department('2C') 

45 ValidationError(func=fr_department, args={'value': '2C'}) 

46 

47 Args: 

48 value: 

49 French department number to validate. 

50 

51 Returns: 

52 (Literal[True]): If `value` is a valid french department number. 

53 (ValidationError): If `value` is an invalid french department number. 

54 """ 

55 if not value: 

56 return False 

57 if isinstance(value, str): 

58 if value in ("2A", "2B"): # Corsica 

59 return True 

60 try: 

61 value = int(value) 

62 except ValueError: 

63 return False 

64 return 1 <= value <= 19 or 21 <= value <= 95 or 971 <= value <= 976 # Overseas departments 

65 

66 

67@validator 

68def fr_ssn(value: str): 

69 """Validate a french Social Security Number. 

70 

71 Each french citizen has a distinct Social Security Number. 

72 For more information see [French Social Security Number][1] (sadly unavailable in english). 

73 

74 [1]: https://fr.wikipedia.org/wiki/Num%C3%A9ro_de_s%C3%A9curit%C3%A9_sociale_en_France 

75 

76 Examples: 

77 >>> fr_ssn('1 84 12 76 451 089 46') 

78 True 

79 >>> fr_ssn('1 84 12 76 451 089') # control key is optional 

80 True 

81 >>> fr_ssn('3 84 12 76 451 089 46') # wrong gender number 

82 ValidationError(func=fr_ssn, args={'value': '3 84 12 76 451 089 46'}) 

83 >>> fr_ssn('1 84 12 76 451 089 47') # wrong control key 

84 ValidationError(func=fr_ssn, args={'value': '1 84 12 76 451 089 47'}) 

85 

86 Args: 

87 value: 

88 French Social Security Number string to validate. 

89 

90 Returns: 

91 (Literal[True]): If `value` is a valid french Social Security Number. 

92 (ValidationError): If `value` is an invalid french Social Security Number. 

93 """ 

94 if not value: 

95 return False 

96 matched = re.match(_ssn_pattern(), value) 

97 if not matched: 

98 return False 

99 groups = list(matched.groups()) 

100 control_key = groups[-1] 

101 department = groups[3] 

102 if department != "99" and not fr_department(department): 

103 # 99 stands for foreign born people 

104 return False 

105 if control_key is None: 

106 # no control key provided, no additional check needed 

107 return True 

108 if len(department) == len(groups[4]): 

109 # if the department number is 3 digits long (overseas departments), 

110 # the town number must be 2 digits long 

111 # and vice versa 

112 return False 

113 if department in ("2A", "2B"): 

114 # Corsica's department numbers are not in the same range as the others 

115 # thus 2A and 2B are replaced by 19 and 18 respectively to compute the control key 

116 groups[3] = "19" if department == "2A" else "18" 

117 # the control key is valid if it is equal to 97 - (the first 13 digits modulo 97) 

118 digits = int("".join(groups[:-1])) 

119 return int(control_key) == (97 - (digits % 97))