pakdatakit 2.2.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.
- pakdatakit-2.2.0/.gitignore +7 -0
- pakdatakit-2.2.0/LICENSE +21 -0
- pakdatakit-2.2.0/PKG-INFO +117 -0
- pakdatakit-2.2.0/README.md +89 -0
- pakdatakit-2.2.0/RELEASING.md +51 -0
- pakdatakit-2.2.0/pyproject.toml +47 -0
- pakdatakit-2.2.0/src/pakdatakit/__init__.py +159 -0
- pakdatakit-2.2.0/src/pakdatakit/_data.py +19 -0
- pakdatakit-2.2.0/src/pakdatakit/_internal/__init__.py +1 -0
- pakdatakit-2.2.0/src/pakdatakit/_internal/emv.py +74 -0
- pakdatakit-2.2.0/src/pakdatakit/_internal/js.py +200 -0
- pakdatakit-2.2.0/src/pakdatakit/_internal/normalize.py +36 -0
- pakdatakit-2.2.0/src/pakdatakit/_internal/phone_utils.py +54 -0
- pakdatakit-2.2.0/src/pakdatakit/address.py +240 -0
- pakdatakit-2.2.0/src/pakdatakit/cnic.py +64 -0
- pakdatakit-2.2.0/src/pakdatakit/data/area-codes.json +294 -0
- pakdatakit-2.2.0/src/pakdatakit/data/banks.json +248 -0
- pakdatakit-2.2.0/src/pakdatakit/data/cities.json +906 -0
- pakdatakit-2.2.0/src/pakdatakit/data/districts.json +710 -0
- pakdatakit-2.2.0/src/pakdatakit/data/holidays.json +154 -0
- pakdatakit-2.2.0/src/pakdatakit/data/networks.json +55 -0
- pakdatakit-2.2.0/src/pakdatakit/data/postal-codes.json +641 -0
- pakdatakit-2.2.0/src/pakdatakit/data_version.py +8 -0
- pakdatakit-2.2.0/src/pakdatakit/format.py +64 -0
- pakdatakit-2.2.0/src/pakdatakit/geo.py +98 -0
- pakdatakit-2.2.0/src/pakdatakit/hijri.py +169 -0
- pakdatakit-2.2.0/src/pakdatakit/holidays.py +171 -0
- pakdatakit-2.2.0/src/pakdatakit/iban.py +116 -0
- pakdatakit-2.2.0/src/pakdatakit/input.py +141 -0
- pakdatakit-2.2.0/src/pakdatakit/networks.py +31 -0
- pakdatakit-2.2.0/src/pakdatakit/numbers.py +175 -0
- pakdatakit-2.2.0/src/pakdatakit/phone.py +48 -0
- pakdatakit-2.2.0/src/pakdatakit/postal.py +95 -0
- pakdatakit-2.2.0/src/pakdatakit/py.typed +0 -0
- pakdatakit-2.2.0/src/pakdatakit/raast.py +498 -0
- pakdatakit-2.2.0/src/pakdatakit/tax.py +61 -0
- pakdatakit-2.2.0/src/pakdatakit/text.py +66 -0
- pakdatakit-2.2.0/src/pakdatakit/whatsapp.py +24 -0
- pakdatakit-2.2.0/src/pakdatakit/words.py +130 -0
- pakdatakit-2.2.0/tests/test_api.py +148 -0
- pakdatakit-2.2.0/tests/test_vectors.py +166 -0
pakdatakit-2.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PakDataKit Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pakdatakit
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Utilities for Pakistani data: CNIC, phone, IBAN, postal codes, address parsing, Hijri dates, holidays, amounts in words and Raast QR codes
|
|
5
|
+
Project-URL: Homepage, https://github.com/chmuzamil/pakdatakit
|
|
6
|
+
Project-URL: Documentation, https://github.com/chmuzamil/pakdatakit#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/chmuzamil/pakdatakit
|
|
8
|
+
Project-URL: Issues, https://github.com/chmuzamil/pakdatakit/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/chmuzamil/pakdatakit/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Muzamil Chaudhery
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: address,amount-in-words,cnic,hijri,holidays,iban,ntn,pakistan,phone,postal,qr,raast,urdu
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# PakDataKit for Python
|
|
30
|
+
|
|
31
|
+
Utilities for Pakistani data: CNIC, mobile numbers and networks, WhatsApp links,
|
|
32
|
+
IBAN, postal codes, cities and districts, address parsing, Roman Urdu, Hijri
|
|
33
|
+
dates, public holidays, rupee formatting, amounts in words, NTN/STRN,
|
|
34
|
+
landlines and Raast QR codes.
|
|
35
|
+
|
|
36
|
+
This is the Python port of the [`pakdatakit` npm package](https://github.com/chmuzamil/pakdatakit).
|
|
37
|
+
It ships **the same datasets** and is tested against **shared test vectors**
|
|
38
|
+
generated from the TypeScript library, so both give identical results.
|
|
39
|
+
Pure Python, no dependencies, Python 3.9+.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pip install pakdatakit
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import datetime as dt
|
|
51
|
+
import pakdatakit as pdk
|
|
52
|
+
|
|
53
|
+
pdk.validate_cnic("35202-1234567-1") # True
|
|
54
|
+
pdk.get_cnic_info("35202-1234567-1")
|
|
55
|
+
# CNICInfo(digits='3520212345671', formatted='35202-1234567-1', province='Punjab', gender='male')
|
|
56
|
+
|
|
57
|
+
pdk.format_phone("0300 1234567") # '+923001234567'
|
|
58
|
+
pdk.get_network("0345-1234567") # 'Telenor'
|
|
59
|
+
pdk.create_whatsapp_link("03001234567", "Salam!")
|
|
60
|
+
|
|
61
|
+
pdk.validate_iban("PK36 SCBL 0000 0011 2345 6702") # True
|
|
62
|
+
pdk.get_bank_from_iban("PK36SCBL0000001123456702") # 'Standard Chartered Bank (Pakistan) Limited'
|
|
63
|
+
pdk.get_bank_info("MEZN").type # 'islamic'
|
|
64
|
+
|
|
65
|
+
pdk.get_province("Lahore") # 'Punjab'
|
|
66
|
+
pdk.parse_address("House 12, Street 5, Model Town Lahore 54000")
|
|
67
|
+
|
|
68
|
+
pdk.format_pkr(150000) # 'Rs. 150,000'
|
|
69
|
+
pdk.format_lakh(2500000) # '25 Lakh'
|
|
70
|
+
pdk.amount_in_words(125000, cheque=True) # 'Rupees One Lakh Twenty-Five Thousand Only'
|
|
71
|
+
pdk.number_to_words(125000, language="ur") # 'ایک لاکھ پچیس ہزار'
|
|
72
|
+
|
|
73
|
+
pdk.format_hijri(dt.date(2026, 3, 1)) # '12 Ramadan 1447'
|
|
74
|
+
pdk.get_pakistan_holidays(2026)[0] # Holiday(id='kashmir-day', ...)
|
|
75
|
+
|
|
76
|
+
payload = pdk.create_raast_p2p_qr("PK36SCBL0000001123456702", amount=1500, expires_in="1d")
|
|
77
|
+
pdk.validate_raast_qr(payload).valid # True
|
|
78
|
+
|
|
79
|
+
pdk.format_cnic_input("3520212") # '35202-12'
|
|
80
|
+
pdk.check_phone_input("0300 12") # 'incomplete'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
Every function of the npm package is available under its snake_case name
|
|
86
|
+
(`validateCNIC` → `validate_cnic`, `createRaastP2PQr` → `create_raast_p2p_qr`).
|
|
87
|
+
Option objects become keyword arguments (`{ expiresIn: '1d' }` →
|
|
88
|
+
`expires_in="1d"`), structured results are frozen dataclasses with snake_case
|
|
89
|
+
fields, `null` is `None`, and a `RangeError` is a `ValueError`. Dates are
|
|
90
|
+
`datetime.date` / naive local `datetime.datetime`.
|
|
91
|
+
|
|
92
|
+
`DATA_VERSION` gives the date the datasets were last checked, and
|
|
93
|
+
`__version__` the package version.
|
|
94
|
+
|
|
95
|
+
See the [main README](https://github.com/chmuzamil/pakdatakit#readme) for the full API
|
|
96
|
+
reference and [DATA.md](https://github.com/chmuzamil/pakdatakit/blob/main/DATA.md)
|
|
97
|
+
for where each dataset comes from.
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
From `python/`:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
uv run --with pytest pytest -q
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The data files in `src/pakdatakit/data/` and the vectors in `../test-vectors/`
|
|
108
|
+
are generated from the TypeScript package; don't edit them by hand. From the
|
|
109
|
+
repository root:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
npm run build && node scripts/generate-test-vectors.mjs
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# PakDataKit for Python
|
|
2
|
+
|
|
3
|
+
Utilities for Pakistani data: CNIC, mobile numbers and networks, WhatsApp links,
|
|
4
|
+
IBAN, postal codes, cities and districts, address parsing, Roman Urdu, Hijri
|
|
5
|
+
dates, public holidays, rupee formatting, amounts in words, NTN/STRN,
|
|
6
|
+
landlines and Raast QR codes.
|
|
7
|
+
|
|
8
|
+
This is the Python port of the [`pakdatakit` npm package](https://github.com/chmuzamil/pakdatakit).
|
|
9
|
+
It ships **the same datasets** and is tested against **shared test vectors**
|
|
10
|
+
generated from the TypeScript library, so both give identical results.
|
|
11
|
+
Pure Python, no dependencies, Python 3.9+.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pip install pakdatakit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import datetime as dt
|
|
23
|
+
import pakdatakit as pdk
|
|
24
|
+
|
|
25
|
+
pdk.validate_cnic("35202-1234567-1") # True
|
|
26
|
+
pdk.get_cnic_info("35202-1234567-1")
|
|
27
|
+
# CNICInfo(digits='3520212345671', formatted='35202-1234567-1', province='Punjab', gender='male')
|
|
28
|
+
|
|
29
|
+
pdk.format_phone("0300 1234567") # '+923001234567'
|
|
30
|
+
pdk.get_network("0345-1234567") # 'Telenor'
|
|
31
|
+
pdk.create_whatsapp_link("03001234567", "Salam!")
|
|
32
|
+
|
|
33
|
+
pdk.validate_iban("PK36 SCBL 0000 0011 2345 6702") # True
|
|
34
|
+
pdk.get_bank_from_iban("PK36SCBL0000001123456702") # 'Standard Chartered Bank (Pakistan) Limited'
|
|
35
|
+
pdk.get_bank_info("MEZN").type # 'islamic'
|
|
36
|
+
|
|
37
|
+
pdk.get_province("Lahore") # 'Punjab'
|
|
38
|
+
pdk.parse_address("House 12, Street 5, Model Town Lahore 54000")
|
|
39
|
+
|
|
40
|
+
pdk.format_pkr(150000) # 'Rs. 150,000'
|
|
41
|
+
pdk.format_lakh(2500000) # '25 Lakh'
|
|
42
|
+
pdk.amount_in_words(125000, cheque=True) # 'Rupees One Lakh Twenty-Five Thousand Only'
|
|
43
|
+
pdk.number_to_words(125000, language="ur") # 'ایک لاکھ پچیس ہزار'
|
|
44
|
+
|
|
45
|
+
pdk.format_hijri(dt.date(2026, 3, 1)) # '12 Ramadan 1447'
|
|
46
|
+
pdk.get_pakistan_holidays(2026)[0] # Holiday(id='kashmir-day', ...)
|
|
47
|
+
|
|
48
|
+
payload = pdk.create_raast_p2p_qr("PK36SCBL0000001123456702", amount=1500, expires_in="1d")
|
|
49
|
+
pdk.validate_raast_qr(payload).valid # True
|
|
50
|
+
|
|
51
|
+
pdk.format_cnic_input("3520212") # '35202-12'
|
|
52
|
+
pdk.check_phone_input("0300 12") # 'incomplete'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
Every function of the npm package is available under its snake_case name
|
|
58
|
+
(`validateCNIC` → `validate_cnic`, `createRaastP2PQr` → `create_raast_p2p_qr`).
|
|
59
|
+
Option objects become keyword arguments (`{ expiresIn: '1d' }` →
|
|
60
|
+
`expires_in="1d"`), structured results are frozen dataclasses with snake_case
|
|
61
|
+
fields, `null` is `None`, and a `RangeError` is a `ValueError`. Dates are
|
|
62
|
+
`datetime.date` / naive local `datetime.datetime`.
|
|
63
|
+
|
|
64
|
+
`DATA_VERSION` gives the date the datasets were last checked, and
|
|
65
|
+
`__version__` the package version.
|
|
66
|
+
|
|
67
|
+
See the [main README](https://github.com/chmuzamil/pakdatakit#readme) for the full API
|
|
68
|
+
reference and [DATA.md](https://github.com/chmuzamil/pakdatakit/blob/main/DATA.md)
|
|
69
|
+
for where each dataset comes from.
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
From `python/`:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
uv run --with pytest pytest -q
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The data files in `src/pakdatakit/data/` and the vectors in `../test-vectors/`
|
|
80
|
+
are generated from the TypeScript package; don't edit them by hand. From the
|
|
81
|
+
repository root:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
npm run build && node scripts/generate-test-vectors.mjs
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Releasing the Python package
|
|
2
|
+
|
|
3
|
+
The Python package `pakdatakit` is published to PyPI by
|
|
4
|
+
`.github/workflows/release-python.yml` when a tag like `python-v2.2.0` is
|
|
5
|
+
pushed. It uses [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/),
|
|
6
|
+
so no API token is stored. See the repository's [RELEASING.md](../RELEASING.md)
|
|
7
|
+
for the release order across all packages.
|
|
8
|
+
|
|
9
|
+
## Before tagging
|
|
10
|
+
|
|
11
|
+
- The version in `pyproject.toml` and `src/pakdatakit/__init__.py` matches the
|
|
12
|
+
npm `pakdatakit` version being released.
|
|
13
|
+
- If data changed, `src/pakdatakit/data_version.py` matches `src/data-version.ts`.
|
|
14
|
+
- Regenerate and commit the shared test vectors and data copies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run build
|
|
18
|
+
node scripts/generate-test-vectors.mjs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
CI and the release workflow fail if `test-vectors/` or
|
|
22
|
+
`python/src/pakdatakit/data/` differ from what this produces.
|
|
23
|
+
|
|
24
|
+
## What the workflow does
|
|
25
|
+
|
|
26
|
+
1. Checks the tag matches `pyproject.toml`.
|
|
27
|
+
2. Rebuilds the TypeScript library, regenerates the vectors and data, and fails
|
|
28
|
+
if they differ from the committed files.
|
|
29
|
+
3. Runs the tests on Python 3.9 and 3.13.
|
|
30
|
+
4. Builds the wheel and sdist with `uv build` and publishes them to PyPI.
|
|
31
|
+
5. Creates a GitHub release.
|
|
32
|
+
|
|
33
|
+
## One-time setup
|
|
34
|
+
|
|
35
|
+
On [pypi.org](https://pypi.org) → **Your account** → **Publishing** → **Add a
|
|
36
|
+
new pending publisher** → **GitHub**:
|
|
37
|
+
|
|
38
|
+
- PyPI project name: `pakdatakit`
|
|
39
|
+
- Owner: `chmuzamil`
|
|
40
|
+
- Repository name: `pakdatakit`
|
|
41
|
+
- Workflow name: `release-python.yml`
|
|
42
|
+
- Environment name: `pypi`
|
|
43
|
+
|
|
44
|
+
## Testing locally
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd python
|
|
48
|
+
uv run --python 3.9 --with pytest pytest -q
|
|
49
|
+
uv run --python 3.13 --with pytest pytest -q
|
|
50
|
+
uv build
|
|
51
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pakdatakit"
|
|
7
|
+
version = "2.2.0"
|
|
8
|
+
description = "Utilities for Pakistani data: CNIC, phone, IBAN, postal codes, address parsing, Hijri dates, holidays, amounts in words and Raast QR codes"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Muzamil Chaudhery" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"pakistan", "cnic", "phone", "iban", "postal", "hijri", "urdu",
|
|
15
|
+
"address", "ntn", "raast", "qr", "holidays", "amount-in-words",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Software Development :: Libraries",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/chmuzamil/pakdatakit"
|
|
35
|
+
Documentation = "https://github.com/chmuzamil/pakdatakit#readme"
|
|
36
|
+
Repository = "https://github.com/chmuzamil/pakdatakit"
|
|
37
|
+
Issues = "https://github.com/chmuzamil/pakdatakit/issues"
|
|
38
|
+
Changelog = "https://github.com/chmuzamil/pakdatakit/blob/main/CHANGELOG.md"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/pakdatakit"]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.sdist]
|
|
44
|
+
include = ["src/pakdatakit", "tests", "README.md", "RELEASING.md"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""PakDataKit: utilities for Pakistani data.
|
|
2
|
+
|
|
3
|
+
CNIC, phone, networks, WhatsApp, IBAN, postal codes, geography, address
|
|
4
|
+
parsing, Roman Urdu, Hijri dates, holidays, currency formatting, amounts in
|
|
5
|
+
words, tax numbers, landlines and Raast QR codes.
|
|
6
|
+
|
|
7
|
+
A Python port of the ``pakdatakit`` npm package: it ships the same datasets and
|
|
8
|
+
is tested against shared test vectors, so both give identical results.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .address import ParsedAddress, format_address, parse_address
|
|
12
|
+
from .cnic import CNICInfo, get_cnic_info, validate_cnic
|
|
13
|
+
from .data_version import DATA_VERSION, data_version
|
|
14
|
+
from .format import format_lakh, format_pkr
|
|
15
|
+
from .geo import (
|
|
16
|
+
get_district_province,
|
|
17
|
+
get_districts_by_province,
|
|
18
|
+
get_province,
|
|
19
|
+
search_cities,
|
|
20
|
+
search_districts,
|
|
21
|
+
)
|
|
22
|
+
from .hijri import (
|
|
23
|
+
HijriDate,
|
|
24
|
+
format_hijri,
|
|
25
|
+
from_hijri,
|
|
26
|
+
get_hijri_month_name,
|
|
27
|
+
is_ramadan,
|
|
28
|
+
to_hijri,
|
|
29
|
+
)
|
|
30
|
+
from .holidays import Holiday, get_holidays_on, get_pakistan_holidays, is_pakistan_holiday
|
|
31
|
+
from .iban import BankInfo, get_bank_from_iban, get_bank_info, get_banks, validate_iban
|
|
32
|
+
from .input import (
|
|
33
|
+
check_cnic_input,
|
|
34
|
+
check_iban_input,
|
|
35
|
+
check_phone_input,
|
|
36
|
+
format_cnic_input,
|
|
37
|
+
format_iban_input,
|
|
38
|
+
format_phone_input,
|
|
39
|
+
)
|
|
40
|
+
from .networks import get_network
|
|
41
|
+
from .numbers import (
|
|
42
|
+
NumberInfo,
|
|
43
|
+
analyze_number,
|
|
44
|
+
get_area_code,
|
|
45
|
+
get_number_type,
|
|
46
|
+
validate_landline,
|
|
47
|
+
)
|
|
48
|
+
from .phone import PhoneAnalysis, analyze_phone, format_phone, validate_phone
|
|
49
|
+
from .postal import (
|
|
50
|
+
get_city_by_postal_code,
|
|
51
|
+
get_postal_areas,
|
|
52
|
+
get_postal_code,
|
|
53
|
+
get_postal_codes,
|
|
54
|
+
search_postal_codes,
|
|
55
|
+
)
|
|
56
|
+
from .raast import (
|
|
57
|
+
EmvField,
|
|
58
|
+
MerchantAccountInfo,
|
|
59
|
+
ParsedRaastQr,
|
|
60
|
+
RaastQrValidation,
|
|
61
|
+
create_merchant_qr,
|
|
62
|
+
create_raast_p2p_qr,
|
|
63
|
+
parse_raast_qr,
|
|
64
|
+
validate_raast_qr,
|
|
65
|
+
)
|
|
66
|
+
from .tax import NTNInfo, analyze_ntn, validate_ntn, validate_strn
|
|
67
|
+
from .text import normalize_roman_urdu, normalize_text, to_english_digits, to_urdu_digits
|
|
68
|
+
from .whatsapp import create_whatsapp_link
|
|
69
|
+
from .words import amount_in_words, number_to_words
|
|
70
|
+
|
|
71
|
+
__version__ = "2.2.0"
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"__version__",
|
|
75
|
+
"DATA_VERSION",
|
|
76
|
+
"data_version",
|
|
77
|
+
# cnic
|
|
78
|
+
"CNICInfo",
|
|
79
|
+
"validate_cnic",
|
|
80
|
+
"get_cnic_info",
|
|
81
|
+
# phone
|
|
82
|
+
"PhoneAnalysis",
|
|
83
|
+
"validate_phone",
|
|
84
|
+
"format_phone",
|
|
85
|
+
"analyze_phone",
|
|
86
|
+
"get_network",
|
|
87
|
+
"create_whatsapp_link",
|
|
88
|
+
# iban
|
|
89
|
+
"validate_iban",
|
|
90
|
+
"get_bank_from_iban",
|
|
91
|
+
"BankInfo",
|
|
92
|
+
"get_bank_info",
|
|
93
|
+
"get_banks",
|
|
94
|
+
# postal
|
|
95
|
+
"get_city_by_postal_code",
|
|
96
|
+
"get_postal_code",
|
|
97
|
+
"get_postal_codes",
|
|
98
|
+
"get_postal_areas",
|
|
99
|
+
"search_postal_codes",
|
|
100
|
+
# geo
|
|
101
|
+
"get_province",
|
|
102
|
+
"search_cities",
|
|
103
|
+
"get_district_province",
|
|
104
|
+
"search_districts",
|
|
105
|
+
"get_districts_by_province",
|
|
106
|
+
# address
|
|
107
|
+
"ParsedAddress",
|
|
108
|
+
"parse_address",
|
|
109
|
+
"format_address",
|
|
110
|
+
# text
|
|
111
|
+
"normalize_text",
|
|
112
|
+
"normalize_roman_urdu",
|
|
113
|
+
"to_urdu_digits",
|
|
114
|
+
"to_english_digits",
|
|
115
|
+
# hijri
|
|
116
|
+
"HijriDate",
|
|
117
|
+
"to_hijri",
|
|
118
|
+
"from_hijri",
|
|
119
|
+
"format_hijri",
|
|
120
|
+
"get_hijri_month_name",
|
|
121
|
+
"is_ramadan",
|
|
122
|
+
# format / words
|
|
123
|
+
"format_pkr",
|
|
124
|
+
"format_lakh",
|
|
125
|
+
"number_to_words",
|
|
126
|
+
"amount_in_words",
|
|
127
|
+
# tax
|
|
128
|
+
"NTNInfo",
|
|
129
|
+
"analyze_ntn",
|
|
130
|
+
"validate_ntn",
|
|
131
|
+
"validate_strn",
|
|
132
|
+
# numbers
|
|
133
|
+
"NumberInfo",
|
|
134
|
+
"analyze_number",
|
|
135
|
+
"get_number_type",
|
|
136
|
+
"validate_landline",
|
|
137
|
+
"get_area_code",
|
|
138
|
+
# raast
|
|
139
|
+
"EmvField",
|
|
140
|
+
"MerchantAccountInfo",
|
|
141
|
+
"ParsedRaastQr",
|
|
142
|
+
"RaastQrValidation",
|
|
143
|
+
"create_raast_p2p_qr",
|
|
144
|
+
"create_merchant_qr",
|
|
145
|
+
"parse_raast_qr",
|
|
146
|
+
"validate_raast_qr",
|
|
147
|
+
# holidays
|
|
148
|
+
"Holiday",
|
|
149
|
+
"get_pakistan_holidays",
|
|
150
|
+
"get_holidays_on",
|
|
151
|
+
"is_pakistan_holiday",
|
|
152
|
+
# input
|
|
153
|
+
"format_cnic_input",
|
|
154
|
+
"check_cnic_input",
|
|
155
|
+
"format_phone_input",
|
|
156
|
+
"check_phone_input",
|
|
157
|
+
"format_iban_input",
|
|
158
|
+
"check_iban_input",
|
|
159
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Loads the bundled JSON datasets (shared with the npm package)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
try: # Python 3.9+
|
|
10
|
+
from importlib.resources import files as _files
|
|
11
|
+
except ImportError: # pragma: no cover
|
|
12
|
+
_files = None # type: ignore[assignment]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache(maxsize=None)
|
|
16
|
+
def load(name: str) -> Any:
|
|
17
|
+
"""Returns the parsed contents of ``pakdatakit/data/<name>``."""
|
|
18
|
+
text = _files("pakdatakit").joinpath("data").joinpath(name).read_text(encoding="utf-8")
|
|
19
|
+
return json.loads(text)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal helpers. Not part of the public API."""
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""EMVCo Merchant-Presented QR encoding (mirrors src/internal/emv.ts)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from .js import utf8_bytes
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class EmvField:
|
|
13
|
+
"""A parsed EMV field. ``children`` is set for template fields."""
|
|
14
|
+
|
|
15
|
+
id: str
|
|
16
|
+
value: str
|
|
17
|
+
children: Optional[List["EmvField"]] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EmvSyntaxError(ValueError):
|
|
21
|
+
"""Malformed EMV data."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_template_id(field_id: str) -> bool:
|
|
25
|
+
n = int(field_id)
|
|
26
|
+
return 26 <= n <= 51 or n == 62 or n == 64 or 80 <= n <= 99
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def crc16(text: str) -> str:
|
|
30
|
+
"""CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF), as 4 uppercase hex digits."""
|
|
31
|
+
crc = 0xFFFF
|
|
32
|
+
for byte in utf8_bytes(text):
|
|
33
|
+
crc ^= byte << 8
|
|
34
|
+
for _ in range(8):
|
|
35
|
+
crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF
|
|
36
|
+
return format(crc, "04X")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def encode_field(field_id: str, value: str) -> str:
|
|
40
|
+
"""Encodes one field. Length counts characters, as the EMV spec requires."""
|
|
41
|
+
length = len(value)
|
|
42
|
+
if length > 99:
|
|
43
|
+
raise ValueError(f"EMV field {field_id} is longer than 99 characters")
|
|
44
|
+
return f"{field_id}{length:02d}{value}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_digits(text: str) -> bool:
|
|
48
|
+
return all("0" <= c <= "9" for c in text)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_fields(data: str, nested: bool = True) -> List[EmvField]:
|
|
52
|
+
"""Parses EMV fields. Raises ``EmvSyntaxError`` if the data is malformed."""
|
|
53
|
+
chars = data
|
|
54
|
+
fields: List[EmvField] = []
|
|
55
|
+
i = 0
|
|
56
|
+
while i < len(chars):
|
|
57
|
+
header = chars[i : i + 4]
|
|
58
|
+
if len(header) != 4 or not _is_digits(header):
|
|
59
|
+
raise EmvSyntaxError(f"Malformed EMV field at position {i}")
|
|
60
|
+
field_id = header[:2]
|
|
61
|
+
length = int(header[2:])
|
|
62
|
+
if i + 4 + length > len(chars):
|
|
63
|
+
raise EmvSyntaxError(f"EMV field {field_id} is truncated")
|
|
64
|
+
value = chars[i + 4 : i + 4 + length]
|
|
65
|
+
children = parse_fields(value, False) if nested and is_template_id(field_id) else None
|
|
66
|
+
fields.append(EmvField(field_id, value, children))
|
|
67
|
+
i += 4 + length
|
|
68
|
+
return fields
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def with_crc(payload: str) -> str:
|
|
72
|
+
"""Appends the CRC field (63) to an encoded payload."""
|
|
73
|
+
data = f"{payload}6304"
|
|
74
|
+
return f"{data}{crc16(data)}"
|