Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/faker/providers/barcode/en_US/__init__.py: 18%

65 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import re 

2 

3from itertools import product 

4from typing import Dict, Optional, Pattern 

5 

6from .. import PrefixType 

7from .. import Provider as BarcodeProvider 

8 

9 

10class Provider(BarcodeProvider): 

11 """Implement barcode provider for ``en_US`` locale. 

12 

13 Sources: 

14 

15 - https://gs1.org/standards/id-keys/company-prefix 

16 """ 

17 

18 local_prefixes = ( 

19 *product((0,), range(10)), 

20 *product((1,), range(4)), 

21 ) 

22 

23 upc_e_base_pattern: Pattern = re.compile(r"^\d{6}$") 

24 upc_ae_pattern1: Pattern = re.compile( 

25 r"^(?P<number_system_digit>[01])" # The first digit must be 0 or 1 

26 r"(?=\d{11}$)" # followed by 11 digits of which 

27 r"(?P<mfr_code>\d{2})" # the first 2 digits make up the manufacturer code, 

28 r"(?:(?P<extra>[012])0{4})" # if immediately followed by 00000, 10000, or 20000, 

29 r"(?P<product_code>\d{3})" # a 3-digit product code, 

30 r"(?P<check_digit>\d)$", # and finally a check digit. 

31 ) 

32 upc_ae_pattern2: Pattern = re.compile( 

33 r"^(?P<number_system_digit>[01])" # The first digit must be 0 or 1 

34 r"(?=\d{11}$)" # followed by 11 digits of which 

35 r"(?P<mfr_code>\d{3,4}?)" # the first 3 or 4 digits make up the manufacturer code, 

36 r"(?:0{5})" # if immediately followed by 00000, 

37 r"(?P<product_code>\d{1,2})" # a 2-digit or single digit product code, 

38 r"(?P<check_digit>\d)$", # and finally a check digit. 

39 ) 

40 upc_ae_pattern3: Pattern = re.compile( 

41 r"^(?P<number_system_digit>[01])" # The first digit must be 0 or 1 

42 r"(?=\d{11}$)" # followed by 11 digits of which 

43 r"(?P<mfr_code>\d{5})" # the first 5 digits make up the manufacturer code, 

44 r"(?:0{4}(?P<extra>[5-9]))" # if immediately followed by 0000 and a 5, 6, 7, 8, or 9, 

45 r"(?P<check_digit>\d)$", # and finally a check digit. 

46 ) 

47 

48 def ean13(self, prefixes: PrefixType = (), leading_zero: Optional[bool] = None) -> str: 

49 """Generate an EAN-13 barcode. 

50 

51 If ``leading_zero`` is ``True``, the leftmost digit of the barcode will 

52 be set to ``0``. If ``False``, the leftmost digit cannot be ``0``. If 

53 ``None`` (default), the leftmost digit can be any digit. 

54 

55 If a value for ``prefixes`` is specified, the result will begin with one 

56 of the sequences in ``prefixes`` and will ignore ``leading_zero``. 

57 

58 This method uses the standard barcode provider's |ean13| under the 

59 hood with the ``prefixes`` argument set to the correct value to attain 

60 the behavior described above. 

61 

62 .. note:: 

63 EAN-13 barcode that starts with a zero can be converted to UPC-A 

64 by dropping the leading zero. This may cause problems with readers 

65 that treat all of these code as UPC-A codes and drop the first digit 

66 when reading it. 

67 

68 You can set the argument ``prefixes`` ( or ``leading_zero`` for 

69 convenience) explicitly to avoid or to force the generated barcode to 

70 start with a zero. You can also generate actual UPC-A barcode with 

71 |EnUsBarcodeProvider.upc_a|. 

72 

73 :sample: 

74 :sample: leading_zero=False 

75 :sample: leading_zero=True 

76 :sample: prefixes=('00',) 

77 :sample: prefixes=('45', '49') 

78 """ 

79 if not prefixes: 

80 if leading_zero is True: 

81 prefixes = ((0,),) 

82 elif leading_zero is False: 

83 prefixes = ((self.random_int(1, 9),),) 

84 

85 return super().ean13(prefixes=prefixes) 

86 

