Skip to content

Commit 53a8b4a

Browse files
author
Yuichiro Luke Smith
authored
BarcodeProvider prefix feature (#1169)
* typo * Add prefix support to BarcodeProvider * Add localization support to BarcodeProvider * add en_US, en_CA, fr_CA (move upc related items to en_US) * Add ja_JP localization to BarcodeProvider * fix tests * BarCode -> Barcode * JaJP * JAN docstrings * docstrings fixes * wrap map(int) around prefix * fix potential breaking change * add more docstring on ean-13 and upc-a compatibility * localize test_barcode * add jan tests * flake8
1 parent fca4fe9 commit 53a8b4a

File tree

6 files changed

+484
-195
lines changed

6 files changed

+484
-195
lines changed

faker/providers/barcode/__init__.py

Lines changed: 57 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,21 @@
1-
import re
2-
31
from .. import BaseProvider
42

3+
localized = True
4+
55

66
class Provider(BaseProvider):
7+
# Source of GS1 country codes: https://gs1.org/standards/id-keys/company-prefix
8+
local_prefixes = ()
79

8-
upc_e_base_pattern = re.compile(r'^\d{6}$')
9-
upc_ae_pattern1 = re.compile(
10-
r'^(?P<number_system_digit>[01])' # The first digit must be 0 or 1
11-
r'(?=\d{11}$)' # followed by 11 digits of which
12-
r'(?P<mfr_code>\d{2})' # the first 2 digits make up the manufacturer code,
13-
r'(?:(?P<extra>[012])0{4})' # if immediately followed by 00000, 10000, or 20000,
14-
r'(?P<product_code>\d{3})' # a 3-digit product code,
15-
r'(?P<check_digit>\d)$', # and finally a check digit.
16-
)
17-
upc_ae_pattern2 = re.compile(
18-
r'^(?P<number_system_digit>[01])' # The first digit must be 0 or 1
19-
r'(?=\d{11}$)' # followed by 11 digits of which
20-
r'(?P<mfr_code>\d{3,4}?)' # the first 3 or 4 digits make up the manufacturer code,
21-
r'(?:0{5})' # if immediately followed by 00000,
22-
r'(?P<product_code>\d{1,2})' # a 2-digit or single digit product code,
23-
r'(?P<check_digit>\d)$', # and finally a check digit.
24-
)
25-
upc_ae_pattern3 = re.compile(
26-
r'^(?P<number_system_digit>[01])' # The first digit must be 0 or 1
27-
r'(?=\d{11}$)' # followed by 11 digits of which
28-
r'(?P<mfr_code>\d{5})' # the first 5 digits make up the manufacturer code,
29-
r'(?:0{4}(?P<extra>[5-9]))' # if immediately followed by 0000 and a 5, 6, 7, 8, or 9,
30-
r'(?P<check_digit>\d)$', # and finally a check digit.
31-
)
32-
33-
def _ean(self, length=13, leading_zero=None):
10+
def _ean(self, length=13, prefixes=()):
3411
if length not in (8, 13):
3512
raise AssertionError("length can only be 8 or 13")
3613

3714
code = [self.random_digit() for _ in range(length - 1)]
38-
if leading_zero is True:
39-
code[0] = 0
40-
elif leading_zero is False:
41-
code[0] = self.random_int(1, 9)
15+
16+
if prefixes:
17+
prefix = self.random_element(prefixes)
18+
code[:len(prefix)] = map(int, prefix)
4219

4320
if length == 8:
4421
weights = [3, 1, 3, 1, 3, 1, 3]
@@ -51,186 +28,88 @@ def _ean(self, length=13, leading_zero=None):
5128

5229
return ''.join(str(x) for x in code)
5330

54-
def _convert_upc_a2e(self, upc_a):
55-
"""
56-
Convert a 12-digit UPC-A barcode to its 8-digit UPC-E equivalent.
57-
58-
Note that not all UPC-A barcodes can be converted.
59-
"""
60-
if not isinstance(upc_a, str):
61-
raise TypeError('`upc_a` is not a string')
62-
m1 = self.upc_ae_pattern1.match(upc_a)
63-
m2 = self.upc_ae_pattern2.match(upc_a)
64-
m3 = self.upc_ae_pattern3.match(upc_a)
65-
if not any([m1, m2, m3]):
66-
raise ValueError('`upc_a` has an invalid value')
67-
upc_e_template = '{number_system_digit}{mfr_code}{product_code}{extra}{check_digit}'
68-
if m1:
69-
upc_e = upc_e_template.format(**m1.groupdict())
70-
elif m2:
71-
groupdict = m2.groupdict()
72-
groupdict['extra'] = str(len(groupdict.get('mfr_code')))
73-
upc_e = upc_e_template.format(**groupdict)
74-
else:
75-
groupdict = m3.groupdict()
76-
groupdict['product_code'] = ''
77-
upc_e = upc_e_template.format(**groupdict)
78-
return upc_e
79-
80-
def _upc_ae(self, base=None, number_system_digit=None):
81-
"""
82-
Create a 12-digit UPC-A barcode that can be converted to UPC-E.
83-
84-
The expected value of ``base`` is a 6-digit string. If any other value is
85-
provided, this method will use a random 6-digit string instead.
86-
87-
The expected value of ``number_system_digit`` is the integer ``0`` or ``1``.
88-
If any other value is provided, this method will randomly choose from the two.
89-
90-
Please also view notes on `upc_a()` and `upc_e()` for more details.
91-
"""
92-
if isinstance(base, str) and self.upc_e_base_pattern.match(base):
93-
base = [int(x) for x in base]
94-
else:
95-
base = [self.random_int(0, 9) for _ in range(6)]
96-
if number_system_digit not in [0, 1]:
97-
number_system_digit = self.random_int(0, 1)
98-
99-
if base[-1] <= 2:
100-
code = base[:2] + base[-1:] + [0] * 4 + base[2:-1]
101-
elif base[-1] <= 4:
102-
code = base[:base[-1]] + [0] * 5 + base[base[-1]:-1]
103-
else:
104-
code = base[:5] + [0] * 4 + base[-1:]
105-
106-
code.insert(0, number_system_digit)
107-
weights = [3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3]
108-
weighted_sum = sum(x * y for x, y in zip(code, weights))
109-
check_digit = (10 - weighted_sum % 10) % 10
110-
code.append(check_digit)
111-
return ''.join(str(x) for x in code)
112-
113-
def ean(self, length=13):
31+
def ean(self, length=13, prefixes=()):
11432
"""Generate an EAN barcode of the specified ``length``.
11533
11634
The value of ``length`` can only be ``8`` or ``13`` (default) which will
11735
create an EAN-8 or an EAN-13 barcode respectively.
11836
37+
If ``prefixes`` are specified, the result will begin with one of the sequence in ``prefixes``
38+
11939
:sample: length=13
12040
:sample: length=8
41+
:sample: prefixes=('00',)
42+
:sample: prefixes=('45', '49')
12143
"""
122-
return self._ean(length)
44+
return self._ean(length, prefixes=prefixes)
12345

124-
def ean8(self):
46+
def ean8(self, prefixes=()):
12547
"""Generate an EAN-8 barcode.
12648
12749
This method uses :meth:`ean() <faker.providers.barcode.Provider.ean>` under the
12850
hood with the ``length`` argument explicitly set to ``8``.
12951
52+
If ``prefixes`` are specified, the result will begin with one of the sequence in ``prefixes``
53+
13054
:sample:
55+
:sample: prefixes=('00',)
56+
:sample: prefixes=('45', '49')
13157
"""
132-
return self._ean(8)
58+
return self._ean(8, prefixes=prefixes)
13359

134-
def ean13(self, leading_zero=None):
60+
def ean13(self, prefixes=()):
13561
"""Generate an EAN-13 barcode.
13662
137-
If ``leading_digit`` is ``True``, the leftmost digit of the barcode will be set
138-
to ``0``. If ``False``, the leftmost digit cannot be ``0``. If ``None`` (default),
139-
the leftmost digit can be any digit.
140-
141-
Note that an EAN-13 barcode that starts with a zero can be converted to UPC-A
142-
by dropping the leading zero.
63+
If ``prefixes`` are specified, the result will begin with one of the sequence in ``prefixes``.
14364
14465
This method uses :meth:`ean() <faker.providers.barcode.Provider.ean>` under the
14566
hood with the ``length`` argument explicitly set to ``13``.
14667
68+
.. note::
69+
70+
Codes starting with a leading zero are treated specially in some barcode readers.
71+
72+
For more information about compatibility with UPC-A codes, see
73+
:meth:`en_US.ean13() <faker.providers.barcode.en_US.Provider.ean13>`
74+
14775
:sample:
148-
:sample: leading_zero=False
149-
:sample: leading_zero=True
76+
:sample: prefixes=('00',)
77+
:sample: prefixes=('45', '49')
15078
"""
151-
return self._ean(13, leading_zero=leading_zero)
15279

153-
def upc_a(self, upc_ae_mode=False, base=None, number_system_digit=None):
154-
"""Generate a 12-digit UPC-A barcode.
80+
return self._ean(13, prefixes=prefixes)
15581

156-
The value of ``upc_ae_mode`` controls how barcodes will be generated. If ``False``
157-
(default), barcodes are not guaranteed to have a UPC-E equivalent. In this mode,
158-
the method uses :meth:`ean13 <faker.providers.barcode.Provider.ean13>` under the hood,
159-
and the values of ``base`` and ``number_system_digit`` will be ignored.
82+
def localized_ean(self, length=13):
83+
"""Generate a localized EAN barcode of the specified ``length``.
16084
161-
If ``upc_ae_mode`` is ``True``, the resulting barcodes are guaranteed to have a UPC-E
162-
equivalent, and the values of ``base`` and ``number_system_digit`` will be used to
163-
control what is generated.
85+
The value of ``length`` can only be ``8`` or ``13`` (default) which will
86+
create an EAN-8 or an EAN-13 barcode respectively.
16487
165-
Under this mode, ``base`` is expected to have a 6-digit string value. If any other value
166-
is supplied, a random 6-digit string will be used instead. As for ``number_system_digit``,
167-
the expected value is a ``0`` or a ``1``. If any other value is provided, this method
168-
will randomly choose from the two.
88+
This method uses :meth:`ean() <faker.providers.barcode.Provider.ean>` under the
89+
hood with the ``prefixes`` argument explicitly set to ``self.local_prefixes``.
16990
170-
.. important::
91+
:sample:
92+
:sample: length=13
93+
:sample: length=8
94+
"""
95+
return self._ean(length, prefixes=self.local_prefixes)
96+
97+
def localized_ean8(self):
98+
"""Generate a localized EAN-8 barcode.
17199
172-
When ``upc_ae_mode`` is enabled, you might encounter instances where different values
173-
of ``base`` (e.g. ``'120003'`` and ``'120004'``) produce the same UPC-A barcode. This
174-
is normal, and the reason lies within the whole conversion process. To learn more about
175-
this and what ``base`` and ``number_system_digit`` actually represent, please refer
176-
to :meth:`upc_e() <faker.providers.barcode.Provider.upc_e>`.
100+
This method uses :meth:`localized_ean() <faker.providers.barcode.Provider.ean>` under the
101+
hood with the ``length`` argument explicitly set to ``8``.
177102
178103
:sample:
179-
:sample: upc_ae_mode=True, number_system_digit=0
180-
:sample: upc_ae_mode=True, number_system_digit=1
181-
:sample: upc_ae_mode=True, base='123456', number_system_digit=0
182-
:sample: upc_ae_mode=True, base='120003', number_system_digit=0
183-
:sample: upc_ae_mode=True, base='120004', number_system_digit=0
184104
"""
185-
if upc_ae_mode is True:
186-
return self._upc_ae(base=base, number_system_digit=number_system_digit)
187-
else:
188-
ean13 = self.ean13(leading_zero=True)
189-
return ean13[1:]
190-
191-
def upc_e(self, base=None, number_system_digit=None, safe_mode=True):
192-
"""Generate an 8-digit UPC-E barcode.
193-
194-
UPC-E barcodes can be expressed in 6, 7, or 8-digit formats, but this method uses the
195-
8 digit format, since it is trivial to convert to the other two formats. The first digit
196-
(starting from the left) is controlled by ``number_system_digit``, and it can only be a
197-
``0`` or a ``1``. The last digit is the check digit that is inherited from the UPC-E barcode's
198-
UPC-A equivalent. The middle six digits are collectively referred to as the ``base`` (for a
199-
lack of a better term).
200-
201-
On that note, this method uses ``base`` and ``number_system_digit`` to first generate a
202-
UPC-A barcode for the check digit, and what happens next depends on the value of ``safe_mode``.
203-
The argument ``safe_mode`` exists, because there are some UPC-E values that share the same
204-
UPC-A equivalent. For example, any UPC-E barcode of the form ``abc0000d``, ``abc0003d``, and
205-
``abc0004d`` share the same UPC-A value ``abc00000000d``, but that UPC-A value will only convert
206-
to ``abc0000d`` because of (a) how UPC-E is just a zero-suppressed version of UPC-A and (b) the
207-
rules around the conversion.
208-
209-
If ``safe_mode`` is ``True`` (default), this method performs another set of conversions to
210-
guarantee that the UPC-E barcodes generated can be converted to UPC-A, and that UPC-A
211-
barcode can be converted back to the original UPC-E barcode. Using the example above, even
212-
if the bases ``120003`` or ``120004`` are used, the resulting UPC-E barcode will always
213-
use the base ``120000``.
214-
215-
If ``safe_mode`` is ``False``, then the ``number_system_digit``, ``base``, and the computed
216-
check digit will just be concatenated together to produce the UPC-E barcode, and attempting
217-
to convert the barcode to UPC-A and back again to UPC-E will exhibit the behavior described
218-
above.
105+
return self.localized_ean(8)
106+
107+
def localized_ean13(self):
108+
"""Generate a localized EAN-13 barcode.
109+
110+
This method uses :meth:`localized_ean() <faker.providers.barcode.Provider.ean>` under the
111+
hood with the ``length`` argument explicitly set to ``13``.
219112
220113
:sample:
221-
:sample: base='123456'
222-
:sample: base='123456', number_system_digit=0
223-
:sample: base='123456', number_system_digit=1
224-
:sample: base='120000', number_system_digit=0
225-
:sample: base='120003', number_system_digit=0
226-
:sample: base='120004', number_system_digit=0
227-
:sample: base='120000', number_system_digit=0, safe_mode=False
228-
:sample: base='120003', number_system_digit=0, safe_mode=False
229-
:sample: base='120004', number_system_digit=0, safe_mode=False
230114
"""
231-
if safe_mode is not False:
232-
upc_ae = self._upc_ae(base=base, number_system_digit=number_system_digit)
233-
return self._convert_upc_a2e(upc_ae)
234-
else:
235-
upc_ae = self._upc_ae(base=base, number_system_digit=number_system_digit)
236-
return upc_ae[0] + ''.join(str(x) for x in base) + upc_ae[-1]
115+
return self.localized_ean(13)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from itertools import product
2+
3+
# Canada uses UPC too
4+
from ..en_US import Provider as BarcodeProvider
5+
6+
7+
class Provider(BarcodeProvider):
8+
# Source of GS1 country codes: https://gs1.org/standards/id-keys/company-prefix
9+
local_prefixes = (
10+
# The source above doesn't specify prefixes 00~01, 06~09 to be used in Canada also,
11+
# but it's referenced in numerous pages e.g.: https://www.nationwidebarcode.com/upc-country-codes/
12+
*product((0,), range(2)),
13+
*product((0,), range(6, 10)),
14+
(7, 5),
15+
)

0 commit comments

Comments
 (0)