india-kit 0.1.0__tar.gz

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.
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ *.tgz
4
+ .turbo/
5
+ .DS_Store
6
+ pnpm-lock.yaml
7
+ .env
8
+ coverage/
9
+ /.vscode/
10
+ .idea/
11
+ /.cache/
@@ -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,44 @@
1
+ # india-kit
2
+
3
+ The definitive Indian developer toolkit for Python. Validate Aadhaar, PAN, GSTIN, IFSC, UPI, pincode and more — zero dependencies, fully typed.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install india-kit
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from india_kit import (
15
+ validate_pan, mask_pan, mock_pan,
16
+ validate_aadhaar, mock_aadhaar,
17
+ validate_gstin, mock_gstin,
18
+ validate_ifsc, lookup_ifsc,
19
+ validate_upi,
20
+ validate_pincode, lookup_pincode,
21
+ split_gst,
22
+ )
23
+
24
+ validate_pan("ABCDE1234F") # {"valid": True}
25
+ validate_aadhaar("234123412346") # {"valid": True/False}
26
+ validate_gstin("27ABCDE1234F1Z5") # {"valid": True/False}
27
+ validate_ifsc("SBIN0000001") # {"valid": True}
28
+ validate_upi("alice@icici") # {"valid": True}
29
+ validate_pincode("110001") # {"valid": True}
30
+
31
+ lookup_ifsc("SBIN0000001") # {"bank": "State Bank of India", ...}
32
+ lookup_pincode("110001") # {"district": "Central Delhi", "state": "Delhi"}
33
+
34
+ split_gst(1000, 18) # {"cgst": 90.0, "sgst": 90.0, "igst": 0.0, "total_tax": 180.0}
35
+ split_gst(1000, 18, intra_state=False) # {"cgst": 0.0, "sgst": 0.0, "igst": 180.0, ...}
36
+
37
+ mask_pan("ABCDE1234F") # "ABCXX1234F"
38
+ mock_pan() # random valid PAN
39
+ mock_aadhaar() # random valid Aadhaar
40
+ ```
41
+
42
+ ## License
43
+
44
+ MIT
@@ -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,3 @@
1
+ from .pincode import validate_pincode, lookup_pincode
2
+
3
+ __all__ = ["validate_pincode", "lookup_pincode"]
@@ -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,4 @@
1
+ from .ifsc import validate_ifsc, lookup_ifsc
2
+ from .upi import validate_upi
3
+
4
+ __all__ = ["validate_ifsc", "lookup_ifsc", "validate_upi"]
@@ -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)
@@ -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}
@@ -0,0 +1,3 @@
1
+ from .gst import split_gst
2
+
3
+ __all__ = ["split_gst"]
@@ -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,38 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "india-kit"
7
+ version = "0.1.0"
8
+ description = "The definitive Indian developer toolkit — validate Aadhaar, PAN, GSTIN, IFSC, UPI, pincode and more"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Inreal Solutions", email = "aryan23062001@gmail.com" }]
12
+ requires-python = ">=3.10"
13
+ dependencies = []
14
+ keywords = [
15
+ "india", "indian", "aadhaar", "pan", "gstin", "ifsc",
16
+ "upi", "pincode", "validate", "validator", "fintech", "gst"
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development :: Libraries",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/Inreal-Solutions/India-kit"
31
+ Repository = "https://github.com/Inreal-Solutions/India-kit"
32
+ Issues = "https://github.com/Inreal-Solutions/India-kit/issues"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["india_kit"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,22 @@
1
+ from india_kit import validate_aadhaar, mock_aadhaar
2
+
3
+
4
+ def test_invalid_short():
5
+ assert validate_aadhaar("12345678901")["valid"] is False
6
+
7
+ def test_invalid_letters():
8
+ assert validate_aadhaar("12345678901X")["valid"] is False
9
+
10
+ def test_invalid_first_digit_zero():
11
+ assert validate_aadhaar("012345678901")["valid"] is False
12
+
13
+ def test_mock_aadhaar_is_valid():
14
+ for _ in range(50):
15
+ a = mock_aadhaar()
16
+ result = validate_aadhaar(a)
17
+ assert result["valid"] is True, f"mock_aadhaar() returned invalid: {a}"
18
+
19
+ def test_mock_aadhaar_is_12_digits():
20
+ a = mock_aadhaar()
21
+ assert len(a) == 12
22
+ assert a.isdigit()
@@ -0,0 +1,19 @@
1
+ from india_kit import validate_pan, mask_pan, mock_pan
2
+
3
+
4
+ def test_valid_pan():
5
+ assert validate_pan("ABCDE1234F")["valid"] is True
6
+
7
+ def test_invalid_pan_short():
8
+ assert validate_pan("ABCD1234F")["valid"] is False
9
+
10
+ def test_pan_lowercase_normalized():
11
+ assert validate_pan("abcde1234f")["valid"] is True
12
+
13
+ def test_mask_pan():
14
+ assert mask_pan("ABCDE1234F") == "ABCXX1234F"
15
+
16
+ def test_mock_pan_is_valid():
17
+ for _ in range(50):
18
+ pan = mock_pan()
19
+ assert validate_pan(pan)["valid"] is True, f"mock_pan() returned invalid PAN: {pan}"
@@ -0,0 +1,63 @@
1
+ from india_kit import (
2
+ validate_gstin, mock_gstin,
3
+ validate_ifsc, lookup_ifsc,
4
+ validate_upi,
5
+ validate_pincode, lookup_pincode,
6
+ split_gst,
7
+ )
8
+
9
+
10
+ def test_valid_gstin():
11
+ g = mock_gstin()
12
+ assert len(g) == 15
13
+
14
+ def test_invalid_gstin():
15
+ assert validate_gstin("INVALID")["valid"] is False
16
+
17
+ def test_valid_ifsc():
18
+ assert validate_ifsc("SBIN0000001")["valid"] is True
19
+
20
+ def test_invalid_ifsc():
21
+ assert validate_ifsc("SBI001")["valid"] is False
22
+
23
+ def test_lookup_ifsc():
24
+ result = lookup_ifsc("SBIN0000001")
25
+ assert result is not None
26
+ assert result["bank"] == "State Bank of India"
27
+
28
+ def test_lookup_ifsc_missing():
29
+ assert lookup_ifsc("XXXX0000000") is None
30
+
31
+ def test_valid_upi():
32
+ assert validate_upi("alice@icici")["valid"] is True
33
+ assert validate_upi("bob.smith@upi")["valid"] is True
34
+
35
+ def test_invalid_upi():
36
+ assert validate_upi("@icici")["valid"] is False
37
+ assert validate_upi("alice@")["valid"] is False
38
+
39
+ def test_valid_pincode():
40
+ assert validate_pincode("110001")["valid"] is True
41
+
42
+ def test_invalid_pincode():
43
+ assert validate_pincode("011001")["valid"] is False
44
+ assert validate_pincode("11001")["valid"] is False
45
+
46
+ def test_lookup_pincode():
47
+ result = lookup_pincode("110001")
48
+ assert result is not None
49
+ assert result["state"] == "Delhi"
50
+
51
+ def test_split_gst_intra():
52
+ result = split_gst(1000, 18, intra_state=True)
53
+ assert result["cgst"] == 90.0
54
+ assert result["sgst"] == 90.0
55
+ assert result["igst"] == 0.0
56
+ assert result["total_tax"] == 180.0
57
+
58
+ def test_split_gst_inter():
59
+ result = split_gst(1000, 18, intra_state=False)
60
+ assert result["cgst"] == 0.0
61
+ assert result["sgst"] == 0.0
62
+ assert result["igst"] == 180.0
63
+ assert result["total_tax"] == 180.0