87 def _convert_upc_a2e(self, upc_a: str) -> str: 

88 """Convert a 12-digit UPC-A barcode to its 8-digit UPC-E equivalent. 

89 

90 .. warning:: 

91 Not all UPC-A barcodes can be converted. 

92 """ 

93 if not isinstance(upc_a, str): 

94 raise TypeError("`upc_a` is not a string") 

95 m1 = self.upc_ae_pattern1.match(upc_a) 

96 m2 = self.upc_ae_pattern2.match(upc_a) 

97 m3 = self.upc_ae_pattern3.match(upc_a) 

98 if not any([m1, m2, m3]): 

99 raise ValueError("`upc_a` has an invalid value") 

100 upc_e_template = "{number_system_digit}{mfr_code}{product_code}{extra}{check_digit}" 

101 if m1: 

102 upc_e = upc_e_template.format(**m1.groupdict()) 

103 elif m2: 

104 groupdict: Dict[str, str] = m2.groupdict() 

105 mfr_code = groupdict.get("mfr_code") or "" 

106 groupdict["extra"] = str(len(mfr_code)) 

107 upc_e = upc_e_template.format(**groupdict) 

108 elif m3: 

109 groupdict = m3.groupdict() 

110 groupdict["product_code"] = "" 

111 upc_e = upc_e_template.format(**groupdict) 

112 return upc_e 

113 

114 def _upc_ae(self, base: Optional[str] = None, number_system_digit: Optional[int] = None) -> str: 

115 """Create a 12-digit UPC-A barcode that can be converted to UPC-E. 

116 

117 The expected value of ``base`` is a 6-digit string. If any other value 

118 is provided, this method will use a random 6-digit string instead. 

119 

120 The expected value of ``number_system_digit`` is the integer ``0`` or 

121 ``1``. If any other value is provided, this method will randomly choose 

122 from the two. 

123 

124 Please also view notes on |EnUsBarcodeProvider.upc_a| and 

125 |EnUsBarcodeProvider.upc_e| for more details. 

126 """ 

127 base_ = ( 

128 [int(x) for x in base] 

129 if isinstance(base, str) and self.upc_e_base_pattern.match(base) 

130 else [self.random_int(0, 9) for _ in range(6)] 

131 ) 

132 if number_system_digit not in [0, 1]: 

133 number_system_digit = self.random_int(0, 1) 

134 

135 if base_[-1] <= 2: 

136 code = base_[:2] + base_[-1:] + [0] * 4 + base_[2:-1] 

137 elif base_[-1] <= 4: 

138 code = base_[: base_[-1]] + [0] * 5 + base_[base_[-1] : -1] 

139 else: 

140 code = base_[:5] + [0] * 4 + base_[-1:] 

141 

142 code.insert(0, number_system_digit) 

143 weights = [3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3] 

144 weighted_sum = sum(x * y for x, y in zip(code, weights)) 

145 check_digit = (10 - weighted_sum % 10) % 10 

146 code.append(check_digit) 

147 return "".join(str(x) for x in code) 

148 

149 def upc_a( 

150 self, 

151 upc_ae_mode: bool = False, 

152 base: Optional[str] = None, 

153 number_system_digit: Optional[int] = None, 

154 ) -> str: 

155 """Generate a 12-digit UPC-A barcode. 

156 

157 The value of ``upc_ae_mode`` controls how barcodes will be generated. If 

158 ``False`` (default), barcodes are not guaranteed to have a UPC-E 

159 equivalent. In this mode, the method uses |EnUsBarcodeProvider.ean13| 

160 under the hood, and the values of ``base`` and ``number_system_digit`` 

161 will be ignored. 

162 

163 If ``upc_ae_mode`` is ``True``, the resulting barcodes are guaranteed to 

164 have a UPC-E equivalent, and the values of ``base`` and 

165 ``number_system_digit`` will be used to control what is generated. 

166 

167 Under this mode, ``base`` is expected to have a 6-digit string value. If 

168 any other value is supplied, a random 6-digit string will be used 

169 instead. As for ``number_system_digit``, the expected value is a ``0`` 

170 or a ``1``. If any other value is provided, this method will randomly 

171 choose from the two. 

172 

173 .. important:: 

174 When ``upc_ae_mode`` is enabled, you might encounter instances where 

175 different values of ``base`` (e.g. ``'120003'`` and ``'120004'``) 

176 produce the same UPC-A barcode. This is normal, and the reason lies 

177 within the whole conversion process. To learn more about this and 

178 what ``base`` and ``number_system_digit`` actually represent, please 

179 refer to |EnUsBarcodeProvider.upc_e|. 

180 

181 :sample: 

182 :sample: upc_ae_mode=True, number_system_digit=0 

183 :sample: upc_ae_mode=True, number_system_digit=1 

184 :sample: upc_ae_mode=True, base='123456', number_system_digit=0 

185 :sample: upc_ae_mode=True, base='120003', number_system_digit=0 

186 :sample: upc_ae_mode=True, base='120004', number_system_digit=0 

187 """ 

