8000 Improve validation and docstrings of Indian numbers · Dzikpol/python-stdnum@fc56388 · GitHub 8000
[go: up one dir, main page]

Skip to content

Commit fc56388

Browse files
vairag22arthurdejong
authored andcommitted
Improve validation and docstrings of Indian numbers
This ensures that an Aadhaar cannot be a palindrome and checks the serial part of the PAN to not be all zeros. It also updates some descriptions of PAN holder types and renames the card_holder_type to just holder_type. Closes arthurdejong#279
1 parent 1a0e613 commit fc56388

File tree

2 files changed

+63
-37
lines changed

2 files changed

+63
-37
lines changed

stdnum/in_/aadhaar.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# aadhaar.py - functions for handling Indian Aadhaar numbers
1+
# aadhaar.py - functions for handling Indian personal identity numbers
22
#
33
# Copyright (C) 2017 Srikanth L
4+
# Copyright (C) 2021 Gaurav Chauhan
45
#
56
# This library is free software; you can redistribute it and/or
67
# modify it under the terms of the GNU Lesser General Public
@@ -17,15 +18,21 @@
1718
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
1819
# 02110-1301 USA
1920

20-
"""Aadhaar (Indian digital resident personal identity number)
21+
"""Aadhaar (Indian personal identity number).
2122
22-
Aadhaar is a 12 digit unique identity number issued to all Indian residents.
23-
The number is assigned by the Unique Identification Authority of India
24-
(UIDAI).
23+
Aadhaar is a 12 digit identification number that can be obtained by Indian
24+
citizens, non-residents passport holders of India and resident foreign
25+
nationals. The number is issued by the Unique Identification Authority of
26+
India (UIDAI).
27+
28+
Aadhaar is made up of 12 digits where the last digits is a check digit
29+
calculated using the Verhoeff algorithm. The numbers are generated in a
30+
random, non-repeating sequence and do not begin with 0 or 1.
2531
2632
More information:
2733
2834
* https://en.wikipedia.org/wiki/Aadhaar
35+
* https://web.archive.org/web/20140611025606/http://uidai.gov.in/UID_PDF/Working_Papers/A_UID_Numbering_Scheme.pdf
2936
3037
>>> validate('234123412346')
3138
'234123412346'
@@ -41,6 +48,10 @@
4148
Traceback (most recent call last):
4249
...
4350
InvalidLength: ...
51+
>>> validate('222222222222') # number cannot be a palindrome
52+
Traceback (most recent call last):
53+
...
54+
InvalidFormat: ...
4455
>>> format('234123412346')
4556
'2341 2341 2346'
4657
>>> mask('234123412346')
@@ -72,6 +83,8 @@ def validate(number):
7283
raise InvalidLength()
7384
if not aadhaar_re.match(number):
7485
raise InvalidFormat()
86+
if number == number[::-1]:
87+
raise InvalidFormat() # Aadhaar cannot be a palindrome
7588
verhoeff.validate(number)
7689
return number
7790

@@ -92,7 +105,7 @@ def format(number):
92105

93106

94107
def mask(number):
95-
"""Masks the first 8 digits as per MeitY guidelines for securing identity
96-
information and Sensitive personal data."""
108+
"""Masks the first 8 digits as per Ministry of Electronics and
109+
Information Technology (MeitY) guidelines."""
97110
number = compact(number)
98111
return 'XXXX XXXX ' + number[-4:]

