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 +7 -0
- kenya_data/client.py +36 -0
- kenya_data/constituencies.py +39 -0
- kenya_data/counties.py +29 -0
- kenya_data/dataset.py +28 -0
- kenya_data/db.py +28 -0
- kenya_data/exceptions.py +14 -0
- kenya_data/kenya.db +0 -0
- kenya_data/models.py +38 -0
- kenya_data/wards.py +51 -0
- kenya_data-0.1.0.dist-info/METADATA +39 -0
- kenya_data-0.1.0.dist-info/RECORD +13 -0
- kenya_data-0.1.0.dist-info/WHEEL +4 -0
kenya_data/__init__.py
ADDED
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
|
kenya_data/exceptions.py
ADDED
|
@@ -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,,
|