188 if upc_ae_mode is True: 

189 return self._upc_ae(base=base, number_system_digit=number_system_digit) 

190 else: 

191 ean13 = self.ean13(leading_zero=True) 

192 return ean13[1:] 

193 

194 def upc_e( 

195 self, 

196 base: Optional[str] = None, 

197 number_system_digit: Optional[int] = None, 

198 safe_mode: bool = True, 

199 ) -> str: 

200 """Generate an 8-digit UPC-E barcode. 

201 

202 UPC-E barcodes can be expressed in 6, 7, or 8-digit formats, but this 

203 method uses the 8 digit format, since it is trivial to convert to the 

204 other two formats. The first digit (starting from the left) is 

205 controlled by ``number_system_digit``, and it can only be a ``0`` or a 

206 ``1``. The last digit is the check digit that is inherited from the 

207 UPC-E barcode's UPC-A equivalent. The middle six digits are collectively 

208 referred to as the ``base`` (for a lack of a better term). 

209 

210 On that note, this method uses ``base`` and ``number_system_digit`` to 

211 first generate a UPC-A barcode for the check digit, and what happens 

212 next depends on the value of ``safe_mode``. The argument ``safe_mode`` 

213 exists, because there are some UPC-E values that share the same UPC-A 

214 equivalent. For example, any UPC-E barcode of the form ``abc0000d``, 

215 ``abc0003d``, and ``abc0004d`` share the same UPC-A value 

216 ``abc00000000d``, but that UPC-A value will only convert to ``abc0000d`` 

217 because of (a) how UPC-E is just a zero-suppressed version of UPC-A and 

218 (b) the rules around the conversion. 

219 

220 If ``safe_mode`` is ``True`` (default), this method performs another set 

221 of conversions to guarantee that the UPC-E barcodes generated can be 

222 converted to UPC-A, and that UPC-A barcode can be converted back to the 

223 original UPC-E barcode. Using the example above, even if the bases 

224 ``120003`` or ``120004`` are used, the resulting UPC-E barcode will 

225 always use the base ``120000``. 

226 

227 If ``safe_mode`` is ``False``, then the ``number_system_digit``, 

228 ``base``, and the computed check digit will just be concatenated 

229 together to produce the UPC-E barcode, and attempting to convert the 

230 barcode to UPC-A and back again to UPC-E will exhibit the behavior 

231 described above. 

232 

233 :sample: 

234 :sample: base='123456' 

235 :sample: base='123456', number_system_digit=0 

236 :sample: base='123456', number_system_digit=1 

237 :sample: base='120000', number_system_digit=0 

238 :sample: base='120003', number_system_digit=0 

239 :sample: base='120004', number_system_digit=0 

240 :sample: base='120000', number_system_digit=0, safe_mode=False 

241 :sample: base='120003', number_system_digit=0, safe_mode=False 

242 :sample: base='120004', number_system_digit=0, safe_mode=False 

243 """ 

244 if safe_mode is not False: 

245 upc_ae = self._upc_ae(base=base, number_system_digit=number_system_digit) 

246 return self._convert_upc_a2e(upc_ae) 

247 else: 

248 upc_ae = self._upc_ae(base=base, number_system_digit=number_system_digit) 

249 return upc_ae[0] + "".join(str(x) for x in base or "") + upc_ae[-1]