india-kit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- india_kit/__init__.py +19 -0
- india_kit/address/__init__.py +3 -0
- india_kit/address/pincode.py +29 -0
- india_kit/banking/__init__.py +4 -0
- india_kit/banking/ifsc.py +25 -0
- india_kit/banking/upi.py +11 -0
- india_kit/finance/__init__.py +3 -0
- india_kit/finance/gst.py +6 -0
- india_kit/identity/__init__.py +9 -0
- india_kit/identity/aadhaar.py +66 -0
- india_kit/identity/gstin.py +35 -0
- india_kit/identity/pan.py +30 -0
- india_kit-0.1.0.dist-info/METADATA +65 -0
- india_kit-0.1.0.dist-info/RECORD +15 -0
- india_kit-0.1.0.dist-info/WHEEL +4 -0
india_kit/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .identity.pan import validate_pan, mask_pan, mock_pan
|
|
2
|
+
from .identity.aadhaar import validate_aadhaar, mock_aadhaar
|
|
3
|
+
from .identity.gstin import validate_gstin, mock_gstin
|
|
4
|
+
from .banking.ifsc import validate_ifsc, lookup_ifsc
|
|
5
|
+
from .banking.upi import validate_upi
|
|
6
|
+
from .address.pincode import validate_pincode, lookup_pincode
|
|
7
|
+
from .finance.gst import split_gst
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"validate_pan", "mask_pan", "mock_pan",
|
|
13
|
+
"validate_aadhaar", "mock_aadhaar",
|
|
14
|
+
"validate_gstin", "mock_gstin",
|
|
15
|
+
"validate_ifsc", "lookup_ifsc",
|
|
16
|
+
"validate_upi",
|
|
17
|
+
"validate_pincode", "lookup_pincode",
|
|
18
|
+
"split_gst",
|
|
19
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_PINCODE_RE = re.compile(r'^[1-9][0-9]{5}$')
|
|
4
|
+
|
|
5
|
+
_PINCODE_DB = {
|
|
6
|
+
"110001": {"district": "Central Delhi", "state": "Delhi"},
|
|
7
|
+
"400001": {"district": "Mumbai City", "state": "Maharashtra"},
|
|
8
|
+
"560001": {"district": "Bangalore Urban", "state": "Karnataka"},
|
|
9
|
+
"600001": {"district": "Chennai", "state": "Tamil Nadu"},
|
|
10
|
+
"500001": {"district": "Hyderabad", "state": "Telangana"},
|
|
11
|
+
"700001": {"district": "Kolkata", "state": "West Bengal"},
|
|
12
|
+
"380001": {"district": "Ahmedabad", "state": "Gujarat"},
|
|
13
|
+
"411001": {"district": "Pune", "state": "Maharashtra"},
|
|
14
|
+
"302001": {"district": "Jaipur", "state": "Rajasthan"},
|
|
15
|
+
"226001": {"district": "Lucknow", "state": "Uttar Pradesh"},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def validate_pincode(pin: str) -> dict:
|
|
20
|
+
if not isinstance(pin, str):
|
|
21
|
+
return {"valid": False, "reason": "must be a string"}
|
|
22
|
+
p = pin.strip()
|
|
23
|
+
if not _PINCODE_RE.match(p):
|
|
24
|
+
return {"valid": False, "reason": "invalid pincode (must be 6 digits, first digit 1-9)"}
|
|
25
|
+
return {"valid": True}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def lookup_pincode(pin: str) -> dict | None:
|
|
29
|
+
return _PINCODE_DB.get(pin.strip())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_IFSC_RE = re.compile(r'^[A-Z]{4}0[A-Z0-9]{6}$')
|
|
4
|
+
|
|
5
|
+
_IFSC_DB = {
|
|
6
|
+
"SBIN0000001": {"bank": "State Bank of India", "branch": "Mumbai Main", "city": "Mumbai"},
|
|
7
|
+
"HDFC0000002": {"bank": "HDFC Bank", "branch": "Delhi Main", "city": "Delhi"},
|
|
8
|
+
"ICIC0000003": {"bank": "ICICI Bank", "branch": "Bangalore Main", "city": "Bangalore"},
|
|
9
|
+
"AXIS0000004": {"bank": "Axis Bank", "branch": "Chennai Main", "city": "Chennai"},
|
|
10
|
+
"KKBK0000005": {"bank": "Kotak Mahindra Bank", "branch": "Pune Main", "city": "Pune"},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_ifsc(ifsc: str) -> dict:
|
|
15
|
+
if not isinstance(ifsc, str):
|
|
16
|
+
return {"valid": False, "reason": "must be a string"}
|
|
17
|
+
i = ifsc.strip().upper()
|
|
18
|
+
if not _IFSC_RE.match(i):
|
|
19
|
+
return {"valid": False, "reason": "invalid IFSC format (expected AAAA0XXXXXX)"}
|
|
20
|
+
return {"valid": True}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def lookup_ifsc(ifsc: str) -> dict | None:
|
|
24
|
+
i = ifsc.strip().upper()
|
|
25
|
+
return _IFSC_DB.get(i)
|
india_kit/banking/upi.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_UPI_RE = re.compile(r'^[a-zA-Z0-9._+%\-]{2,}@[a-zA-Z]{2,}$')
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def validate_upi(vpa: str) -> dict:
|
|
7
|
+
if not isinstance(vpa, str):
|
|
8
|
+
return {"valid": False, "reason": "must be a string"}
|
|
9
|
+
if not _UPI_RE.match(vpa.strip()):
|
|
10
|
+
return {"valid": False, "reason": "invalid UPI VPA format (expected localpart@bank)"}
|
|
11
|
+
return {"valid": True}
|
india_kit/finance/gst.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
def split_gst(amount: float, rate_percent: float, intra_state: bool = True) -> dict:
|
|
2
|
+
total_tax = round(amount * rate_percent / 100, 2)
|
|
3
|
+
if intra_state:
|
|
4
|
+
half = round(total_tax / 2, 2)
|
|
5
|
+
return {"cgst": half, "sgst": half, "igst": 0.0, "total_tax": total_tax}
|
|
6
|
+
return {"cgst": 0.0, "sgst": 0.0, "igst": total_tax, "total_tax": total_tax}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .pan import validate_pan, mask_pan, mock_pan
|
|
2
|
+
from .aadhaar import validate_aadhaar, mock_aadhaar
|
|
3
|
+
from .gstin import validate_gstin, mock_gstin
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"validate_pan", "mask_pan", "mock_pan",
|
|
7
|
+
"validate_aadhaar", "mock_aadhaar",
|
|
8
|
+
"validate_gstin", "mock_gstin",
|
|
9
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
_D = [
|
|
4
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
5
|
+
[1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
|
|
6
|
+
[2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
|
|
7
|
+
[3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
|
|
8
|
+
[4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
|
|
9
|
+
[5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
|
|
10
|
+
[6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
|
|
11
|
+
[7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
|
|
12
|
+
[8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
|
|
13
|
+
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
_P = [
|
|
17
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
18
|
+
[1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
|
|
19
|
+
[5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
|
|
20
|
+
[8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
|
|
21
|
+
[9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
|
|
22
|
+
[4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
|
|
23
|
+
[2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
|
|
24
|
+
[7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
_INV = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _verhoeff_check(number: str) -> bool:
|
|
31
|
+
c = 0
|
|
32
|
+
for i, ch in enumerate(reversed(number)):
|
|
33
|
+
c = _D[c][_P[i % 8][int(ch)]]
|
|
34
|
+
return c == 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _verhoeff_checksum(number: str) -> int:
|
|
38
|
+
c = 0
|
|
39
|
+
padded = number + "0"
|
|
40
|
+
for i, ch in enumerate(reversed(padded)):
|
|
41
|
+
c = _D[c][_P[i % 8][int(ch)]]
|
|
42
|
+
return _INV[c]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_aadhaar(aadhaar: str) -> dict:
|
|
46
|
+
if not isinstance(aadhaar, str):
|
|
47
|
+
return {"valid": False, "reason": "must be a string"}
|
|
48
|
+
a = aadhaar.strip().replace(" ", "")
|
|
49
|
+
if not a.isdigit() or len(a) != 12:
|
|
50
|
+
return {"valid": False, "reason": "must be exactly 12 digits"}
|
|
51
|
+
if a[0] in "01":
|
|
52
|
+
return {"valid": False, "reason": "first digit cannot be 0 or 1"}
|
|
53
|
+
if not _verhoeff_check(a):
|
|
54
|
+
return {"valid": False, "reason": "invalid Verhoeff checksum"}
|
|
55
|
+
return {"valid": True}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def mock_aadhaar() -> str:
|
|
59
|
+
while True:
|
|
60
|
+
first = str(random.randint(2, 9))
|
|
61
|
+
rest = ''.join([str(random.randint(0, 9)) for _ in range(10)])
|
|
62
|
+
base = first + rest
|
|
63
|
+
check = _verhoeff_checksum(base)
|
|
64
|
+
candidate = base + str(check)
|
|
65
|
+
if _verhoeff_check(candidate):
|
|
66
|
+
return candidate
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
from .pan import validate_pan, mock_pan
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_GSTIN_RE = re.compile(r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$')
|
|
8
|
+
|
|
9
|
+
_STATE_CODES = [
|
|
10
|
+
"01","02","03","04","05","06","07","08","09","10",
|
|
11
|
+
"11","12","13","14","15","16","17","18","19","20",
|
|
12
|
+
"21","22","23","24","25","26","27","28","29","30",
|
|
13
|
+
"31","32","33","34","35","36","37",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_gstin(gstin: str) -> dict:
|
|
18
|
+
if not isinstance(gstin, str):
|
|
19
|
+
return {"valid": False, "reason": "must be a string"}
|
|
20
|
+
g = gstin.strip().upper()
|
|
21
|
+
if not _GSTIN_RE.match(g):
|
|
22
|
+
return {"valid": False, "reason": "invalid GSTIN format"}
|
|
23
|
+
embedded_pan = g[2:12]
|
|
24
|
+
result = validate_pan(embedded_pan)
|
|
25
|
+
if not result["valid"]:
|
|
26
|
+
return {"valid": False, "reason": "embedded PAN is invalid"}
|
|
27
|
+
return {"valid": True}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def mock_gstin() -> str:
|
|
31
|
+
state = random.choice(_STATE_CODES)
|
|
32
|
+
pan = mock_pan()
|
|
33
|
+
entity = str(random.randint(1, 9))
|
|
34
|
+
checksum = random.choice(string.digits + string.ascii_uppercase)
|
|
35
|
+
return state + pan + entity + "Z" + checksum
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_PAN_RE = re.compile(r'^[A-Z]{5}[0-9]{4}[A-Z]$')
|
|
7
|
+
|
|
8
|
+
_ENTITY_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_pan(pan: str) -> dict:
|
|
12
|
+
if not isinstance(pan, str):
|
|
13
|
+
return {"valid": False, "reason": "must be a string"}
|
|
14
|
+
p = pan.strip().upper()
|
|
15
|
+
if not _PAN_RE.match(p):
|
|
16
|
+
return {"valid": False, "reason": "invalid PAN format (expected AAAAA9999A)"}
|
|
17
|
+
return {"valid": True}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def mask_pan(pan: str) -> str:
|
|
21
|
+
p = pan.strip().upper()
|
|
22
|
+
return p[:3] + "XX" + p[5:]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mock_pan() -> str:
|
|
26
|
+
letters = string.ascii_uppercase
|
|
27
|
+
part1 = ''.join(random.choices(letters, k=5))
|
|
28
|
+
part2 = ''.join(random.choices(string.digits, k=4))
|
|
29
|
+
part3 = random.choice(letters)
|
|
30
|
+
return part1 + part2 + part3
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: india-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The definitive Indian developer toolkit — validate Aadhaar, PAN, GSTIN, IFSC, UPI, pincode and more
|
|
5
|
+
Project-URL: Homepage, https://github.com/Inreal-Solutions/India-kit
|
|
6
|
+
Project-URL: Repository, https://github.com/Inreal-Solutions/India-kit
|
|
7
|
+
Project-URL: Issues, https://github.com/Inreal-Solutions/India-kit/issues
|
|
8
|
+
Author-email: Inreal Solutions <aryan23062001@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: aadhaar,fintech,gst,gstin,ifsc,india,indian,pan,pincode,upi,validate,validator
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# india-kit
|
|
23
|
+
|
|
24
|
+
The definitive Indian developer toolkit for Python. Validate Aadhaar, PAN, GSTIN, IFSC, UPI, pincode and more — zero dependencies, fully typed.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install india-kit
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from india_kit import (
|
|
36
|
+
validate_pan, mask_pan, mock_pan,
|
|
37
|
+
validate_aadhaar, mock_aadhaar,
|
|
38
|
+
validate_gstin, mock_gstin,
|
|
39
|
+
validate_ifsc, lookup_ifsc,
|
|
40
|
+
validate_upi,
|
|
41
|
+
validate_pincode, lookup_pincode,
|
|
42
|
+
split_gst,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
validate_pan("ABCDE1234F") # {"valid": True}
|
|
46
|
+
validate_aadhaar("234123412346") # {"valid": True/False}
|
|
47
|
+
validate_gstin("27ABCDE1234F1Z5") # {"valid": True/False}
|
|
48
|
+
validate_ifsc("SBIN0000001") # {"valid": True}
|
|
49
|
+
validate_upi("alice@icici") # {"valid": True}
|
|
50
|
+
validate_pincode("110001") # {"valid": True}
|
|
51
|
+
|
|
52
|
+
lookup_ifsc("SBIN0000001") # {"bank": "State Bank of India", ...}
|
|
53
|
+
lookup_pincode("110001") # {"district": "Central Delhi", "state": "Delhi"}
|
|
54
|
+
|
|
55
|
+
split_gst(1000, 18) # {"cgst": 90.0, "sgst": 90.0, "igst": 0.0, "total_tax": 180.0}
|
|
56
|
+
split_gst(1000, 18, intra_state=False) # {"cgst": 0.0, "sgst": 0.0, "igst": 180.0, ...}
|
|
57
|
+
|
|
58
|
+
mask_pan("ABCDE1234F") # "ABCXX1234F"
|
|
59
|
+
mock_pan() # random valid PAN
|
|
60
|
+
mock_aadhaar() # random valid Aadhaar
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
india_kit/__init__.py,sha256=RCsZq-uQCWtr3SDe7syRmc5CqCIQ4k-jaidURCD-FJ0,636
|
|
2
|
+
india_kit/address/__init__.py,sha256=X7aAV4xzR7W0ctGyR_7IE4ICUD7QVXyftTIjx_VkxkI,104
|
|
3
|
+
india_kit/address/pincode.py,sha256=wF15bwHWtSHGdvK64Y097CN_3eRy7V_gg2baEdpkck4,1109
|
|
4
|
+
india_kit/banking/__init__.py,sha256=4pNZYIqzdSae8w3J6_ZsnPQgaRdGrdlYdb-8NQgdE4E,135
|
|
5
|
+
india_kit/banking/ifsc.py,sha256=KXESkZt-eaWb4Hyx2jZVwvpvxcKdgpqBcYEA0Y7bVkw,935
|
|
6
|
+
india_kit/banking/upi.py,sha256=7StJa9lvlr4VyDboTZMBEhyiM0ROt2AJ1E3X8ooqpEY,366
|
|
7
|
+
india_kit/finance/__init__.py,sha256=tkl1nS2CfTBbyhCApJZ7aW3iA0rEhIK3ca8YGOJBE44,52
|
|
8
|
+
india_kit/finance/gst.py,sha256=PbAoo-mT1D8N9zkMmjL5JE55wBOlouaImwlNGJQ__Ms,360
|
|
9
|
+
india_kit/identity/__init__.py,sha256=Iv0WF5vBHv_SH6j9YC-vzYDrvpK5rF3td93FR8XdfNc,283
|
|
10
|
+
india_kit/identity/aadhaar.py,sha256=7-_CkYEIaODD3L-L509FHZaVnWhsQKzb5r_UmMYZ6H8,1916
|
|
11
|
+
india_kit/identity/gstin.py,sha256=jOTXqYoVu-iVC7G4UFUFkSU_B2-RwlYDyi5CwBiaHPA,1083
|
|
12
|
+
india_kit/identity/pan.py,sha256=4xgBFirJUP-6Hgp7EdD2R7i0_kMD39NnsEjiN9zt-Vo,767
|
|
13
|
+
india_kit-0.1.0.dist-info/METADATA,sha256=zmCVfdreqKPI9H5WAIxZtw_K9bfWQ-c144_vJeN09lM,2289
|
|
14
|
+
india_kit-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
15
|
+
india_kit-0.1.0.dist-info/RECORD,,
|