elara-route 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.
- elara_route-0.1.0/LICENSE +22 -0
- elara_route-0.1.0/PKG-INFO +161 -0
- elara_route-0.1.0/README.md +109 -0
- elara_route-0.1.0/elara_route/__init__.py +43 -0
- elara_route-0.1.0/elara_route/client.py +300 -0
- elara_route-0.1.0/elara_route/constants.py +40 -0
- elara_route-0.1.0/elara_route/exceptions.py +40 -0
- elara_route-0.1.0/elara_route/py.typed +0 -0
- elara_route-0.1.0/elara_route.egg-info/PKG-INFO +161 -0
- elara_route-0.1.0/elara_route.egg-info/SOURCES.txt +14 -0
- elara_route-0.1.0/elara_route.egg-info/dependency_links.txt +1 -0
- elara_route-0.1.0/elara_route.egg-info/requires.txt +6 -0
- elara_route-0.1.0/elara_route.egg-info/top_level.txt +1 -0
- elara_route-0.1.0/pyproject.toml +53 -0
- elara_route-0.1.0/setup.cfg +4 -0
- elara_route-0.1.0/tests/test_client.py +107 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elara Cortex (www.elara-cortex.com) — Institute for Decision Systems & Number Theory
|
|
4
|
+
Author: .LEKOLA · CODE ATELIER
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: elara-route
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Elara Route API — road, drone, maritime, AV routing + geocoding + fleet tracking
|
|
5
|
+
Author-email: ".LEKOLA · CODE ATELIER" <hello@elara-cortex.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Elara Cortex (www.elara-cortex.com) — Institute for Decision Systems & Number Theory
|
|
9
|
+
Author: .LEKOLA · CODE ATELIER
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://route.elara-cortex.com
|
|
30
|
+
Project-URL: Documentation, https://route.elara-cortex.com/developers
|
|
31
|
+
Project-URL: Repository, https://github.com/ELARA-CORTEX-MATH-INFRA/elara-route-python
|
|
32
|
+
Project-URL: Issues, https://github.com/ELARA-CORTEX-MATH-INFRA/elara-route-python/issues
|
|
33
|
+
Keywords: routing,navigation,geocoding,fleet,drone,africa,api,sdk,elara
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Requires-Python: <4.0,>=3.9
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest<9.0.0,>=7.4.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-mock<4.0.0,>=3.12.0; extra == "dev"
|
|
50
|
+
Requires-Dist: build<2.0.0,>=1.0.0; extra == "dev"
|
|
51
|
+
Requires-Dist: twine<6.0.0,>=4.0.0; extra == "dev"
|
|
52
|
+
|
|
53
|
+
# elara-route — Python SDK
|
|
54
|
+
|
|
55
|
+
The official Python client for the [Elara Route API](https://route.elara-cortex.com/developers) — road, drone, maritime, AV routing + geocoding + fleet tracking.
|
|
56
|
+
|
|
57
|
+
**No routing logic ships in this package.** All algorithms run server-side at `route.elara-cortex.com`. Your IP stays protected.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install elara-route
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or from source:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd D:\elara_sdk
|
|
69
|
+
pip install -e ".[dev]"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Zero runtime dependencies. Pure Python 3.9+ standard library.
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from elara_route import ElaraClient
|
|
78
|
+
|
|
79
|
+
client = ElaraClient(api_key="elara_your_key_here")
|
|
80
|
+
|
|
81
|
+
route = client.route(
|
|
82
|
+
from_=[-26.1076, 28.0567],
|
|
83
|
+
to=[-26.2041, 28.0473],
|
|
84
|
+
)
|
|
85
|
+
print(f"{route['distance_km']:.1f} km — {route['eta_min']:.0f} min")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or create an account programmatically:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
client = ElaraClient.signup("you@company.com", "your-secure-password")
|
|
92
|
+
route = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## All endpoints
|
|
96
|
+
|
|
97
|
+
| Method | Endpoint | Description |
|
|
98
|
+
|--------|----------|-------------|
|
|
99
|
+
| `route()` | POST `/v1/route` | A→B road route |
|
|
100
|
+
| `reroute()` | POST `/v1/reroute` | Compact reroute delta (~80% smaller payload) |
|
|
101
|
+
| `matrix()` | POST `/v1/matrix` | Distance/time matrix |
|
|
102
|
+
| `optimize()` | POST `/v1/optimize` | Multi-stop VRP order |
|
|
103
|
+
| `drone_route()` | POST `/v1/drone/route` | Drone corridor routing |
|
|
104
|
+
| `maritime_route()` | POST `/v1/maritime/route` | Sea routing |
|
|
105
|
+
| `av_route()` | POST `/v1/av/route` | Autonomous-vehicle routing |
|
|
106
|
+
| `geocode()` | GET `/v1/geocode` | Address → coordinates |
|
|
107
|
+
| `reverse()` | GET `/v1/reverse` | Coordinates → address |
|
|
108
|
+
| `nearby()` | GET `/v1/nearby` | POIs by category |
|
|
109
|
+
| `signup()` | POST `/v1/account/signup` | Create account + API key |
|
|
110
|
+
| `usage()` | GET `/v1/account/usage` | Quota and usage |
|
|
111
|
+
| `assets_list()` | GET `/v1/assets` | List fleet assets |
|
|
112
|
+
| `asset_create()` | POST `/v1/assets` | Register asset |
|
|
113
|
+
| `asset_route()` | POST `/v1/assets/{id}/route` | Attach route to asset |
|
|
114
|
+
| `asset_position()` | POST `/v1/assets/{id}/position` | Push live GPS |
|
|
115
|
+
| `asset_live()` | GET `/v1/assets/{id}/live` | Live position vs plan |
|
|
116
|
+
|
|
117
|
+
### Nearby categories (OpenAPI enum)
|
|
118
|
+
|
|
119
|
+
`atm`, `cafe`, `charging`, `checkpoint`, `food`, `fuel`, `hospital`, `hotel`, `parking`, `police`, `speed_camera`
|
|
120
|
+
|
|
121
|
+
## Error handling
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from elara_route import ElaraAuthError, ElaraRateLimitError, ElaraValidationError
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
route = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
128
|
+
except ElaraValidationError as e:
|
|
129
|
+
print("Bad input:", e.detail)
|
|
130
|
+
except ElaraRateLimitError as e:
|
|
131
|
+
print(f"Retry in {e.retry_after}s")
|
|
132
|
+
except ElaraAuthError:
|
|
133
|
+
print("Check your API key")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Run tests
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pytest tests/ -v
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Run example
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
set ELARA_API_KEY=elara_your_key
|
|
146
|
+
python examples/quickstart.py
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Free tier
|
|
150
|
+
|
|
151
|
+
25,000 routes/month, no credit card. [Get your key →](https://route.elara-cortex.com/developers)
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT — see [LICENSE](LICENSE).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
**ELARA·CORTEX** · Mathematical infrastructure for complex systems · Institute for Decision Systems & Number Theory
|
|
160
|
+
|
|
161
|
+
**AUTHOR:** .LEKOLA · CODE ATELIER · GitHub: [ELARA-CORTEX-MATH-INFRA](https://github.com/ELARA-CORTEX-MATH-INFRA)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# elara-route — Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python client for the [Elara Route API](https://route.elara-cortex.com/developers) — road, drone, maritime, AV routing + geocoding + fleet tracking.
|
|
4
|
+
|
|
5
|
+
**No routing logic ships in this package.** All algorithms run server-side at `route.elara-cortex.com`. Your IP stays protected.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install elara-route
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or from source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd D:\elara_sdk
|
|
17
|
+
pip install -e ".[dev]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Zero runtime dependencies. Pure Python 3.9+ standard library.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from elara_route import ElaraClient
|
|
26
|
+
|
|
27
|
+
client = ElaraClient(api_key="elara_your_key_here")
|
|
28
|
+
|
|
29
|
+
route = client.route(
|
|
30
|
+
from_=[-26.1076, 28.0567],
|
|
31
|
+
to=[-26.2041, 28.0473],
|
|
32
|
+
)
|
|
33
|
+
print(f"{route['distance_km']:.1f} km — {route['eta_min']:.0f} min")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or create an account programmatically:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
client = ElaraClient.signup("you@company.com", "your-secure-password")
|
|
40
|
+
route = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## All endpoints
|
|
44
|
+
|
|
45
|
+
| Method | Endpoint | Description |
|
|
46
|
+
|--------|----------|-------------|
|
|
47
|
+
| `route()` | POST `/v1/route` | A→B road route |
|
|
48
|
+
| `reroute()` | POST `/v1/reroute` | Compact reroute delta (~80% smaller payload) |
|
|
49
|
+
| `matrix()` | POST `/v1/matrix` | Distance/time matrix |
|
|
50
|
+
| `optimize()` | POST `/v1/optimize` | Multi-stop VRP order |
|
|
51
|
+
| `drone_route()` | POST `/v1/drone/route` | Drone corridor routing |
|
|
52
|
+
| `maritime_route()` | POST `/v1/maritime/route` | Sea routing |
|
|
53
|
+
| `av_route()` | POST `/v1/av/route` | Autonomous-vehicle routing |
|
|
54
|
+
| `geocode()` | GET `/v1/geocode` | Address → coordinates |
|
|
55
|
+
| `reverse()` | GET `/v1/reverse` | Coordinates → address |
|
|
56
|
+
| `nearby()` | GET `/v1/nearby` | POIs by category |
|
|
57
|
+
| `signup()` | POST `/v1/account/signup` | Create account + API key |
|
|
58
|
+
| `usage()` | GET `/v1/account/usage` | Quota and usage |
|
|
59
|
+
| `assets_list()` | GET `/v1/assets` | List fleet assets |
|
|
60
|
+
| `asset_create()` | POST `/v1/assets` | Register asset |
|
|
61
|
+
| `asset_route()` | POST `/v1/assets/{id}/route` | Attach route to asset |
|
|
62
|
+
| `asset_position()` | POST `/v1/assets/{id}/position` | Push live GPS |
|
|
63
|
+
| `asset_live()` | GET `/v1/assets/{id}/live` | Live position vs plan |
|
|
64
|
+
|
|
65
|
+
### Nearby categories (OpenAPI enum)
|
|
66
|
+
|
|
67
|
+
`atm`, `cafe`, `charging`, `checkpoint`, `food`, `fuel`, `hospital`, `hotel`, `parking`, `police`, `speed_camera`
|
|
68
|
+
|
|
69
|
+
## Error handling
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from elara_route import ElaraAuthError, ElaraRateLimitError, ElaraValidationError
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
route = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
76
|
+
except ElaraValidationError as e:
|
|
77
|
+
print("Bad input:", e.detail)
|
|
78
|
+
except ElaraRateLimitError as e:
|
|
79
|
+
print(f"Retry in {e.retry_after}s")
|
|
80
|
+
except ElaraAuthError:
|
|
81
|
+
print("Check your API key")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Run tests
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pytest tests/ -v
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Run example
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
set ELARA_API_KEY=elara_your_key
|
|
94
|
+
python examples/quickstart.py
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Free tier
|
|
98
|
+
|
|
99
|
+
25,000 routes/month, no credit card. [Get your key →](https://route.elara-cortex.com/developers)
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT — see [LICENSE](LICENSE).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
**ELARA·CORTEX** · Mathematical infrastructure for complex systems · Institute for Decision Systems & Number Theory
|
|
108
|
+
|
|
109
|
+
**AUTHOR:** .LEKOLA · CODE ATELIER · GitHub: [ELARA-CORTEX-MATH-INFRA](https://github.com/ELARA-CORTEX-MATH-INFRA)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORGANISATION: ELARA·CORTEX
|
|
3
|
+
Mathematical infrastructure for complex systems
|
|
4
|
+
Institute for Decision Systems & Number Theory
|
|
5
|
+
AUTHOR: .LEKOLA · CODE ATELIER
|
|
6
|
+
GITHUB_ORG: ELARA-CORTEX-MATH-INFRA
|
|
7
|
+
|
|
8
|
+
NODE_ID: SDK-PY-ROUTE-INIT
|
|
9
|
+
PROJECT_ID: elara-route-python
|
|
10
|
+
PARENT_NODE: sdk/python/elara_route
|
|
11
|
+
PURPOSE: Package entrypoint — exports ElaraClient, constants, and exceptions.
|
|
12
|
+
|
|
13
|
+
CHANGE_ID: CHG-20260617-SDK-FULL
|
|
14
|
+
CHANGE_NOTE: Full SDK build — export constants and version from single module.
|
|
15
|
+
|
|
16
|
+
elara-route — Python SDK for the Elara Route API
|
|
17
|
+
https://route.elara-cortex.com/developers
|
|
18
|
+
|
|
19
|
+
Install:
|
|
20
|
+
pip install elara-route
|
|
21
|
+
|
|
22
|
+
Quick start:
|
|
23
|
+
from elara_route import ElaraClient
|
|
24
|
+
client = ElaraClient(api_key="elara_xxx")
|
|
25
|
+
route = client.route(from_=[-26.1076, 28.0567], to=[-26.2041, 28.0473])
|
|
26
|
+
print(route["distance_km"], "km —", route["eta_min"], "min")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from .client import ElaraClient
|
|
30
|
+
from .constants import ASSET_TYPES, DEFAULT_BASE_URL, NEARBY_CATEGORIES, __version__
|
|
31
|
+
from .exceptions import ElaraAuthError, ElaraError, ElaraRateLimitError, ElaraValidationError
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ElaraClient",
|
|
35
|
+
"ElaraError",
|
|
36
|
+
"ElaraAuthError",
|
|
37
|
+
"ElaraRateLimitError",
|
|
38
|
+
"ElaraValidationError",
|
|
39
|
+
"ASSET_TYPES",
|
|
40
|
+
"NEARBY_CATEGORIES",
|
|
41
|
+
"DEFAULT_BASE_URL",
|
|
42
|
+
"__version__",
|
|
43
|
+
]
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORGANISATION: ELARA·CORTEX
|
|
3
|
+
Mathematical infrastructure for complex systems
|
|
4
|
+
Institute for Decision Systems & Number Theory
|
|
5
|
+
AUTHOR: .LEKOLA · CODE ATELIER
|
|
6
|
+
GITHUB_ORG: ELARA-CORTEX-MATH-INFRA
|
|
7
|
+
|
|
8
|
+
NODE_ID: SDK-PY-ROUTE-CLIENT
|
|
9
|
+
PROJECT_ID: elara-route-python
|
|
10
|
+
PARENT_NODE: sdk/python/elara_route
|
|
11
|
+
PURPOSE: Authenticated HTTP client for Elara Route API endpoints.
|
|
12
|
+
|
|
13
|
+
CHANGE_ID: CHG-20260617-SDK-FULL
|
|
14
|
+
CHANGE_NOTE: Full SDK — av_route, signup, OpenAPI category enum, constants module.
|
|
15
|
+
|
|
16
|
+
ElaraClient — the main entry point for the Elara Route SDK.
|
|
17
|
+
|
|
18
|
+
All algorithm execution happens server-side at route.elara-cortex.com.
|
|
19
|
+
This SDK is a thin, authenticated HTTP wrapper — no routing logic ships in this package.
|
|
20
|
+
Your IP is 100% protected.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import urllib.error
|
|
26
|
+
import urllib.parse
|
|
27
|
+
import urllib.request
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
from .constants import ASSET_TYPES, DEFAULT_BASE_URL, DEFAULT_TIMEOUT, NEARBY_CATEGORIES, __version__
|
|
31
|
+
from .exceptions import ElaraAuthError, ElaraError, ElaraRateLimitError, ElaraValidationError
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ElaraClient:
|
|
35
|
+
"""
|
|
36
|
+
Elara Route API client.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
api_key : str
|
|
41
|
+
Your Elara API key (get one free at https://route.elara-cortex.com/developers).
|
|
42
|
+
timeout : int
|
|
43
|
+
Request timeout in seconds. Default: 30.
|
|
44
|
+
base_url : str
|
|
45
|
+
Override the API base URL (useful for testing).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
api_key: str,
|
|
51
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
52
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
53
|
+
):
|
|
54
|
+
if not api_key:
|
|
55
|
+
raise ElaraAuthError("api_key is required. Get yours free at https://route.elara-cortex.com/developers")
|
|
56
|
+
self._key = api_key
|
|
57
|
+
self._timeout = timeout
|
|
58
|
+
self._base = base_url.rstrip("/")
|
|
59
|
+
|
|
60
|
+
# ── internal HTTP helpers ────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
def _request(self, method: str, path: str, body: Optional[dict] = None) -> dict:
|
|
63
|
+
url = self._base + path
|
|
64
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
65
|
+
req = urllib.request.Request(url, data=data, method=method)
|
|
66
|
+
req.add_header("Content-Type", "application/json")
|
|
67
|
+
req.add_header("X-API-Key", self._key)
|
|
68
|
+
req.add_header("User-Agent", f"elara-route-python/{__version__}")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
with urllib.request.urlopen(req, timeout=self._timeout) as r:
|
|
72
|
+
return json.loads(r.read().decode("utf-8"))
|
|
73
|
+
except urllib.error.HTTPError as e:
|
|
74
|
+
body_str = e.read().decode("utf-8", "replace")
|
|
75
|
+
try:
|
|
76
|
+
detail = json.loads(body_str).get("detail", body_str)
|
|
77
|
+
except Exception:
|
|
78
|
+
detail = body_str[:200]
|
|
79
|
+
|
|
80
|
+
if e.code in (401, 403):
|
|
81
|
+
raise ElaraAuthError(f"Authentication failed: {detail}", status_code=e.code, detail=detail)
|
|
82
|
+
if e.code == 422:
|
|
83
|
+
raise ElaraValidationError(f"Invalid request: {detail}", status_code=422, detail=detail)
|
|
84
|
+
if e.code == 429:
|
|
85
|
+
retry_after = int(e.headers.get("Retry-After", 60))
|
|
86
|
+
raise ElaraRateLimitError(f"Rate limit exceeded. Retry after {retry_after}s.", retry_after=retry_after)
|
|
87
|
+
raise ElaraError(f"API error {e.code}: {detail}", status_code=e.code, detail=detail)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise ElaraError(f"Network error: {e}")
|
|
90
|
+
|
|
91
|
+
def _get(self, path: str) -> dict:
|
|
92
|
+
return self._request("GET", path)
|
|
93
|
+
|
|
94
|
+
def _post(self, path: str, body: dict) -> dict:
|
|
95
|
+
return self._request("POST", path, body)
|
|
96
|
+
|
|
97
|
+
# ── Routing ──────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
def route(
|
|
100
|
+
self,
|
|
101
|
+
from_: List[float],
|
|
102
|
+
to: List[float],
|
|
103
|
+
alternatives: bool = False,
|
|
104
|
+
) -> dict:
|
|
105
|
+
"""
|
|
106
|
+
Plan an A→B road route.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
from_ : [lat, lon]
|
|
111
|
+
Origin coordinates.
|
|
112
|
+
to : [lat, lon]
|
|
113
|
+
Destination coordinates.
|
|
114
|
+
alternatives : bool
|
|
115
|
+
Request alternative routes (default: False).
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
dict with keys: distance_km, eta_min, geometry_latlng, steps, engine, ...
|
|
120
|
+
|
|
121
|
+
Example
|
|
122
|
+
-------
|
|
123
|
+
>>> client.route(from_=[-26.1076, 28.0567], to=[-26.2041, 28.0473])
|
|
124
|
+
{'distance_km': 13.28, 'eta_min': 18.4, ...}
|
|
125
|
+
"""
|
|
126
|
+
body: Dict[str, Any] = {"from": from_, "to": to}
|
|
127
|
+
if alternatives:
|
|
128
|
+
body["alternatives"] = True
|
|
129
|
+
return self._post("/v1/route", body)
|
|
130
|
+
|
|
131
|
+
def reroute(
|
|
132
|
+
self,
|
|
133
|
+
from_: List[float],
|
|
134
|
+
to: List[float],
|
|
135
|
+
progress: float = 0.0,
|
|
136
|
+
) -> dict:
|
|
137
|
+
"""
|
|
138
|
+
Request a compact reroute delta (typically ~80% smaller than a full route).
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
from_ : [lat, lon] Current vehicle position.
|
|
143
|
+
to : [lat, lon] Destination.
|
|
144
|
+
progress : float Fraction of original route already completed (0–1).
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
dict with reroute delta + full route fields.
|
|
149
|
+
"""
|
|
150
|
+
return self._post("/v1/reroute", {"from": from_, "to": to, "progress": progress})
|
|
151
|
+
|
|
152
|
+
def matrix(
|
|
153
|
+
self,
|
|
154
|
+
depot: List[float],
|
|
155
|
+
stops: List[List[float]],
|
|
156
|
+
) -> dict:
|
|
157
|
+
"""
|
|
158
|
+
Compute a distance/time matrix between a depot and multiple stops.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
dict with 'distances' key containing an N×N matrix (km).
|
|
163
|
+
"""
|
|
164
|
+
return self._post("/v1/matrix", {"depot": depot, "stops": stops})
|
|
165
|
+
|
|
166
|
+
def optimize(
|
|
167
|
+
self,
|
|
168
|
+
depot: List[float],
|
|
169
|
+
stops: List[List[float]],
|
|
170
|
+
vehicles: int = 1,
|
|
171
|
+
) -> dict:
|
|
172
|
+
"""
|
|
173
|
+
Find the optimal multi-stop order (VRP solver).
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
dict with 'order', 'distance_km', 'improvement_pct', 'guaranteed_never_worse'.
|
|
178
|
+
"""
|
|
179
|
+
body: Dict[str, Any] = {"depot": depot, "stops": stops}
|
|
180
|
+
if vehicles > 1:
|
|
181
|
+
body["vehicles"] = vehicles
|
|
182
|
+
return self._post("/v1/optimize", body)
|
|
183
|
+
|
|
184
|
+
def drone_route(self, from_: List[float], to: List[float]) -> dict:
|
|
185
|
+
"""Air routing along drone corridors where they exist, else direct flight."""
|
|
186
|
+
return self._post("/v1/drone/route", {"from": from_, "to": to})
|
|
187
|
+
|
|
188
|
+
def maritime_route(self, from_: List[float], to: List[float]) -> dict:
|
|
189
|
+
"""Sea routing between coastal/offshore points."""
|
|
190
|
+
return self._post("/v1/maritime/route", {"from": from_, "to": to})
|
|
191
|
+
|
|
192
|
+
def av_route(self, from_: List[float], to: List[float]) -> dict:
|
|
193
|
+
"""Autonomous-vehicle routing with corridor-aware geometry where available."""
|
|
194
|
+
return self._post("/v1/av/route", {"from": from_, "to": to})
|
|
195
|
+
|
|
196
|
+
# ── Geocoding ────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
def geocode(self, query: str) -> dict:
|
|
199
|
+
"""
|
|
200
|
+
Convert an address or place name to coordinates.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
dict with 'results' list, each containing lat, lon, name.
|
|
205
|
+
"""
|
|
206
|
+
return self._get("/v1/geocode?q=" + urllib.parse.quote(query))
|
|
207
|
+
|
|
208
|
+
def reverse(self, lat: float, lon: float) -> dict:
|
|
209
|
+
"""Convert coordinates to a human-readable address."""
|
|
210
|
+
return self._get(f"/v1/reverse?lat={lat}&lon={lon}")
|
|
211
|
+
|
|
212
|
+
def nearby(self, lat: float, lon: float, category: str = "fuel") -> dict:
|
|
213
|
+
"""
|
|
214
|
+
Find nearby places by category.
|
|
215
|
+
|
|
216
|
+
Categories (OpenAPI enum): atm, cafe, charging, checkpoint, food, fuel,
|
|
217
|
+
hospital, hotel, parking, police, speed_camera
|
|
218
|
+
"""
|
|
219
|
+
if category not in NEARBY_CATEGORIES:
|
|
220
|
+
raise ElaraValidationError(
|
|
221
|
+
f"Invalid category '{category}'. Allowed: {', '.join(sorted(NEARBY_CATEGORIES))}"
|
|
222
|
+
)
|
|
223
|
+
return self._get(f"/v1/nearby?lat={lat}&lon={lon}&category={urllib.parse.quote(category)}")
|
|
224
|
+
|
|
225
|
+
# ── Account ──────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def signup(cls, email: str, password: str, timeout: int = DEFAULT_TIMEOUT) -> "ElaraClient":
|
|
229
|
+
"""
|
|
230
|
+
Create a new account and return a client pre-loaded with the issued API key.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
email, password : str
|
|
235
|
+
Account credentials (public beta signup at route.elara-cortex.com).
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
ElaraClient
|
|
240
|
+
Client authenticated with the new API key from the signup response.
|
|
241
|
+
"""
|
|
242
|
+
url = DEFAULT_BASE_URL + "/v1/account/signup"
|
|
243
|
+
body = json.dumps({"email": email, "password": password}).encode()
|
|
244
|
+
req = urllib.request.Request(url, data=body, method="POST")
|
|
245
|
+
req.add_header("Content-Type", "application/json")
|
|
246
|
+
req.add_header("User-Agent", f"elara-route-python/{__version__}")
|
|
247
|
+
try:
|
|
248
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
249
|
+
payload = json.loads(r.read().decode("utf-8"))
|
|
250
|
+
except urllib.error.HTTPError as e:
|
|
251
|
+
detail = e.read().decode("utf-8", "replace")
|
|
252
|
+
if e.code == 422:
|
|
253
|
+
raise ElaraValidationError(f"Invalid signup: {detail}", status_code=422, detail=detail)
|
|
254
|
+
raise ElaraError(f"Signup failed ({e.code}): {detail}", status_code=e.code, detail=detail)
|
|
255
|
+
key = payload.get("api_key") or payload.get("key")
|
|
256
|
+
if not key:
|
|
257
|
+
raise ElaraError("Signup response missing api_key", detail=str(payload))
|
|
258
|
+
return cls(api_key=key, timeout=timeout)
|
|
259
|
+
|
|
260
|
+
def usage(self) -> dict:
|
|
261
|
+
"""Return current API key usage and quota."""
|
|
262
|
+
return self._get("/v1/account/usage")
|
|
263
|
+
|
|
264
|
+
# ── Assets / Fleet tracking ───────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
def assets_list(self) -> dict:
|
|
267
|
+
"""List all registered assets and their live-tracking limits."""
|
|
268
|
+
return self._get("/v1/assets")
|
|
269
|
+
|
|
270
|
+
def asset_create(self, name: str, asset_type: str = "vehicle") -> dict:
|
|
271
|
+
"""Register a new asset (vehicle, drone, robot, vessel, person)."""
|
|
272
|
+
if asset_type not in ASSET_TYPES:
|
|
273
|
+
raise ElaraValidationError(
|
|
274
|
+
f"Invalid asset type '{asset_type}'. Allowed: {', '.join(sorted(ASSET_TYPES))}"
|
|
275
|
+
)
|
|
276
|
+
return self._post("/v1/assets", {"name": name, "type": asset_type})
|
|
277
|
+
|
|
278
|
+
def asset_route(self, asset_id: str, from_: List[float], to: List[float]) -> dict:
|
|
279
|
+
"""Plan and attach a route to an asset."""
|
|
280
|
+
return self._post(f"/v1/assets/{asset_id}/route", {"from": from_, "to": to})
|
|
281
|
+
|
|
282
|
+
def asset_position(
|
|
283
|
+
self,
|
|
284
|
+
asset_id: str,
|
|
285
|
+
lat: float,
|
|
286
|
+
lon: float,
|
|
287
|
+
battery_pct: Optional[float] = None,
|
|
288
|
+
heading: Optional[float] = None,
|
|
289
|
+
) -> dict:
|
|
290
|
+
"""Push a live GPS position for an asset."""
|
|
291
|
+
body: Dict[str, Any] = {"lat": lat, "lon": lon}
|
|
292
|
+
if battery_pct is not None:
|
|
293
|
+
body["battery_pct"] = battery_pct
|
|
294
|
+
if heading is not None:
|
|
295
|
+
body["heading"] = heading
|
|
296
|
+
return self._post(f"/v1/assets/{asset_id}/position", body)
|
|
297
|
+
|
|
298
|
+
def asset_live(self, asset_id: str) -> dict:
|
|
299
|
+
"""Get the live position of an asset vs its planned route."""
|
|
300
|
+
return self._get(f"/v1/assets/{asset_id}/live")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORGANISATION: ELARA·CORTEX
|
|
3
|
+
Mathematical infrastructure for complex systems
|
|
4
|
+
Institute for Decision Systems & Number Theory
|
|
5
|
+
AUTHOR: .LEKOLA · CODE ATELIER
|
|
6
|
+
GITHUB_ORG: ELARA-CORTEX-MATH-INFRA
|
|
7
|
+
|
|
8
|
+
NODE_ID: SDK-PY-ROUTE-CONSTANTS
|
|
9
|
+
PROJECT_ID: elara-route-python
|
|
10
|
+
PARENT_NODE: sdk/python/elara_route
|
|
11
|
+
PURPOSE: Shared API constants — base URL, categories, version string.
|
|
12
|
+
|
|
13
|
+
CHANGE_ID: CHG-20260617-SDK-FULL
|
|
14
|
+
CHANGE_NOTE: Full SDK build — public contract constants from OpenAPI 2026-06-16.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
DEFAULT_BASE_URL = "https://route.elara-cortex.com"
|
|
22
|
+
DEFAULT_TIMEOUT = 30
|
|
23
|
+
|
|
24
|
+
NEARBY_CATEGORIES = frozenset(
|
|
25
|
+
{
|
|
26
|
+
"atm",
|
|
27
|
+
"cafe",
|
|
28
|
+
"charging",
|
|
29
|
+
"checkpoint",
|
|
30
|
+
"food",
|
|
31
|
+
"fuel",
|
|
32
|
+
"hospital",
|
|
33
|
+
"hotel",
|
|
34
|
+
"parking",
|
|
35
|
+
"police",
|
|
36
|
+
"speed_camera",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
ASSET_TYPES = frozenset({"vehicle", "drone", "robot", "vessel", "person"})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORGANISATION: ELARA·CORTEX
|
|
3
|
+
Mathematical infrastructure for complex systems
|
|
4
|
+
Institute for Decision Systems & Number Theory
|
|
5
|
+
AUTHOR: .LEKOLA · CODE ATELIER
|
|
6
|
+
GITHUB_ORG: ELARA-CORTEX-MATH-INFRA
|
|
7
|
+
|
|
8
|
+
NODE_ID: SDK-PY-ROUTE-EXCEPTIONS
|
|
9
|
+
PROJECT_ID: elara-route-python
|
|
10
|
+
PARENT_NODE: sdk/python/elara_route
|
|
11
|
+
PURPOSE: Typed exceptions mapping Elara Route API HTTP error codes.
|
|
12
|
+
|
|
13
|
+
CHANGE_ID: CHG-20260617-SDK-FULL
|
|
14
|
+
CHANGE_NOTE: Full SDK build — removed STUDIO from header.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ElaraError(Exception):
|
|
19
|
+
"""Base exception for all Elara SDK errors."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, status_code: int = None, detail: str = None):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.status_code = status_code
|
|
24
|
+
self.detail = detail
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ElaraAuthError(ElaraError):
|
|
28
|
+
"""Raised when the API key is missing or invalid (HTTP 401/403)."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ElaraRateLimitError(ElaraError):
|
|
32
|
+
"""Raised when the rate limit is exceeded (HTTP 429)."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str, retry_after: int = None):
|
|
35
|
+
super().__init__(message, status_code=429)
|
|
36
|
+
self.retry_after = retry_after
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ElaraValidationError(ElaraError):
|
|
40
|
+
"""Raised when the request parameters are invalid (HTTP 422)."""
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: elara-route
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Elara Route API — road, drone, maritime, AV routing + geocoding + fleet tracking
|
|
5
|
+
Author-email: ".LEKOLA · CODE ATELIER" <hello@elara-cortex.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Elara Cortex (www.elara-cortex.com) — Institute for Decision Systems & Number Theory
|
|
9
|
+
Author: .LEKOLA · CODE ATELIER
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://route.elara-cortex.com
|
|
30
|
+
Project-URL: Documentation, https://route.elara-cortex.com/developers
|
|
31
|
+
Project-URL: Repository, https://github.com/ELARA-CORTEX-MATH-INFRA/elara-route-python
|
|
32
|
+
Project-URL: Issues, https://github.com/ELARA-CORTEX-MATH-INFRA/elara-route-python/issues
|
|
33
|
+
Keywords: routing,navigation,geocoding,fleet,drone,africa,api,sdk,elara
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Requires-Python: <4.0,>=3.9
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest<9.0.0,>=7.4.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-mock<4.0.0,>=3.12.0; extra == "dev"
|
|
50
|
+
Requires-Dist: build<2.0.0,>=1.0.0; extra == "dev"
|
|
51
|
+
Requires-Dist: twine<6.0.0,>=4.0.0; extra == "dev"
|
|
52
|
+
|
|
53
|
+
# elara-route — Python SDK
|
|
54
|
+
|
|
55
|
+
The official Python client for the [Elara Route API](https://route.elara-cortex.com/developers) — road, drone, maritime, AV routing + geocoding + fleet tracking.
|
|
56
|
+
|
|
57
|
+
**No routing logic ships in this package.** All algorithms run server-side at `route.elara-cortex.com`. Your IP stays protected.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install elara-route
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or from source:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd D:\elara_sdk
|
|
69
|
+
pip install -e ".[dev]"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Zero runtime dependencies. Pure Python 3.9+ standard library.
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from elara_route import ElaraClient
|
|
78
|
+
|
|
79
|
+
client = ElaraClient(api_key="elara_your_key_here")
|
|
80
|
+
|
|
81
|
+
route = client.route(
|
|
82
|
+
from_=[-26.1076, 28.0567],
|
|
83
|
+
to=[-26.2041, 28.0473],
|
|
84
|
+
)
|
|
85
|
+
print(f"{route['distance_km']:.1f} km — {route['eta_min']:.0f} min")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or create an account programmatically:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
client = ElaraClient.signup("you@company.com", "your-secure-password")
|
|
92
|
+
route = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## All endpoints
|
|
96
|
+
|
|
97
|
+
| Method | Endpoint | Description |
|
|
98
|
+
|--------|----------|-------------|
|
|
99
|
+
| `route()` | POST `/v1/route` | A→B road route |
|
|
100
|
+
| `reroute()` | POST `/v1/reroute` | Compact reroute delta (~80% smaller payload) |
|
|
101
|
+
| `matrix()` | POST `/v1/matrix` | Distance/time matrix |
|
|
102
|
+
| `optimize()` | POST `/v1/optimize` | Multi-stop VRP order |
|
|
103
|
+
| `drone_route()` | POST `/v1/drone/route` | Drone corridor routing |
|
|
104
|
+
| `maritime_route()` | POST `/v1/maritime/route` | Sea routing |
|
|
105
|
+
| `av_route()` | POST `/v1/av/route` | Autonomous-vehicle routing |
|
|
106
|
+
| `geocode()` | GET `/v1/geocode` | Address → coordinates |
|
|
107
|
+
| `reverse()` | GET `/v1/reverse` | Coordinates → address |
|
|
108
|
+
| `nearby()` | GET `/v1/nearby` | POIs by category |
|
|
109
|
+
| `signup()` | POST `/v1/account/signup` | Create account + API key |
|
|
110
|
+
| `usage()` | GET `/v1/account/usage` | Quota and usage |
|
|
111
|
+
| `assets_list()` | GET `/v1/assets` | List fleet assets |
|
|
112
|
+
| `asset_create()` | POST `/v1/assets` | Register asset |
|
|
113
|
+
| `asset_route()` | POST `/v1/assets/{id}/route` | Attach route to asset |
|
|
114
|
+
| `asset_position()` | POST `/v1/assets/{id}/position` | Push live GPS |
|
|
115
|
+
| `asset_live()` | GET `/v1/assets/{id}/live` | Live position vs plan |
|
|
116
|
+
|
|
117
|
+
### Nearby categories (OpenAPI enum)
|
|
118
|
+
|
|
119
|
+
`atm`, `cafe`, `charging`, `checkpoint`, `food`, `fuel`, `hospital`, `hotel`, `parking`, `police`, `speed_camera`
|
|
120
|
+
|
|
121
|
+
## Error handling
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from elara_route import ElaraAuthError, ElaraRateLimitError, ElaraValidationError
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
route = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
128
|
+
except ElaraValidationError as e:
|
|
129
|
+
print("Bad input:", e.detail)
|
|
130
|
+
except ElaraRateLimitError as e:
|
|
131
|
+
print(f"Retry in {e.retry_after}s")
|
|
132
|
+
except ElaraAuthError:
|
|
133
|
+
print("Check your API key")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Run tests
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pytest tests/ -v
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Run example
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
set ELARA_API_KEY=elara_your_key
|
|
146
|
+
python examples/quickstart.py
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Free tier
|
|
150
|
+
|
|
151
|
+
25,000 routes/month, no credit card. [Get your key →](https://route.elara-cortex.com/developers)
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT — see [LICENSE](LICENSE).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
**ELARA·CORTEX** · Mathematical infrastructure for complex systems · Institute for Decision Systems & Number Theory
|
|
160
|
+
|
|
161
|
+
**AUTHOR:** .LEKOLA · CODE ATELIER · GitHub: [ELARA-CORTEX-MATH-INFRA](https://github.com/ELARA-CORTEX-MATH-INFRA)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
elara_route/__init__.py
|
|
5
|
+
elara_route/client.py
|
|
6
|
+
elara_route/constants.py
|
|
7
|
+
elara_route/exceptions.py
|
|
8
|
+
elara_route/py.typed
|
|
9
|
+
elara_route.egg-info/PKG-INFO
|
|
10
|
+
elara_route.egg-info/SOURCES.txt
|
|
11
|
+
elara_route.egg-info/dependency_links.txt
|
|
12
|
+
elara_route.egg-info/requires.txt
|
|
13
|
+
elara_route.egg-info/top_level.txt
|
|
14
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
elara_route
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0,<75.0", "wheel>=0.41.0,<1.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "elara-route"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the Elara Route API — road, drone, maritime, AV routing + geocoding + fleet tracking"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {file = "LICENSE"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = ".LEKOLA · CODE ATELIER", email = "hello@elara-cortex.com"}
|
|
13
|
+
]
|
|
14
|
+
keywords = ["routing", "navigation", "geocoding", "fleet", "drone", "africa", "api", "sdk", "elara"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
]
|
|
27
|
+
requires-python = ">=3.9,<4.0"
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://route.elara-cortex.com"
|
|
32
|
+
Documentation = "https://route.elara-cortex.com/developers"
|
|
33
|
+
Repository = "https://github.com/ELARA-CORTEX-MATH-INFRA/elara-route-python"
|
|
34
|
+
Issues = "https://github.com/ELARA-CORTEX-MATH-INFRA/elara-route-python/issues"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=7.4.0,<9.0.0",
|
|
39
|
+
"pytest-mock>=3.12.0,<4.0.0",
|
|
40
|
+
"build>=1.0.0,<2.0.0",
|
|
41
|
+
"twine>=4.0.0,<6.0.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["."]
|
|
46
|
+
include = ["elara_route*"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
target-version = "py39"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORGANISATION: ELARA·CORTEX
|
|
3
|
+
Mathematical infrastructure for complex systems
|
|
4
|
+
Institute for Decision Systems & Number Theory
|
|
5
|
+
AUTHOR: .LEKOLA · CODE ATELIER
|
|
6
|
+
GITHUB_ORG: ELARA-CORTEX-MATH-INFRA
|
|
7
|
+
|
|
8
|
+
NODE_ID: SDK-PY-ROUTE-TEST-CLIENT
|
|
9
|
+
PROJECT_ID: elara-route-python
|
|
10
|
+
PARENT_NODE: sdk/python/tests
|
|
11
|
+
PURPOSE: Unit tests for ElaraClient HTTP wrapper (mocked, no live API key).
|
|
12
|
+
|
|
13
|
+
CHANGE_ID: CHG-20260617-SDK-FULL
|
|
14
|
+
CHANGE_NOTE: Full SDK test suite.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import io
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from unittest import mock
|
|
24
|
+
|
|
25
|
+
import pytest
|
|
26
|
+
|
|
27
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
28
|
+
if str(ROOT) not in sys.path:
|
|
29
|
+
sys.path.insert(0, str(ROOT))
|
|
30
|
+
|
|
31
|
+
from elara_route import ElaraAuthError, ElaraClient, ElaraRateLimitError, ElaraValidationError
|
|
32
|
+
from elara_route.constants import NEARBY_CATEGORIES
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _mock_response(payload: dict, status: int = 200):
|
|
36
|
+
body = json.dumps(payload).encode()
|
|
37
|
+
resp = mock.Mock()
|
|
38
|
+
resp.read.return_value = body
|
|
39
|
+
resp.__enter__ = mock.Mock(return_value=resp)
|
|
40
|
+
resp.__exit__ = mock.Mock(return_value=False)
|
|
41
|
+
return resp
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@mock.patch("urllib.request.urlopen")
|
|
45
|
+
def test_route_success(mock_urlopen):
|
|
46
|
+
mock_urlopen.return_value = _mock_response(
|
|
47
|
+
{"distance_km": 13.28, "eta_min": 18.4, "engine": ".LEKOLA CORTEX"}
|
|
48
|
+
)
|
|
49
|
+
client = ElaraClient(api_key="elara_test")
|
|
50
|
+
result = client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
51
|
+
assert result["distance_km"] == 13.28
|
|
52
|
+
req = mock_urlopen.call_args[0][0]
|
|
53
|
+
assert req.get_header("X-api-key") == "elara_test"
|
|
54
|
+
assert req.full_url.endswith("/v1/route")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_missing_api_key_raises():
|
|
58
|
+
with pytest.raises(ElaraAuthError):
|
|
59
|
+
ElaraClient(api_key="")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@mock.patch("urllib.request.urlopen")
|
|
63
|
+
def test_rate_limit(mock_urlopen):
|
|
64
|
+
import urllib.error
|
|
65
|
+
|
|
66
|
+
err = urllib.error.HTTPError(
|
|
67
|
+
url="https://route.elara-cortex.com/v1/route",
|
|
68
|
+
code=429,
|
|
69
|
+
msg="Too Many Requests",
|
|
70
|
+
hdrs={"Retry-After": "30"},
|
|
71
|
+
fp=io.BytesIO(b'{"detail":"slow down"}'),
|
|
72
|
+
)
|
|
73
|
+
mock_urlopen.side_effect = err
|
|
74
|
+
client = ElaraClient(api_key="elara_test")
|
|
75
|
+
with pytest.raises(ElaraRateLimitError) as exc:
|
|
76
|
+
client.route(from_=[-26.1, 28.0], to=[-26.2, 28.04])
|
|
77
|
+
assert exc.value.retry_after == 30
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_nearby_invalid_category():
|
|
81
|
+
client = ElaraClient(api_key="elara_test")
|
|
82
|
+
with pytest.raises(ElaraValidationError):
|
|
83
|
+
client.nearby(lat=-26.1, lon=28.0, category="invalid")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_nearby_categories_match_openapi():
|
|
87
|
+
assert "speed_camera" in NEARBY_CATEGORIES
|
|
88
|
+
assert "checkpoint" in NEARBY_CATEGORIES
|
|
89
|
+
assert "all" not in NEARBY_CATEGORIES
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@mock.patch("urllib.request.urlopen")
|
|
93
|
+
def test_signup_returns_client(mock_urlopen):
|
|
94
|
+
mock_urlopen.return_value = _mock_response({"api_key": "elara_new_key"})
|
|
95
|
+
client = ElaraClient.signup("dev@example.com", "secure-pass-123")
|
|
96
|
+
assert isinstance(client, ElaraClient)
|
|
97
|
+
assert client._key == "elara_new_key"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@mock.patch("urllib.request.urlopen")
|
|
101
|
+
def test_av_route(mock_urlopen):
|
|
102
|
+
mock_urlopen.return_value = _mock_response({"distance_km": 5.0, "eta_min": 8.0})
|
|
103
|
+
client = ElaraClient(api_key="elara_test")
|
|
104
|
+
result = client.av_route(from_=[-26.1, 28.0], to=[-26.15, 28.05])
|
|
105
|
+
assert result["distance_km"] == 5.0
|
|
106
|
+
req = mock_urlopen.call_args[0][0]
|
|
107
|
+
assert "/v1/av/route" in req.full_url
|