afrogeo 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.
afrogeo-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 404 Codes
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.
afrogeo-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: afrogeo
3
+ Version: 0.1.0
4
+ Summary: Offline-first African location verification for Python.
5
+ Author: 404Codes
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 404 Codes
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.8
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Afrogeo
37
+
38
+ Offline-first African location verification for Python.
39
+
40
+ Validate country, state, city, and district (LGA) without an internet connection.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install afrogeo
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Afrogeo provides a simple `verify` function that takes a dictionary and returns a `Result` object.
51
+
52
+ ### Basic Example
53
+
54
+ ```python
55
+ from afrogeo import verify
56
+
57
+ data = {
58
+ "country": "Nigeria",
59
+ "state": "Lagos",
60
+ "lga": "Agege"
61
+ }
62
+
63
+ result = verify(data)
64
+
65
+ if result.valid:
66
+ print(f"Validated: {result.normalized['state']} - {result.normalized['lga']}")
67
+ else:
68
+ print(f"Errors: {result.errors}")
69
+ ```
70
+
71
+ ### Full Validation (including City)
72
+
73
+ ```python
74
+ data = {
75
+ "country": "NG", # Supports codes or full names
76
+ "state": "LA", # Supports state codes
77
+ "lga": "Agege",
78
+ "city": "Agege" # Optional
79
+ }
80
+
81
+ result = verify(data)
82
+ ```
83
+
84
+ ## Features
85
+
86
+ - **Offline-First**: Uses local JSON data for instant verification.
87
+ - **Data Normalization**: Automatically converts codes to full names and vice-versa.
88
+ - **Case Insensitive**: Accepts "LAGOS", "lagos", or "Lagos".
89
+ - **Dynamic Loading**: Loads only the data for the country you are verifying.
90
+
91
+ ## API Reference
92
+
93
+ ### `verify(user_input: dict) -> Result`
94
+
95
+ The `user_input` dictionary accepts:
96
+ - `country` (Required): Full name or ISO code.
97
+ - `state` (Required): Full name or code.
98
+ - `lga` (Required): Local Government Area name.
99
+ - `city` (Optional): City name.
100
+
101
+ Returns a `Result` object with:
102
+ - `valid`: Boolean.
103
+ - `errors`: List of strings (empty if valid).
104
+ - `normalized`: Dictionary of standard location names and codes.
105
+
106
+ ## Contributing
107
+
108
+ Data is stored in `afrogeo/data/`. To add a new country, create a `<country_code>.json` file following the existing structure.
@@ -0,0 +1,73 @@
1
+ # Afrogeo
2
+
3
+ Offline-first African location verification for Python.
4
+
5
+ Validate country, state, city, and district (LGA) without an internet connection.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install afrogeo
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Afrogeo provides a simple `verify` function that takes a dictionary and returns a `Result` object.
16
+
17
+ ### Basic Example
18
+
19
+ ```python
20
+ from afrogeo import verify
21
+
22
+ data = {
23
+ "country": "Nigeria",
24
+ "state": "Lagos",
25
+ "lga": "Agege"
26
+ }
27
+
28
+ result = verify(data)
29
+
30
+ if result.valid:
31
+ print(f"Validated: {result.normalized['state']} - {result.normalized['lga']}")
32
+ else:
33
+ print(f"Errors: {result.errors}")
34
+ ```
35
+
36
+ ### Full Validation (including City)
37
+
38
+ ```python
39
+ data = {
40
+ "country": "NG", # Supports codes or full names
41
+ "state": "LA", # Supports state codes
42
+ "lga": "Agege",
43
+ "city": "Agege" # Optional
44
+ }
45
+
46
+ result = verify(data)
47
+ ```
48
+
49
+ ## Features
50
+
51
+ - **Offline-First**: Uses local JSON data for instant verification.
52
+ - **Data Normalization**: Automatically converts codes to full names and vice-versa.
53
+ - **Case Insensitive**: Accepts "LAGOS", "lagos", or "Lagos".
54
+ - **Dynamic Loading**: Loads only the data for the country you are verifying.
55
+
56
+ ## API Reference
57
+
58
+ ### `verify(user_input: dict) -> Result`
59
+
60
+ The `user_input` dictionary accepts:
61
+ - `country` (Required): Full name or ISO code.
62
+ - `state` (Required): Full name or code.
63
+ - `lga` (Required): Local Government Area name.
64
+ - `city` (Optional): City name.
65
+
66
+ Returns a `Result` object with:
67
+ - `valid`: Boolean.
68
+ - `errors`: List of strings (empty if valid).
69
+ - `normalized`: Dictionary of standard location names and codes.
70
+
71
+ ## Contributing
72
+
73
+ Data is stored in `afrogeo/data/`. To add a new country, create a `<country_code>.json` file following the existing structure.
@@ -0,0 +1,20 @@
1
+ """
2
+ Afrogeo: Offline-first African location verification for Python.
3
+
4
+ This package provides tools to validate and normalize location data
5
+ (Country, State, LGA, City) across Africa using local JSON datasets.
6
+ It is designed to work without an internet connection.
7
+
8
+ Main function:
9
+ verify: Validates location data and returns a Result object.
10
+
11
+ Usage:
12
+ >>> from afrogeo import verify
13
+ >>> result = verify({"country": "Nigeria", "state": "Lagos", "lga": "Agege"})
14
+ >>> if result:
15
+ ... print("Location is valid!")
16
+ """
17
+
18
+ from .verify import verify
19
+
20
+ __all__ = ["verify"]
File without changes
@@ -0,0 +1,33 @@
1
+ class Result:
2
+ """
3
+ Represents the outcome of a location verification.
4
+
5
+ This object is returned by the `verify()` function and contains
6
+ information about whether the validation succeeded, any errors
7
+ encountered, and the normalized location data.
8
+
9
+ Attributes:
10
+ valid (bool): True if the location was successfully verified.
11
+ errors (list[str]): A list of error messages if validation failed.
12
+ normalized (dict): A dictionary containing standardized location names
13
+ (country, state, lga, city) and codes if successful.
14
+ """
15
+ def __init__(self, valid: bool, errors=None, normalized=None):
16
+ """
17
+ Initialize a Result object.
18
+
19
+ Args:
20
+ valid: Boolean indicating success.
21
+ errors: Optional list of error strings.
22
+ normalized: Optional dictionary of normalized data.
23
+ """
24
+ self.valid = valid
25
+ self.errors = errors or []
26
+ self.normalized = normalized or {}
27
+
28
+ def __bool__(self):
29
+ """Allows using the result directly in an 'if' statement."""
30
+ return self.valid
31
+
32
+ def __repr__(self):
33
+ return f"<AfrogeoResult valid={self.valid} errors={self.errors}>"
@@ -0,0 +1,152 @@
1
+ import json
2
+ import functools
3
+ from pathlib import Path
4
+ from afrogeo.result import Result
5
+
6
+ # Constants
7
+ BASE_DIR = Path(__file__).parent
8
+ DATA_DIR = BASE_DIR / "data"
9
+
10
+ @functools.lru_cache(maxsize=10)
11
+ def load_country_data(country_code: str):
12
+ """
13
+ Load country data from a JSON file in the data directory.
14
+
15
+ Args:
16
+ country_code: The ISO country code (e.g., 'ng').
17
+
18
+ Returns:
19
+ A dictionary containing the country data, or None if not found.
20
+ """
21
+ file_path = DATA_DIR / f"{country_code.lower()}.json"
22
+ if not file_path.exists():
23
+ return None
24
+
25
+ try:
26
+ with open(file_path, "r", encoding="utf-8") as f:
27
+ return json.load(f)
28
+ except (json.JSONDecodeError, IOError):
29
+ return None
30
+
31
+ def verify(user_input: dict):
32
+ """
33
+ Verify African location data against local datasets.
34
+
35
+ This function validates if a given state, LGA, and optional city exist
36
+ within the specified country. It handles case-insensitivity and
37
+ normalizes the output.
38
+
39
+ Args:
40
+ user_input (dict): A dictionary containing:
41
+ - 'country' (str): Full name or ISO code (e.g., 'Nigeria' or 'NG').
42
+ - 'state' (str): Full name or code (e.g., 'Lagos' or 'LA').
43
+ - 'lga' (str): Local Government Area name.
44
+ - 'city' (str, optional): City name for validation.
45
+
46
+ Returns:
47
+ afrogeo.result.Result: An object containing validation status,
48
+ errors (if any), and normalized data.
49
+
50
+ Example:
51
+ >>> from afrogeo import verify
52
+ >>> res = verify({"country": "NG", "state": "LA", "lga": "Agege"})
53
+ >>> if res:
54
+ ... print(res.normalized['state'])
55
+ Lagos
56
+ """
57
+ country_input = str(user_input.get("country", "")).strip().lower()
58
+ state_input = str(user_input.get("state", "")).strip().lower()
59
+ lga_input = str(user_input.get("lga", "")).strip().lower()
60
+ city_input = str(user_input.get("city", "")).strip().lower() or None
61
+
62
+ if not country_input:
63
+ return Result(False, ["country is required"])
64
+ if not state_input or not lga_input:
65
+ return Result(False, ["state and lga are required"])
66
+
67
+ # -------------------------
68
+ # DATA LOADING (Dynamic)
69
+ # -------------------------
70
+ # Map country names to codes if necessary (Basic mapping for now)
71
+ country_map = {"nigeria": "ng", "ghana": "gh", "kenya": "ke"}
72
+ country_code = country_map.get(country_input, country_input)
73
+
74
+ data = load_country_data(country_code)
75
+ if not data:
76
+ return Result(False, [f"Data for country '{country_input}' not found or unsupported"])
77
+
78
+ # -------------------------
79
+ # COUNTRY MATCH
80
+ # -------------------------
81
+ found_country_key = None
82
+ for key in data.keys():
83
+ if key.lower() == country_code:
84
+ found_country_key = key
85
+ break
86
+
87
+ if not found_country_key:
88
+ return Result(False, [f"Invalid country code in data for '{country_input}'"])
89
+
90
+ country_data = data[found_country_key]
91
+ states = country_data.get("states", {})
92
+
93
+ # -------------------------
94
+ # STATE MATCH (code OR name)
95
+ # -------------------------
96
+ found_state_code = None
97
+ found_state_data = None
98
+
99
+ for code, state in states.items():
100
+ if (
101
+ code.lower() == state_input
102
+ or state["name"].lower() == state_input
103
+ ):
104
+ found_state_code = code
105
+ found_state_data = state
106
+ break
107
+
108
+ if not found_state_code:
109
+ return Result(False, ["Invalid state"])
110
+
111
+ # -------------------------
112
+ # LGA MATCH
113
+ # -------------------------
114
+ lgas = found_state_data.get("lgas", {})
115
+
116
+ found_lga_name = None
117
+ found_lga_info = None
118
+
119
+ for lga_name, lga_info in lgas.items():
120
+ if lga_name.lower() == lga_input:
121
+ found_lga_name = lga_name
122
+ found_lga_info = lga_info
123
+ break
124
+
125
+ if not found_lga_name:
126
+ return Result(False, ["Invalid LGA for selected state"])
127
+
128
+ # -------------------------
129
+ # OPTIONAL CITY CHECK
130
+ # -------------------------
131
+ found_city_name = found_lga_info.get("city")
132
+ if city_input:
133
+ if not found_city_name:
134
+ return Result(False, ["LGA does not belong to a city"])
135
+
136
+ if found_city_name.lower() != city_input:
137
+ return Result(False, ["City does not match LGA"])
138
+
139
+ # -------------------------
140
+ # SUCCESS
141
+ # -------------------------
142
+ normalized = {
143
+ "country_code": found_country_key,
144
+ "country": country_data["name"],
145
+ "state_code": found_state_code,
146
+ "state": found_state_data["name"],
147
+ "capital": found_state_data.get("capital"),
148
+ "lga": found_lga_name,
149
+ "city": found_city_name,
150
+ }
151
+
152
+ return Result(True, [], normalized)
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: afrogeo
3
+ Version: 0.1.0
4
+ Summary: Offline-first African location verification for Python.
5
+ Author: 404Codes
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 404 Codes
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.8
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Afrogeo
37
+
38
+ Offline-first African location verification for Python.
39
+
40
+ Validate country, state, city, and district (LGA) without an internet connection.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install afrogeo
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Afrogeo provides a simple `verify` function that takes a dictionary and returns a `Result` object.
51
+
52
+ ### Basic Example
53
+
54
+ ```python
55
+ from afrogeo import verify
56
+
57
+ data = {
58
+ "country": "Nigeria",
59
+ "state": "Lagos",
60
+ "lga": "Agege"
61
+ }
62
+
63
+ result = verify(data)
64
+
65
+ if result.valid:
66
+ print(f"Validated: {result.normalized['state']} - {result.normalized['lga']}")
67
+ else:
68
+ print(f"Errors: {result.errors}")
69
+ ```
70
+
71
+ ### Full Validation (including City)
72
+
73
+ ```python
74
+ data = {
75
+ "country": "NG", # Supports codes or full names
76
+ "state": "LA", # Supports state codes
77
+ "lga": "Agege",
78
+ "city": "Agege" # Optional
79
+ }
80
+
81
+ result = verify(data)
82
+ ```
83
+
84
+ ## Features
85
+
86
+ - **Offline-First**: Uses local JSON data for instant verification.
87
+ - **Data Normalization**: Automatically converts codes to full names and vice-versa.
88
+ - **Case Insensitive**: Accepts "LAGOS", "lagos", or "Lagos".
89
+ - **Dynamic Loading**: Loads only the data for the country you are verifying.
90
+
91
+ ## API Reference
92
+
93
+ ### `verify(user_input: dict) -> Result`
94
+
95
+ The `user_input` dictionary accepts:
96
+ - `country` (Required): Full name or ISO code.
97
+ - `state` (Required): Full name or code.
98
+ - `lga` (Required): Local Government Area name.
99
+ - `city` (Optional): City name.
100
+
101
+ Returns a `Result` object with:
102
+ - `valid`: Boolean.
103
+ - `errors`: List of strings (empty if valid).
104
+ - `normalized`: Dictionary of standard location names and codes.
105
+
106
+ ## Contributing
107
+
108
+ Data is stored in `afrogeo/data/`. To add a new country, create a `<country_code>.json` file following the existing structure.
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ afrogeo/__init__.py
5
+ afrogeo/result.py
6
+ afrogeo/verify.py
7
+ afrogeo.egg-info/PKG-INFO
8
+ afrogeo.egg-info/SOURCES.txt
9
+ afrogeo.egg-info/dependency_links.txt
10
+ afrogeo.egg-info/requires.txt
11
+ afrogeo.egg-info/top_level.txt
12
+ afrogeo/data/__init__.py
13
+ tests/test.py
14
+ tests/test_verify.py
@@ -0,0 +1,4 @@
1
+
2
+ [dev]
3
+ pytest
4
+ pytest-cov
@@ -0,0 +1 @@
1
+ afrogeo
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "afrogeo"
7
+ version = "0.1.0"
8
+ description = "Offline-first African location verification for Python."
9
+ authors = [
10
+ { name="404Codes" }
11
+ ]
12
+ readme = "README.md"
13
+ license = { file = "LICENSE" }
14
+ requires-python = ">=3.8"
15
+ dependencies = []
16
+
17
+ [project.optional-dependencies]
18
+ dev = [
19
+ "pytest",
20
+ "pytest-cov"
21
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ from afrogeo import verify
2
+
3
+ # Full mode
4
+ r1 = verify({
5
+ "country": "Nigeria",
6
+ "state": "Lagos",
7
+ "city": "Agege",
8
+ "lga": "Agege"
9
+ })
10
+ print(r1, r1.normalized)
11
+
12
+ # Minimal mode
13
+ r2 = verify({
14
+ "country": "Nigeria",
15
+ "state": "Lagos",
16
+ "lga": "Agege"
17
+ })
18
+ print(r2, r2.normalized)
19
+
20
+ # Wrong input
21
+ r3 = verify({
22
+ "country": "Nigeria",
23
+ "state": "Lagos",
24
+ "lga": "InvalidLGA"
25
+ })
26
+ print(r3, r3.normalized)
@@ -0,0 +1,62 @@
1
+ try:
2
+ import pytest
3
+ except ImportError:
4
+ pytest = None
5
+ from afrogeo import verify
6
+
7
+ def test_verify_valid_nigeria_full():
8
+ user_input = {
9
+ "country": "Nigeria",
10
+ "state": "Lagos",
11
+ "lga": "Agege",
12
+ "city": "Agege"
13
+ }
14
+ result = verify(user_input)
15
+ assert result.valid is True
16
+ assert result.normalized["state_code"] == "LA"
17
+
18
+ def test_verify_valid_nigeria_minimal():
19
+ user_input = {
20
+ "country": "NG",
21
+ "state": "LA",
22
+ "lga": "Agege"
23
+ }
24
+ result = verify(user_input)
25
+ assert result.valid is True
26
+ assert result.normalized["state"] == "Lagos"
27
+
28
+ def test_verify_invalid_state():
29
+ user_input = {
30
+ "country": "Nigeria",
31
+ "state": "InvalidState",
32
+ "lga": "Agege"
33
+ }
34
+ result = verify(user_input)
35
+ assert result.valid is False
36
+ assert "Invalid state" in result.errors
37
+
38
+ def test_verify_invalid_lga():
39
+ user_input = {
40
+ "country": "Nigeria",
41
+ "state": "Lagos",
42
+ "lga": "InvalidLGA"
43
+ }
44
+ result = verify(user_input)
45
+ assert result.valid is False
46
+ assert "Invalid LGA for selected state" in result.errors
47
+
48
+ def test_verify_unsupported_country():
49
+ user_input = {
50
+ "country": "UnknownCountry",
51
+ "state": "SomeState",
52
+ "lga": "SomeLGA"
53
+ }
54
+ result = verify(user_input)
55
+ assert result.valid is False
56
+ assert "Data for country" in result.errors[0]
57
+
58
+ if __name__ == "__main__":
59
+ # If run directly without pytest, just run a quick check
60
+ print("Running basic check...")
61
+ res = verify({"country": "Nigeria", "state": "Lagos", "lga": "Agege"})
62
+ print(f"Success: {res.valid}, Data: {res.normalized}")