kenya-data 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.
kenya_data/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Kenya Data — Python SDK."""
2
+
3
+ from .client import Kenya
4
+ from .exceptions import EntityNotFoundError
5
+
6
+ __all__ = ["EntityNotFoundError", "Kenya"]
7
+ __version__ = "0.1.0"
kenya_data/client.py ADDED
@@ -0,0 +1,36 @@
1
+ """The top-level Kenya Data client."""
2
+ from __future__ import annotations
3
+
4
+ import sqlite3
5
+ from pathlib import Path
6
+
7
+ from .constituencies import Constituencies
8
+ from .counties import Counties
9
+ from .dataset import Dataset
10
+ from .db import connect
11
+ from .wards import Wards
12
+
13
+
14
+ class Kenya:
15
+ """Entry point for the Kenya Data SDK.
16
+
17
+ >>> kenya = Kenya()
18
+ >>> kenya.counties.list() # doctest: +SKIP
19
+ >>> kenya.counties.get("Nakuru") # doctest: +SKIP
20
+ """
21
+
22
+ def __init__(self, db_path: str | Path | None = None) -> None:
23
+ self._conn: sqlite3.Connection = connect(db_path)
24
+ self.counties = Counties(self._conn)
25
+ self.constituencies = Constituencies(self._conn)
26
+ self.wards = Wards(self._conn)
27
+ self.dataset = Dataset(self._conn)
28
+
29
+ def close(self) -> None:
30
+ self._conn.close()
31
+
32
+ def __enter__(self) -> Kenya: # noqa: PYI034 (Self requires Python >=3.11)
33
+ return self
34
+
35
+ def __exit__(self, *exc_info: object) -> None:
36
+ self.close()
@@ -0,0 +1,39 @@
1
+ """Constituency lookups."""
2
+ from __future__ import annotations
3
+
4
+ import builtins
5
+ import sqlite3
6
+
7
+ from .counties import Counties
8
+ from .exceptions import EntityNotFoundError
9
+ from .models import Constituency
10
+
11
+
12
+ class Constituencies:
13
+ def __init__(self, conn: sqlite3.Connection) -> None:
14
+ self._conn = conn
15
+
16
+ def list(self) -> builtins.list[Constituency]:
17
+ rows = self._conn.execute(
18
+ "SELECT id, code, name, county_id, status FROM constituencies ORDER BY name"
19
+ ).fetchall()
20
+ return [Constituency(**dict(r)) for r in rows]
21
+
22
+ def get(self, name_or_code: str) -> Constituency:
23
+ row = self._conn.execute(
24
+ "SELECT id, code, name, county_id, status FROM constituencies "
25
+ "WHERE name = ? COLLATE NOCASE OR code = ? COLLATE NOCASE",
26
+ (name_or_code, name_or_code),
27
+ ).fetchone()
28
+ if row is None:
29
+ raise EntityNotFoundError("constituency", name_or_code)
30
+ return Constituency(**dict(row))
31
+
32
+ def in_county(self, county_name_or_code: str) -> builtins.list[Constituency]:
33
+ county = Counties(self._conn).get(county_name_or_code)
34
+ rows = self._conn.execute(
35
+ "SELECT id, code, name, county_id, status FROM constituencies "
36
+ "WHERE county_id = ? ORDER BY name",
37
+ (county.id,),
38
+ ).fetchall()
39
+ return [Constituency(**dict(r)) for r in rows]
kenya_data/counties.py ADDED
@@ -0,0 +1,29 @@
1
+ """County lookups."""
2
+ from __future__ import annotations
3
+
4
+ import builtins
5
+ import sqlite3
6
+
7
+ from .exceptions import EntityNotFoundError
8
+ from .models import County
9
+
10
+
11
+ class Counties:
12
+ def __init__(self, conn: sqlite3.Connection) -> None:
13
+ self._conn = conn
14
+
15
+ def list(self) -> builtins.list[County]:
16
+ rows = self._conn.execute(
17
+ "SELECT id, code, name, capital, status FROM counties ORDER BY name"
18
+ ).fetchall()
19
+ return [County(**dict(r)) for r in rows]
20
+
21
+ def get(self, name_or_code: str) -> County:
22
+ row = self._conn.execute(
23
+ "SELECT id, code, name, capital, status FROM counties "
24
+ "WHERE name = ? COLLATE NOCASE OR code = ? COLLATE NOCASE",
25
+ (name_or_code, name_or_code),
26
+ ).fetchone()
27
+ if row is None:
28
+ raise EntityNotFoundError("county", name_or_code)
29
+ return County(**dict(row))
kenya_data/dataset.py ADDED
@@ -0,0 +1,28 @@
1
+ """Dataset (as opposed to SDK) version metadata."""
2
+ from __future__ import annotations
3
+
4
+ import sqlite3
5
+
6
+ from .models import DatasetInfo
7
+
8
+
9
+ class Dataset:
10
+ def __init__(self, conn: sqlite3.Connection) -> None:
11
+ self._conn = conn
12
+
13
+ def info(self) -> DatasetInfo:
14
+ row = self._conn.execute(
15
+ "SELECT version, status, created_at FROM dataset_versions "
16
+ "ORDER BY id DESC LIMIT 1"
17
+ ).fetchone()
18
+ if row is None:
19
+ return DatasetInfo(version="unknown", status="unknown", updated_at="unknown")
20
+ return DatasetInfo(version=row["version"], status=row["status"], updated_at=row["created_at"])
21
+
22
+ @property
23
+ def version(self) -> str:
24
+ return self.info().version
25
+
26
+ @property
27
+ def updated_at(self) -> str:
28
+ return self.info().updated_at
kenya_data/db.py ADDED
@@ -0,0 +1,28 @@
1
+ """Connection handling and packaged database resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ from importlib.resources import files
7
+ from pathlib import Path
8
+
9
+
10
+ def default_db_path() -> Path:
11
+ packaged = Path(str(files("kenya_data").joinpath("kenya.db")))
12
+ if packaged.exists():
13
+ return packaged
14
+
15
+ # Local repository fallback.
16
+ return Path(__file__).resolve().parents[4] / "data" / "kenya.db"
17
+
18
+
19
+ def connect(db_path: str | Path | None = None) -> sqlite3.Connection:
20
+ path = Path(db_path) if db_path is not None else default_db_path()
21
+
22
+ if not path.exists():
23
+ raise FileNotFoundError(f"No Kenya Data database found at {path}")
24
+
25
+ conn = sqlite3.connect(path)
26
+ conn.row_factory = sqlite3.Row
27
+ conn.execute("PRAGMA foreign_keys = ON")
28
+ return conn
@@ -0,0 +1,14 @@
1
+ """Exceptions raised by the Kenya Data SDK."""
2
+
3
+
4
+ class KenyaDataError(Exception):
5
+ """Base class for all kenya-data SDK errors."""
6
+
7
+
8
+ class EntityNotFoundError(KenyaDataError):
9
+ """Raised when a lookup does not match any entity."""
10
+
11
+ def __init__(self, entity_type: str, identifier: str) -> None:
12
+ self.entity_type = entity_type
13
+ self.identifier = identifier
14
+ super().__init__(f"No {entity_type} found matching {identifier!r}")
kenya_data/kenya.db ADDED
Binary file
kenya_data/models.py ADDED
@@ -0,0 +1,38 @@
1
+ """Lightweight, typed record wrappers returned by the SDK."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class County:
9
+ id: int
10
+ code: str
11
+ name: str
12
+ capital: str | None
13
+ status: str
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Constituency:
18
+ id: int
19
+ code: str
20
+ name: str
21
+ county_id: int
22
+ status: str
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class Ward:
27
+ id: int
28
+ code: str
29
+ name: str
30
+ constituency_id: int
31
+ status: str
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class DatasetInfo:
36
+ version: str
37
+ status: str
38
+ updated_at: str
kenya_data/wards.py ADDED
@@ -0,0 +1,51 @@
1
+ """Ward lookups."""
2
+ from __future__ import annotations
3
+
4
+ import builtins
5
+ import sqlite3
6
+
7
+ from .constituencies import Constituencies
8
+ from .exceptions import EntityNotFoundError
9
+ from .models import Ward
10
+
11
+
12
+ class Wards:
13
+ def __init__(self, conn: sqlite3.Connection) -> None:
14
+ self._conn = conn
15
+
16
+ def list(self) -> builtins.list[Ward]:
17
+ rows = self._conn.execute(
18
+ "SELECT id, code, name, constituency_id, status FROM wards ORDER BY name"
19
+ ).fetchall()
20
+ return [Ward(**dict(r)) for r in rows]
21
+
22
+ def get(self, name_or_code: str) -> Ward:
23
+ row = self._conn.execute(
24
+ "SELECT id, code, name, constituency_id, status FROM wards "
25
+ "WHERE name = ? COLLATE NOCASE OR code = ? COLLATE NOCASE",
26
+ (name_or_code, name_or_code),
27
+ ).fetchone()
28
+ if row is None:
29
+ raise EntityNotFoundError("ward", name_or_code)
30
+ return Ward(**dict(row))
31
+
32
+ def in_constituency(self, constituency_name_or_code: str) -> builtins.list[Ward]:
33
+ constituency = Constituencies(self._conn).get(constituency_name_or_code)
34
+ rows = self._conn.execute(
35
+ "SELECT id, code, name, constituency_id, status FROM wards "
36
+ "WHERE constituency_id = ? ORDER BY name",
37
+ (constituency.id,),
38
+ ).fetchall()
39
+ return [Ward(**dict(r)) for r in rows]
40
+
41
+ def in_county(self, county_name_or_code: str) -> builtins.list[Ward]:
42
+ rows = self._conn.execute(
43
+ """SELECT wards.id, wards.code, wards.name, wards.constituency_id, wards.status
44
+ FROM wards
45
+ JOIN constituencies ON constituencies.id = wards.constituency_id
46
+ JOIN counties ON counties.id = constituencies.county_id
47
+ WHERE counties.name = ? COLLATE NOCASE OR counties.code = ? COLLATE NOCASE
48
+ ORDER BY wards.name""",
49
+ (county_name_or_code, county_name_or_code),
50
+ ).fetchall()
51
+ return [Ward(**dict(r)) for r in rows]
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.5
2
+ Name: kenya-data
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Kenya Data structured dataset
5
+ Project-URL: Homepage, https://github.com/ToshGitonga0/kenya-data
6
+ Project-URL: Repository, https://github.com/ToshGitonga0/kenya-data
7
+ Project-URL: Issues, https://github.com/ToshGitonga0/kenya-data/issues
8
+ Author: Kenya Data contributors
9
+ License: MIT
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.10
14
+ Provides-Extra: dev
15
+ Requires-Dist: mypy>=1.0; extra == 'dev'
16
+ Requires-Dist: pytest>=7.0; extra == 'dev'
17
+ Requires-Dist: ruff>=0.4; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # kenya-data (Python SDK)
21
+
22
+ ```python
23
+ from kenya_data import Kenya
24
+
25
+ kenya = Kenya() # loads the bundled data/kenya.db by default
26
+ kenya = Kenya(db_path="/path/to/kenya.db") # or point at a specific build
27
+
28
+ kenya.counties.list()
29
+ kenya.counties.get("Nakuru")
30
+ kenya.constituencies.in_county("Nakuru")
31
+ kenya.wards.in_constituency("Naivasha")
32
+
33
+ kenya.dataset.version
34
+ kenya.dataset.updated_at
35
+ ```
36
+
37
+ Backed by the SQLite database built from `database/schema/schema.sql` —
38
+ see the repository root [README](../../README.md) and
39
+ [docs/sdk-design.md](../../docs/sdk-design.md).
@@ -0,0 +1,13 @@
1
+ kenya_data/__init__.py,sha256=gW9PvFotPYtxoUFbL1y9EwUo6k6WG44V6ZlfKqRFd0c,170
2
+ kenya_data/client.py,sha256=cHaKkc7_Z9EXNvXIv-TPDMXNUEK-ReIGXVU8U_01dAw,1012
3
+ kenya_data/constituencies.py,sha256=jkJ8pnjpROhSoTsH0ealArX4bQMjKlo8DGRqrXtieNM,1405
4
+ kenya_data/counties.py,sha256=aJJKBRXihHLy7tlJfzee75V0UHLQtLYWGnXQksVvdgc,902
5
+ kenya_data/dataset.py,sha256=irm3Zp4xPpSy98GUIye2NB9srEBSMQDkRY_Sqlnx9cQ,822
6
+ kenya_data/db.py,sha256=ag6VsYup_Qks_yXRpQOP4p-wdx2tKeRKdP04T6DGpTY,803
7
+ kenya_data/exceptions.py,sha256=t32YbufRyu-S1EGUgLDCWINNOpJRi9qcWzNeOwZNtQM,457
8
+ kenya_data/models.py,sha256=f7WzA5FUg0y76R8Beko9JFtAGkW4mB_HisEaqVy1PsI,590
9
+ kenya_data/wards.py,sha256=HtWIBFQDV1oQAZ18qTjqLgi9piVkUtly2PFI2QYlax0,2001
10
+ kenya_data/kenya.db,sha256=KE6py0ekepZxKShKehfkwyuMz8ZuWHdJ5Y0R4zsSn_g,466944
11
+ kenya_data-0.1.0.dist-info/METADATA,sha256=umj1lHI8bDDBJbmUMqtLfvZ0ROdl1wgA_HWnkoiHo7U,1305
12
+ kenya_data-0.1.0.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
13
+ kenya_data-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any