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.
Files changed (41) hide show
  1. pakdatakit-2.2.0/.gitignore +7 -0
  2. pakdatakit-2.2.0/LICENSE +21 -0
  3. pakdatakit-2.2.0/PKG-INFO +117 -0
  4. pakdatakit-2.2.0/README.md +89 -0
  5. pakdatakit-2.2.0/RELEASING.md +51 -0
  6. pakdatakit-2.2.0/pyproject.toml +47 -0
  7. pakdatakit-2.2.0/src/pakdatakit/__init__.py +159 -0
  8. pakdatakit-2.2.0/src/pakdatakit/_data.py +19 -0
  9. pakdatakit-2.2.0/src/pakdatakit/_internal/__init__.py +1 -0
  10. pakdatakit-2.2.0/src/pakdatakit/_internal/emv.py +74 -0
  11. pakdatakit-2.2.0/src/pakdatakit/_internal/js.py +200 -0
  12. pakdatakit-2.2.0/src/pakdatakit/_internal/normalize.py +36 -0
  13. pakdatakit-2.2.0/src/pakdatakit/_internal/phone_utils.py +54 -0
  14. pakdatakit-2.2.0/src/pakdatakit/address.py +240 -0
  15. pakdatakit-2.2.0/src/pakdatakit/cnic.py +64 -0
  16. pakdatakit-2.2.0/src/pakdatakit/data/area-codes.json +294 -0
  17. pakdatakit-2.2.0/src/pakdatakit/data/banks.json +248 -0
  18. pakdatakit-2.2.0/src/pakdatakit/data/cities.json +906 -0
  19. pakdatakit-2.2.0/src/pakdatakit/data/districts.json +710 -0
  20. pakdatakit-2.2.0/src/pakdatakit/data/holidays.json +154 -0
  21. pakdatakit-2.2.0/src/pakdatakit/data/networks.json +55 -0
  22. pakdatakit-2.2.0/src/pakdatakit/data/postal-codes.json +641 -0
  23. pakdatakit-2.2.0/src/pakdatakit/data_version.py +8 -0
  24. pakdatakit-2.2.0/src/pakdatakit/format.py +64 -0
  25. pakdatakit-2.2.0/src/pakdatakit/geo.py +98 -0
  26. pakdatakit-2.2.0/src/pakdatakit/hijri.py +169 -0
  27. pakdatakit-2.2.0/src/pakdatakit/holidays.py +171 -0
  28. pakdatakit-2.2.0/src/pakdatakit/iban.py +116 -0
  29. pakdatakit-2.2.0/src/pakdatakit/input.py +141 -0
  30. pakdatakit-2.2.0/src/pakdatakit/networks.py +31 -0
  31. pakdatakit-2.2.0/src/pakdatakit/numbers.py +175 -0
  32. pakdatakit-2.2.0/src/pakdatakit/phone.py +48 -0
  33. pakdatakit-2.2.0/src/pakdatakit/postal.py +95 -0
  34. pakdatakit-2.2.0/src/pakdatakit/py.typed +0 -0
  35. pakdatakit-2.2.0/src/pakdatakit/raast.py +498 -0
  36. pakdatakit-2.2.0/src/pakdatakit/tax.py +61 -0
  37. pakdatakit-2.2.0/src/pakdatakit/text.py +66 -0
  38. pakdatakit-2.2.0/src/pakdatakit/whatsapp.py +24 -0
  39. pakdatakit-2.2.0/src/pakdatakit/words.py +130 -0
  40. pakdatakit-2.2.0/tests/test_api.py +148 -0
  41. pakdatakit-2.2.0/tests/test_vectors.py +166 -0
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ dist/
3
+ build/
4
+ __pycache__/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ uv.lock
@@ -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)}"