kenya-data 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.
- kenya_data-0.1.0/.gitignore +30 -0
- kenya_data-0.1.0/PKG-INFO +39 -0
- kenya_data-0.1.0/README.md +20 -0
- kenya_data-0.1.0/pyproject.toml +42 -0
- kenya_data-0.1.0/src/kenya_data/__init__.py +7 -0
- kenya_data-0.1.0/src/kenya_data/client.py +36 -0
- kenya_data-0.1.0/src/kenya_data/constituencies.py +39 -0
- kenya_data-0.1.0/src/kenya_data/counties.py +29 -0
- kenya_data-0.1.0/src/kenya_data/dataset.py +28 -0
- kenya_data-0.1.0/src/kenya_data/db.py +28 -0
- kenya_data-0.1.0/src/kenya_data/exceptions.py +14 -0
- kenya_data-0.1.0/src/kenya_data/kenya.db +0 -0
- kenya_data-0.1.0/src/kenya_data/models.py +38 -0
- kenya_data-0.1.0/src/kenya_data/wards.py +51 -0
- kenya_data-0.1.0/tests/conftest.py +18 -0
- kenya_data-0.1.0/tests/test_client.py +57 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
|
|
13
|
+
# Node / TypeScript
|
|
14
|
+
node_modules/
|
|
15
|
+
packages/typescript/dist/
|
|
16
|
+
packages/typescript/coverage/
|
|
17
|
+
npm-debug.log*
|
|
18
|
+
|
|
19
|
+
# Database (generated artifact — rebuilt via `make build-db`)
|
|
20
|
+
data/kenya.db
|
|
21
|
+
data/kenya.db-journal
|
|
22
|
+
|
|
23
|
+
# Editors / OS
|
|
24
|
+
.vscode/
|
|
25
|
+
.idea/
|
|
26
|
+
.DS_Store
|
|
27
|
+
|
|
28
|
+
# Env
|
|
29
|
+
.env
|
|
30
|
+
.env.local
|
|
@@ -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,20 @@
|
|
|
1
|
+
# kenya-data (Python SDK)
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
from kenya_data import Kenya
|
|
5
|
+
|
|
6
|
+
kenya = Kenya() # loads the bundled data/kenya.db by default
|
|
7
|
+
kenya = Kenya(db_path="/path/to/kenya.db") # or point at a specific build
|
|
8
|
+
|
|
9
|
+
kenya.counties.list()
|
|
10
|
+
kenya.counties.get("Nakuru")
|
|
11
|
+
kenya.constituencies.in_county("Nakuru")
|
|
12
|
+
kenya.wards.in_constituency("Naivasha")
|
|
13
|
+
|
|
14
|
+
kenya.dataset.version
|
|
15
|
+
kenya.dataset.updated_at
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Backed by the SQLite database built from `database/schema/schema.sql` —
|
|
19
|
+
see the repository root [README](../../README.md) and
|
|
20
|
+
[docs/sdk-design.md](../../docs/sdk-design.md).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kenya-data"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the Kenya Data structured dataset"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Kenya Data contributors" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = []
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/ToshGitonga0/kenya-data"
|
|
22
|
+
Repository = "https://github.com/ToshGitonga0/kenya-data"
|
|
23
|
+
Issues = "https://github.com/ToshGitonga0/kenya-data/issues"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=7.0", "mypy>=1.0", "ruff>=0.4"]
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["src/kenya_data"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
32
|
+
"src/kenya_data/kenya.db" = "kenya_data/kenya.db"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
|
|
37
|
+
[tool.mypy]
|
|
38
|
+
python_version = "3.10"
|
|
39
|
+
strict = true
|
|
40
|
+
|
|
41
|
+
[tool.ruff]
|
|
42
|
+
line-length = 100
|
|
@@ -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]
|
|
@@ -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))
|
|
@@ -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
|
|
@@ -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}")
|
|
Binary file
|
|
@@ -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
|
|
@@ -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,18 @@
|
|
|
1
|
+
"""Builds a throwaway SQLite database from the real approved data for each test session."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(scope="session")
|
|
14
|
+
def test_db_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
|
15
|
+
db_path = tmp_path_factory.mktemp("kenya-data-db") / "kenya.db"
|
|
16
|
+
seed_script = REPO_ROOT / "database" / "seeds" / "load_approved_data.py"
|
|
17
|
+
subprocess.run([sys.executable, str(seed_script), str(db_path)], check=True)
|
|
18
|
+
return db_path
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from kenya_data import EntityNotFoundError, Kenya
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_counties_list_returns_real_counties(test_db_path: Path) -> None:
|
|
11
|
+
kenya = Kenya(db_path=test_db_path)
|
|
12
|
+
counties = kenya.counties.list()
|
|
13
|
+
assert len(counties) > 0
|
|
14
|
+
assert all(c.name for c in counties)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_counties_get_by_name_case_insensitive(test_db_path: Path) -> None:
|
|
18
|
+
kenya = Kenya(db_path=test_db_path)
|
|
19
|
+
county = kenya.counties.get("nakuru")
|
|
20
|
+
assert county.name.lower() == "nakuru"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_counties_get_unknown_raises(test_db_path: Path) -> None:
|
|
24
|
+
kenya = Kenya(db_path=test_db_path)
|
|
25
|
+
with pytest.raises(EntityNotFoundError):
|
|
26
|
+
kenya.counties.get("Not A Real County")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_constituencies_in_county(test_db_path: Path) -> None:
|
|
30
|
+
kenya = Kenya(db_path=test_db_path)
|
|
31
|
+
constituencies = kenya.constituencies.in_county("Nakuru")
|
|
32
|
+
assert len(constituencies) > 0
|
|
33
|
+
assert all(c.county_id == kenya.counties.get("Nakuru").id for c in constituencies)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_wards_in_constituency(test_db_path: Path) -> None:
|
|
37
|
+
kenya = Kenya(db_path=test_db_path)
|
|
38
|
+
constituency = kenya.constituencies.list()[0]
|
|
39
|
+
wards = kenya.wards.in_constituency(constituency.code)
|
|
40
|
+
assert all(w.constituency_id == constituency.id for w in wards)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_wards_in_county_traverses_hierarchy(test_db_path: Path) -> None:
|
|
44
|
+
kenya = Kenya(db_path=test_db_path)
|
|
45
|
+
wards = kenya.wards.in_county("Nakuru")
|
|
46
|
+
assert len(wards) > 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_dataset_metadata_present(test_db_path: Path) -> None:
|
|
50
|
+
kenya = Kenya(db_path=test_db_path)
|
|
51
|
+
assert kenya.dataset.version
|
|
52
|
+
assert kenya.dataset.updated_at
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_missing_database_raises_file_not_found(tmp_path: Path) -> None:
|
|
56
|
+
with pytest.raises(FileNotFoundError):
|
|
57
|
+
Kenya(db_path=tmp_path / "does-not-exist.db")
|