stdnum/in_/pan.py

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# pan.py - functions for handling Indian Permanent Account number (PAN)
1+
# pan.py - functions for handling Indian income tax numbers
22
#
33
# Copyright (C) 2017 Srikanth Lakshmanan
4+
# Copyright (C) 2021 Gaurav Chauhan
45
#
56
# This library is free software; you can redistribute it and/or
67
# modify it under the terms of the GNU Lesser General Public
@@ -20,19 +21,23 @@
2021
"""PAN (Permanent Account Number, Indian income tax identifier).
2122
2223
The Permanent Account Number (PAN) is a 10 digit alphanumeric identifier for
23-
Indian individuals, families and corporates for income tax purposes.
24+
Indian individuals, families and corporates for income tax purposes. It is
25+
also issued to foreign nationals subject to a valid visa.
2426
25-
The number is built up of 5 characters, 4 numbers and 1 character. The fourth
26-
character indicates the type of holder of the number and the last character
27-
is computed by an undocumented checksum algorithm.
27+
PAN is made up of 5 letters, 4 digits and 1 alphabetic check digit. The 4th
28+
character indicates the type of holder, the 5th character is either 1st
29+
letter of the holder's name or holder's surname in case of 'Individual' PAN,
30+
next 4 digits are serial numbers running from 0001 to 9999 and the last
31+
character is a check digit computed by an undocumented checksum algorithm.
2832
2933
More information:
3034
3135
* https://en.wikipedia.org/wiki/Permanent_account_number
36+
* https://incometaxindia.gov.in/tutorials/1.permanent%20account%20number%20(pan).pdf
3237
3338
>>> validate('ACUPA7085R')
3439
'ACUPA7085R'
35-
>>> validate('234123412347')
40+
>>> validate('ACUPA7085RR')
3641
Traceback (most recent call last):
3742
...
3843
InvalidLength: ...
@@ -44,9 +49,13 @@
4449
Traceback (most recent call last):
4550
...
4651
InvalidComponent: ...
52+
>>> validate('ACUPA0000R') # serial number should not be '0000'
53+
Traceback (most recent call last):
54+
...
55+
InvalidComponent: ...
4756
>>> mask('AAPPV8261K')
4857
'AAPPVXXXXK'
49-
>>> info('AAPPV8261K')['card_holder_type']
58+
>>> info('AAPPV8261K')['holder_type']
5059
'Individual'
5160
"""
5261

@@ -58,6 +67,21 @@
5867

5968
_pan_re = re.compile(r'^[A-Z]{5}[0-9]{4}[A-Z]$')
6069

70+
_pan_holder_types = {
71+
'A': 'Association of Persons (AOP)',
72+
'B': 'Body of Individuals (BOI)',
73+
'C': 'Company',
74+
'F': 'Firm/Limited Liability Partnership',
75+
'G': 'Government Agency',
76+
'H': 'Hindu Undivided Family (HUF)',
77+
'L': 'Local Authority',
78+
'J': 'Artificial Juridical Person',
79+
'P': 'Individual',
80+
'T': 'Trust',
81+
'K': 'Krish (Trust Krish)',
82+
}
83+
# Type 'K' may have been discontinued, not listed on Income Text Dept website.
84+
6185

6286
def compact(number):
6387
"""Convert the number to the minimal representation. This strips the
@@ -66,54 +90,43 @@ def compact(number):
6690

6791

6892
def validate(number):
69-
"""Check if the number provided is a valid PAN. This checks the
70-
length and formatting."""
93+
"""Check if the number provided is a valid PAN. This checks the length
94+
and formatting."""
7195
number = compact(number)
7296
if len(number) != 10:
7397
raise InvalidLength()
7498
if not _pan_re.match(number):
7599
raise InvalidFormat()
76100
info(number) # used to check 4th digit
101+
if number[5:9] == '0000':
102+
raise InvalidComponent()
77103
return number
78104

79105

80106
def is_valid(number):
81-
"""Check if the number provided is a valid PAN. This checks the
82-
length and formatting."""
107+
"""Check if the number provided is a valid PAN. This checks the length
108+
and formatting."""
83109
try:
84110
return bool(validate(number))
85111
except ValidationError:
86112
return False
87113

88114

89-
_card_holder_types = {
90-
'A': 'Association of Persons (AOP)',
91-
'B': 'Body of Individuals (BOI)',
92-
'C': 'Company',
93-
'F': 'Firm',
94-
'G': 'Government',
95-
'H': 'HUF (Hindu Undivided Family)',
96-
'L': 'Local Authority',
97-
'J': 'Artificial Juridical Person',
98-
'P': 'Individual',
99-
'T': 'Trust (AOP)',
100-
'K': 'Krish (Trust Krish)',
101-
}
102-
103-
104115
def info(number):
105116
"""Provide information that can be decoded from the PAN."""
106117
number = compact(number)
107-
card_holder_type = _card_holder_types.get(number[3])
108-
if not card_holder_type:
118+
holder_type = _pan_holder_types.get(number[3])
119+
if not holder_type:
109120
raise InvalidComponent()
110121
return {
111-
'card_holder_type': card_holder_type,
122+
'holder_type': holder_type,
123+
'card_holder_type': holder_type, # for backwards compatibility
112124
'initial': number[4],
113125
}
114126

115127

116128
def mask(number):
117-
"""Mask the PAN as per CBDT masking standard."""
129+
"""Mask the PAN as per Central Board of Direct Taxes (CBDT) masking
130+
standard."""
118131
number = compact(number)
119132
return number[:5] + 'XXXX' + number[-1:]

0 commit comments

Comments
 (0)
0