tanzania-postcodes 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,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: tanzania-postcodes
3
+ Version: 0.1.0
4
+ Summary: Real Tanzania postal codes, sourced directly from TCRA's official gazetted National Postcode System -- not a third-party compilation.
5
+ Author-email: Freddy Nyanda <freddynyanda@proton.me>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/nyandajr/tanzania-postcodes
8
+ Project-URL: Repository, https://github.com/nyandajr/tanzania-postcodes
9
+ Project-URL: Source, https://www.tcra.go.tz/services/publication-of-postcode-list
10
+ Keywords: tanzania,postcode,postal-code,zip-code,tcra,address
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
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 :: Python Modules
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+
22
+ # tanzania-postcodes
23
+
24
+ Real Tanzania postal codes, sourced directly from **TCRA's official
25
+ gazetted National Postcode System** (Government Gazette Notice No. 240,
26
+ 22 April 2016) — not a third-party site's compiled or scraped version.
27
+
28
+ No Python package for Tanzania postcodes existed before this one.
29
+ [`pgeocode`](https://pypi.org/project/pgeocode/), the most common
30
+ general-purpose postal code library, explicitly doesn't support Tanzania
31
+ (`ValueError: country=TZ is not a known country code`) — its data comes
32
+ from GeoNames, which has no real Tanzania coverage.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install tanzania-postcodes
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```python
43
+ import tanzania_postcodes as tzpc
44
+
45
+ # Look up everything at a postcode
46
+ tzpc.lookup("16112")
47
+ # [Postcode(region='Dar Es Salaam', district='UBUNGO', ward='Goba',
48
+ # postcode='16112', locality='Goba', ...), ...]
49
+
50
+ # Search by name (ward, district, region, or locality)
51
+ tzpc.search("Goba")[0].postcode
52
+ # '16112'
53
+
54
+ # List all 26 regions
55
+ tzpc.regions()
56
+
57
+ # List all wards, optionally scoped to one region
58
+ tzpc.wards("Dar Es Salaam")
59
+ ```
60
+
61
+ Each result is a `Postcode`:
62
+
63
+ ```python
64
+ Postcode(
65
+ region: str, # e.g. "Dar Es Salaam"
66
+ district: str | None, # e.g. "UBUNGO"
67
+ ward: str, # e.g. "Goba" -- the unit TCRA's postcode identifies
68
+ postcode: str, # e.g. "16112"
69
+ locality: str | None, # mtaa/village within the ward
70
+ sub_locality: str | None, # kitongoji, where the source PDF has one
71
+ old_postcode: str | None, # legacy code, where the source PDF has one
72
+ district_postcode: str | None,
73
+ )
74
+ ```
75
+
76
+ ## Coverage
77
+
78
+ All **26 regions**, **67,000+ locality-level records**, **3,900+ unique
79
+ ward postcodes**. A postcode identifies a *ward* — the same postcode
80
+ applies to every village/mtaa within it, which is why `lookup()` usually
81
+ returns multiple records for one code.
82
+
83
+ ## Where this data comes from, and how it was built
84
+
85
+ TCRA publishes the postcode list as 26 separate regional PDFs (no
86
+ CSV/Excel version exists) at
87
+ [tcra.go.tz/services/publication-of-postcode-list](https://www.tcra.go.tz/services/publication-of-postcode-list).
88
+ Each PDF's table uses merged cells for repeated values, and — found
89
+ during development — **three different column layouts** across the 26
90
+ files (some have a separate District level, some don't; some have an Old
91
+ Postcode column, some don't; one region's table has no header row at
92
+ all). `scripts/parse_pdfs.py` detects each table's actual header and
93
+ maps columns by name rather than assuming one fixed schema, with a
94
+ documented fallback for the one region missing a header entirely.
95
+
96
+ `scripts/download_pdfs.py` and `scripts/parse_pdfs.py` are included in
97
+ this repo if you want to verify the extraction yourself or regenerate
98
+ the dataset from TCRA's source PDFs directly.
99
+
100
+ ## Why this exists
101
+
102
+ Built after actually needing a Tanzania postcode (Goba ward, Dar es
103
+ Salaam — `16112`), confirming no Python package or well-covered general
104
+ library had it, and finding TCRA's own official data was real, current,
105
+ and genuinely extractable — just never turned into a package before.
106
+
107
+ ## License
108
+
109
+ MIT for this package's code. The underlying postcode data is Tanzania
110
+ government public record (Government Gazette Notice No. 240, 2016).
@@ -0,0 +1,89 @@
1
+ # tanzania-postcodes
2
+
3
+ Real Tanzania postal codes, sourced directly from **TCRA's official
4
+ gazetted National Postcode System** (Government Gazette Notice No. 240,
5
+ 22 April 2016) — not a third-party site's compiled or scraped version.
6
+
7
+ No Python package for Tanzania postcodes existed before this one.
8
+ [`pgeocode`](https://pypi.org/project/pgeocode/), the most common
9
+ general-purpose postal code library, explicitly doesn't support Tanzania
10
+ (`ValueError: country=TZ is not a known country code`) — its data comes
11
+ from GeoNames, which has no real Tanzania coverage.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install tanzania-postcodes
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```python
22
+ import tanzania_postcodes as tzpc
23
+
24
+ # Look up everything at a postcode
25
+ tzpc.lookup("16112")
26
+ # [Postcode(region='Dar Es Salaam', district='UBUNGO', ward='Goba',
27
+ # postcode='16112', locality='Goba', ...), ...]
28
+
29
+ # Search by name (ward, district, region, or locality)
30
+ tzpc.search("Goba")[0].postcode
31
+ # '16112'
32
+
33
+ # List all 26 regions
34
+ tzpc.regions()
35
+
36
+ # List all wards, optionally scoped to one region
37
+ tzpc.wards("Dar Es Salaam")
38
+ ```
39
+
40
+ Each result is a `Postcode`:
41
+
42
+ ```python
43
+ Postcode(
44
+ region: str, # e.g. "Dar Es Salaam"
45
+ district: str | None, # e.g. "UBUNGO"
46
+ ward: str, # e.g. "Goba" -- the unit TCRA's postcode identifies
47
+ postcode: str, # e.g. "16112"
48
+ locality: str | None, # mtaa/village within the ward
49
+ sub_locality: str | None, # kitongoji, where the source PDF has one
50
+ old_postcode: str | None, # legacy code, where the source PDF has one
51
+ district_postcode: str | None,
52
+ )
53
+ ```
54
+
55
+ ## Coverage
56
+
57
+ All **26 regions**, **67,000+ locality-level records**, **3,900+ unique
58
+ ward postcodes**. A postcode identifies a *ward* — the same postcode
59
+ applies to every village/mtaa within it, which is why `lookup()` usually
60
+ returns multiple records for one code.
61
+
62
+ ## Where this data comes from, and how it was built
63
+
64
+ TCRA publishes the postcode list as 26 separate regional PDFs (no
65
+ CSV/Excel version exists) at
66
+ [tcra.go.tz/services/publication-of-postcode-list](https://www.tcra.go.tz/services/publication-of-postcode-list).
67
+ Each PDF's table uses merged cells for repeated values, and — found
68
+ during development — **three different column layouts** across the 26
69
+ files (some have a separate District level, some don't; some have an Old
70
+ Postcode column, some don't; one region's table has no header row at
71
+ all). `scripts/parse_pdfs.py` detects each table's actual header and
72
+ maps columns by name rather than assuming one fixed schema, with a
73
+ documented fallback for the one region missing a header entirely.
74
+
75
+ `scripts/download_pdfs.py` and `scripts/parse_pdfs.py` are included in
76
+ this repo if you want to verify the extraction yourself or regenerate
77
+ the dataset from TCRA's source PDFs directly.
78
+
79
+ ## Why this exists
80
+
81
+ Built after actually needing a Tanzania postcode (Goba ward, Dar es
82
+ Salaam — `16112`), confirming no Python package or well-covered general
83
+ library had it, and finding TCRA's own official data was real, current,
84
+ and genuinely extractable — just never turned into a package before.
85
+
86
+ ## License
87
+
88
+ MIT for this package's code. The underlying postcode data is Tanzania
89
+ government public record (Government Gazette Notice No. 240, 2016).
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tanzania-postcodes"
7
+ version = "0.1.0"
8
+ description = "Real Tanzania postal codes, sourced directly from TCRA's official gazetted National Postcode System -- not a third-party compilation."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "Freddy Nyanda", email = "freddynyanda@proton.me" }]
13
+ keywords = ["tanzania", "postcode", "postal-code", "zip-code", "tcra", "address"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/nyandajr/tanzania-postcodes"
27
+ Repository = "https://github.com/nyandajr/tanzania-postcodes"
28
+ Source = "https://www.tcra.go.tz/services/publication-of-postcode-list"
29
+
30
+ [tool.setuptools.packages.find]
31
+ include = ["tanzania_postcodes*"]
32
+
33
+ [tool.setuptools.package-data]
34
+ tanzania_postcodes = ["data/*.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,86 @@
1
+ """tanzania-postcodes: real Tanzania postal codes, sourced directly from
2
+ TCRA's official gazetted National Postcode System (Government Gazette
3
+ Notice No. 240, 22 April 2016) -- not a third-party's compiled version.
4
+
5
+ >>> import tanzania_postcodes as tzpc
6
+ >>> tzpc.lookup("16112")[0].ward
7
+ 'Goba'
8
+ >>> tzpc.search("Goba")[0].postcode
9
+ '16112'
10
+ """
11
+
12
+ import json
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ _DATA_PATH = Path(__file__).parent / "data" / "postcodes.json"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Postcode:
24
+ """One locality-level record. Multiple localities share the same
25
+ ward-level `postcode` -- that's the actual unit TCRA's system
26
+ identifies, not the individual village/mtaa.
27
+ """
28
+ region: str
29
+ district: Optional[str]
30
+ ward: str
31
+ postcode: str
32
+ locality: Optional[str]
33
+ sub_locality: Optional[str]
34
+ old_postcode: Optional[str] = None
35
+ district_postcode: Optional[str] = None
36
+
37
+
38
+ def _load():
39
+ with open(_DATA_PATH, encoding="utf-8") as f:
40
+ raw = json.load(f)
41
+ return [Postcode(**r) for r in raw]
42
+
43
+
44
+ _RECORDS = None
45
+
46
+
47
+ def _records():
48
+ global _RECORDS
49
+ if _RECORDS is None:
50
+ _RECORDS = _load()
51
+ return _RECORDS
52
+
53
+
54
+ def lookup(postcode: str) -> list[Postcode]:
55
+ """All records sharing this exact postcode (usually several -- one
56
+ ward maps to multiple villages/mtaa)."""
57
+ postcode = postcode.strip()
58
+ return [r for r in _records() if r.postcode == postcode]
59
+
60
+
61
+ def search(query: str) -> list[Postcode]:
62
+ """Case-insensitive substring search across ward, district, region,
63
+ locality, and sub_locality names."""
64
+ q = query.strip().lower()
65
+ if not q:
66
+ return []
67
+ results = []
68
+ for r in _records():
69
+ haystack = " ".join(filter(None, [r.region, r.district, r.ward, r.locality, r.sub_locality])).lower()
70
+ if q in haystack:
71
+ results.append(r)
72
+ return results
73
+
74
+
75
+ def regions() -> list[str]:
76
+ """All 26 regions with postcode data."""
77
+ return sorted({r.region for r in _records()})
78
+
79
+
80
+ def wards(region: Optional[str] = None) -> list[str]:
81
+ """All ward names, optionally filtered to one region."""
82
+ recs = _records() if region is None else [r for r in _records() if r.region.lower() == region.lower()]
83
+ return sorted({r.ward for r in recs})
84
+
85
+
86
+ __all__ = ["Postcode", "lookup", "search", "regions", "wards", "__version__"]