1# luhn.py - functions for performing the Luhn and Luhn mod N algorithms
2#
3# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18# 02110-1301 USA
19
20"""The Luhn and Luhn mod N algorithms.
21
22The Luhn algorithm is used to detect most accidental errors in various
23identification numbers.
24
25>>> validate('7894')
26Traceback (most recent call last):
27    ...
28InvalidChecksum: ...
29>>> checksum('7894')
306
31>>> calc_check_digit('7894')
32'9'
33>>> validate('78949')
34'78949'
35
36An alternative alphabet can be provided to use the Luhn mod N algorithm.
37The default alphabet is '0123456789'.
38
39>>> validate('1234', alphabet='0123456789abcdef')
40Traceback (most recent call last):
41    ...
42InvalidChecksum: ...
43>>> checksum('1234', alphabet='0123456789abcdef')
4414
45"""
46
47from stdnum.exceptions import *
48
49
50def checksum(number, alphabet='0123456789'):
51    """Calculate the Luhn checksum over the provided number. The checksum
52    is returned as an int. Valid numbers should have a checksum of 0."""
53    n = len(alphabet)
54    number = tuple(alphabet.index(i)
55                   for i in reversed(str(number)))
56    return (sum(number[::2]) +
57            sum(sum(divmod(i * 2, n))
58                for i in number[1::2])) % n
59
60
61def validate(number, alphabet='0123456789'):
62    """Check if the number provided passes the Luhn checksum."""
63    if not bool(number):
64        raise InvalidFormat()
65    try:
66        valid = checksum(number, alphabet) == 0
67    except Exception:
68        raise InvalidFormat()
69    if not valid:
70        raise InvalidChecksum()
71    return number
72
73
74def is_valid(number, alphabet='0123456789'):
75    """Check if the number passes the Luhn checksum."""
76    try:
77        return bool(validate(number, alphabet))
78    except ValidationError:
79        return False
80
81
82def calc_check_digit(number, alphabet='0123456789'):
83    """Calculate the extra digit that should be appended to the number to
84    make it a valid number."""
85    ck = checksum(str(number) + alphabet[0], alphabet)
86    return alphabet[-ck]
87