justrouting 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 justrouting
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.
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: justrouting
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the JustRouting API — routing, distance matrices, and vehicle routing optimization across Southeast Asia.
5
+ Author: justrouting
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://justrouting.tech
8
+ Keywords: routing,osrm,vroom,matrix,optimization,logistics
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering :: GIS
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7; extra == "dev"
24
+ Requires-Dist: build>=1.2; extra == "dev"
25
+ Requires-Dist: twine>=5; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # JustRouting Python Client
29
+
30
+ Official Python client for the [JustRouting](https://justrouting.tech) API — routing, distance matrices, and vehicle routing optimization across Southeast Asia.
31
+
32
+ No dependencies outside the standard library. Requires Python 3.9+.
33
+
34
+ ## Install
35
+
36
+ ```shell
37
+ # (Not yet available on PyPI)
38
+ pip install justrouting
39
+ ```
40
+
41
+ > Or install from a clone of this repository:
42
+
43
+ ```shell
44
+ pip install -e .
45
+ ```
46
+
47
+ ```python
48
+ import justrouting
49
+ ```
50
+
51
+ ## Quickstart
52
+
53
+ ```python
54
+ import justrouting
55
+
56
+ client = justrouting.Client("YOUR_API_KEY")
57
+
58
+ route = client.routes.get(
59
+ justrouting.RouteRequest(
60
+ origin=[103.8198, 1.3521],
61
+ destination=[101.6869, 3.1390],
62
+ ),
63
+ timeout=30,
64
+ )
65
+
66
+ print(f"Distance: {route.distance / 1000:.1f} km")
67
+ ```
68
+
69
+ > The coordinates above are Singapore and Kuala Lumpur, which span two countries. See [Coordinates must share a country](#coordinates-must-share-a-country) — the runnable examples use same-country pairs.
70
+
71
+ ## Services
72
+
73
+ A `Client` exposes four services.
74
+
75
+ ### Routes
76
+
77
+ `Routes.get` returns the best route. `Routes.get_all` additionally returns alternatives and the snapped input waypoints.
78
+
79
+ ```python
80
+ route = client.routes.get(justrouting.RouteRequest(
81
+ origin=[103.8198, 1.3521],
82
+ destination=[103.9915, 1.3644],
83
+ waypoints=[[103.8514, 1.2897]], # stops in order
84
+ overview="full", # full geometry
85
+ steps=True, # turn-by-turn
86
+ ))
87
+
88
+ print(route.distance) # metres
89
+ print(route.duration) # seconds
90
+ ```
91
+
92
+ `route.geometry` holds whichever encoding you asked for:
93
+
94
+ ```python
95
+ polyline = route.geometry.polyline() # default, and "polyline6"
96
+ line = route.geometry.geojson() # when geometries="geojson"
97
+ ```
98
+
99
+ ### Matrix
100
+
101
+ Travel time and distance between many points at once.
102
+
103
+ ```python
104
+ m = client.matrix.get(justrouting.MatrixRequest(
105
+ coordinates=[depot, stop_a, stop_b],
106
+ sources=[0], # only the depot row; cheaper than N×N
107
+ destinations=[1, 2],
108
+ ))
109
+
110
+ seconds = m.duration(0, 1)
111
+ if seconds is not None:
112
+ print(f"depot -> stopA: {seconds / 60:.0f} min")
113
+ ```
114
+
115
+ The accessors return `None` for unreachable pairs. The API reports those as `null`, which is deliberately kept distinct from a genuine zero.
116
+
117
+ ### Optimization
118
+
119
+ Assign tasks to a fleet and order each vehicle's stops.
120
+
121
+ ```python
122
+ solution = client.optimization.solve(justrouting.OptimizationRequest(
123
+ vehicles=[justrouting.Vehicle(
124
+ id=1, start=depot, end=depot, capacity=[4],
125
+ )],
126
+ jobs=[
127
+ justrouting.Job(id=1, location=stop_a, delivery=[1], service=300),
128
+ justrouting.Job(id=2, location=stop_b, delivery=[2], service=300),
129
+ ],
130
+ ))
131
+
132
+ for route in solution.routes:
133
+ print(f"vehicle {route.vehicle}: {len(route.steps)} stops")
134
+ print(len(solution.unassigned), "task(s) could not be served")
135
+ ```
136
+
137
+ Use `Shipments` instead of `Jobs` for pickup-and-delivery pairs that must be served in order by the same vehicle.
138
+
139
+ ### Health
140
+
141
+ The only call that works without an API key, which makes it a useful connectivity check.
142
+
143
+ ```python
144
+ health = client.health.get()
145
+ print(health.ok(), health.upstreams)
146
+ ```
147
+
148
+ ## Error handling
149
+
150
+ Every failure raises a subclass of `justrouting.Error`. Classify it with `except` clauses rather than matching on message text:
151
+
152
+ ```python
153
+ try:
154
+ route = client.routes.get(req)
155
+ except justrouting.QuotaExceededError:
156
+ # daily allowance used up — retrying will not help
157
+ except justrouting.RateLimitedError:
158
+ # throttled; the client already retried
159
+ except justrouting.CrossCountryError:
160
+ # coordinates span more than one country
161
+ except justrouting.NoRouteError:
162
+ # no road connects these points
163
+ ```
164
+
165
+ | Exception | Meaning |
166
+ | --- | --- |
167
+ | `UnauthorizedError` | API key missing, invalid, or revoked |
168
+ | `RateLimitedError` | Throttled (per-second limit or daily quota) |
169
+ | `QuotaExceededError` | Daily quota exhausted; retrying will not help (subclasses `RateLimitedError`) |
170
+ | `PlanLimitExceededError` | Too many matrix coordinates, jobs, or vehicles |
171
+ | `CrossCountryError` | Coordinates span more than one country |
172
+ | `InvalidCoordinatesError` | Coordinate malformed or out of range |
173
+ | `NoRouteError` | No route exists between the points |
174
+ | `UpstreamUnavailableError` | Routing engine unreachable; usually transient |
175
+ | `InvalidRequestError` | Rejected locally before any request was sent |
176
+
177
+ Catch `justrouting.Error` itself when you need the status code, engine code, or raw body:
178
+
179
+ ```python
180
+ except justrouting.Error as e:
181
+ print(f"HTTP {e.status_code}: {e.message}")
182
+ print(e.body) # raw response body, truncated
183
+ ```
184
+
185
+ Failures that never produced an API response raise `TransportError` (network-level, retried like a 5xx) or `DecodeError` (unparseable success response) instead; neither is a subclass of `Error`.
186
+
187
+ ## Configuration
188
+
189
+ | Argument | Default | Purpose |
190
+ | --- | --- | --- |
191
+ | `api_key` | — | Sent as `Authorization: Bearer` on every authenticated request |
192
+ | `base_url` | `https://api.justrouting.tech` | Target a local or staging server |
193
+ | `user_agent` | `justrouting-py/<version>` | Identify your application |
194
+ | `max_retries` | `2` | Retry budget on top of the initial attempt |
195
+ | `backoff` | 500ms → 8s, jittered | Callable replacing the retry delay schedule |
196
+ | `timeout` | `30` | Seconds per HTTP attempt |
197
+
198
+ Invalid options raise `ValueError` immediately. (The Go client defers them to the first request because its constructor cannot fail; Python constructors can.)
199
+
200
+ ### Timeouts and retries
201
+
202
+ Rate limits (429), server errors (5xx), and transport failures are retried with exponential backoff and jitter; a `Retry-After` header takes precedence when present. Other 4xx responses are returned immediately — they would fail identically on a retry and would still consume quota.
203
+
204
+ `Client(timeout=...)` covers a single HTTP attempt, like `http.Client.Timeout` in Go. Pass the per-call `timeout` argument to bound the whole retry sequence:
205
+
206
+ ```python
207
+ route = client.routes.get(req, timeout=30) # 30s total, retries included
208
+ ```
209
+
210
+ Proxy configuration follows the standard `HTTP_PROXY` / `HTTPS_PROXY` environment variables.
211
+
212
+ ## Things to know
213
+
214
+ ### Coordinates are `[longitude, latitude]`
215
+
216
+ This is the GeoJSON order, and the reverse of the "lat, lng" used by most map UIs. Swapped coordinates are usually caught locally — a longitude in the latitude slot fails the `[-90, 90]` check before a request is sent — but a swap that stays in range will silently route somewhere unexpected.
217
+
218
+ `Point` is a `list` subclass, so both of these work:
219
+
220
+ ```python
221
+ origin=[103.8198, 1.3521]
222
+ origin=justrouting.Point([103.8198, 1.3521])
223
+ ```
224
+
225
+ ### Coordinates must share a country
226
+
227
+ Every coordinate in a single request must fall within one country; the API routes each request to a per-country engine. A Singapore → Kuala Lumpur request fails with `CrossCountryError`.
228
+
229
+ Supported countries: Brunei, Cambodia, Indonesia, Laos, Malaysia, Myanmar, the Philippines, Singapore, Thailand, and Vietnam.
230
+
231
+ ### Plan limits
232
+
233
+ | | Free | Hobby |
234
+ | --- | --- | --- |
235
+ | Requests per day | 100 | 10,000 |
236
+ | Requests per second | 5 | 10 |
237
+ | Matrix coordinates | 100 | 500 |
238
+ | Jobs per optimization | 100 | 1,000 |
239
+ | Vehicles per optimization | 10 | 50 |
240
+
241
+ Exceeding a size limit raises `PlanLimitExceededError`; exhausting the daily allowance raises `QuotaExceededError`.
242
+
243
+ ## Examples
244
+
245
+ Runnable scripts live in [`examples/`](./examples):
246
+
247
+ ```shell
248
+ export JUSTROUTING_API_KEY=<your key>
249
+ python examples/route.py
250
+ python examples/matrix.py
251
+ python examples/optimization.py
252
+ ```
253
+
254
+ ## Development
255
+
256
+ ```shell
257
+ pip install -e '.[dev]'
258
+ pytest
259
+ ```
260
+
261
+ The default suite runs entirely against local `http.server` instances — no network access and no API key.
262
+
263
+ Integration tests hit a live API and are behind a marker, so they never run by accident:
264
+
265
+ ```shell
266
+ pytest -m integration # health and auth only
267
+ JUSTROUTING_API_KEY=<key> pytest -m integration
268
+ JUSTROUTING_BASE_URL=http://localhost:8080 pytest -m integration
269
+ ```
270
+
271
+ ## Differences from the Go client
272
+
273
+ * Exceptions replace sentinel errors and `errors.Is`; `QuotaExceededError` subclasses `RateLimitedError`.
274
+ * Constructor options are keyword arguments; invalid ones raise `ValueError` immediately instead of being deferred to the first request.
275
+ * There is no `context.Context`; the per-call `timeout` argument plays the role of a context deadline.
276
+ * Matrix accessors return `float | None` instead of `(float, ok)`.
277
+ * There is no custom-HTTP-client option; use `base_url`, `timeout`, and the standard proxy environment variables instead.
278
+
279
+ ## License
280
+
281
+ [MIT](./LICENSE)
@@ -0,0 +1,254 @@
1
+ # JustRouting Python Client
2
+
3
+ Official Python client for the [JustRouting](https://justrouting.tech) API — routing, distance matrices, and vehicle routing optimization across Southeast Asia.
4
+
5
+ No dependencies outside the standard library. Requires Python 3.9+.
6
+
7
+ ## Install
8
+
9
+ ```shell
10
+ # (Not yet available on PyPI)
11
+ pip install justrouting
12
+ ```
13
+
14
+ > Or install from a clone of this repository:
15
+
16
+ ```shell
17
+ pip install -e .
18
+ ```
19
+
20
+ ```python
21
+ import justrouting
22
+ ```
23
+
24
+ ## Quickstart
25
+
26
+ ```python
27
+ import justrouting
28
+
29
+ client = justrouting.Client("YOUR_API_KEY")
30
+
31
+ route = client.routes.get(
32
+ justrouting.RouteRequest(
33
+ origin=[103.8198, 1.3521],
34
+ destination=[101.6869, 3.1390],
35
+ ),
36
+ timeout=30,
37
+ )
38
+
39
+ print(f"Distance: {route.distance / 1000:.1f} km")
40
+ ```
41
+
42
+ > The coordinates above are Singapore and Kuala Lumpur, which span two countries. See [Coordinates must share a country](#coordinates-must-share-a-country) — the runnable examples use same-country pairs.
43
+
44
+ ## Services
45
+
46
+ A `Client` exposes four services.
47
+
48
+ ### Routes
49
+
50
+ `Routes.get` returns the best route. `Routes.get_all` additionally returns alternatives and the snapped input waypoints.
51
+
52
+ ```python
53
+ route = client.routes.get(justrouting.RouteRequest(
54
+ origin=[103.8198, 1.3521],
55
+ destination=[103.9915, 1.3644],
56
+ waypoints=[[103.8514, 1.2897]], # stops in order
57
+ overview="full", # full geometry
58
+ steps=True, # turn-by-turn
59
+ ))
60
+
61
+ print(route.distance) # metres
62
+ print(route.duration) # seconds
63
+ ```
64
+
65
+ `route.geometry` holds whichever encoding you asked for:
66
+
67
+ ```python
68
+ polyline = route.geometry.polyline() # default, and "polyline6"
69
+ line = route.geometry.geojson() # when geometries="geojson"
70
+ ```
71
+
72
+ ### Matrix
73
+
74
+ Travel time and distance between many points at once.
75
+
76
+ ```python
77
+ m = client.matrix.get(justrouting.MatrixRequest(
78
+ coordinates=[depot, stop_a, stop_b],
79
+ sources=[0], # only the depot row; cheaper than N×N
80
+ destinations=[1, 2],
81
+ ))
82
+
83
+ seconds = m.duration(0, 1)
84
+ if seconds is not None:
85
+ print(f"depot -> stopA: {seconds / 60:.0f} min")
86
+ ```
87
+
88
+ The accessors return `None` for unreachable pairs. The API reports those as `null`, which is deliberately kept distinct from a genuine zero.
89
+
90
+ ### Optimization
91
+
92
+ Assign tasks to a fleet and order each vehicle's stops.
93
+
94
+ ```python
95
+ solution = client.optimization.solve(justrouting.OptimizationRequest(
96
+ vehicles=[justrouting.Vehicle(
97
+ id=1, start=depot, end=depot, capacity=[4],
98
+ )],
99
+ jobs=[
100
+ justrouting.Job(id=1, location=stop_a, delivery=[1], service=300),
101
+ justrouting.Job(id=2, location=stop_b, delivery=[2], service=300),
102
+ ],
103
+ ))
104
+
105
+ for route in solution.routes:
106
+ print(f"vehicle {route.vehicle}: {len(route.steps)} stops")
107
+ print(len(solution.unassigned), "task(s) could not be served")
108
+ ```
109
+
110
+ Use `Shipments` instead of `Jobs` for pickup-and-delivery pairs that must be served in order by the same vehicle.
111
+
112
+ ### Health
113
+
114
+ The only call that works without an API key, which makes it a useful connectivity check.
115
+
116
+ ```python
117
+ health = client.health.get()
118
+ print(health.ok(), health.upstreams)
119
+ ```
120
+
121
+ ## Error handling
122
+
123
+ Every failure raises a subclass of `justrouting.Error`. Classify it with `except` clauses rather than matching on message text:
124
+
125
+ ```python
126
+ try:
127
+ route = client.routes.get(req)
128
+ except justrouting.QuotaExceededError:
129
+ # daily allowance used up — retrying will not help
130
+ except justrouting.RateLimitedError:
131
+ # throttled; the client already retried
132
+ except justrouting.CrossCountryError:
133
+ # coordinates span more than one country
134
+ except justrouting.NoRouteError:
135
+ # no road connects these points
136
+ ```
137
+
138
+ | Exception | Meaning |
139
+ | --- | --- |
140
+ | `UnauthorizedError` | API key missing, invalid, or revoked |
141
+ | `RateLimitedError` | Throttled (per-second limit or daily quota) |
142
+ | `QuotaExceededError` | Daily quota exhausted; retrying will not help (subclasses `RateLimitedError`) |
143
+ | `PlanLimitExceededError` | Too many matrix coordinates, jobs, or vehicles |
144
+ | `CrossCountryError` | Coordinates span more than one country |
145
+ | `InvalidCoordinatesError` | Coordinate malformed or out of range |
146
+ | `NoRouteError` | No route exists between the points |
147
+ | `UpstreamUnavailableError` | Routing engine unreachable; usually transient |
148
+ | `InvalidRequestError` | Rejected locally before any request was sent |
149
+
150
+ Catch `justrouting.Error` itself when you need the status code, engine code, or raw body:
151
+
152
+ ```python
153
+ except justrouting.Error as e:
154
+ print(f"HTTP {e.status_code}: {e.message}")
155
+ print(e.body) # raw response body, truncated
156
+ ```
157
+
158
+ Failures that never produced an API response raise `TransportError` (network-level, retried like a 5xx) or `DecodeError` (unparseable success response) instead; neither is a subclass of `Error`.
159
+
160
+ ## Configuration
161
+
162
+ | Argument | Default | Purpose |
163
+ | --- | --- | --- |
164
+ | `api_key` | — | Sent as `Authorization: Bearer` on every authenticated request |
165
+ | `base_url` | `https://api.justrouting.tech` | Target a local or staging server |
166
+ | `user_agent` | `justrouting-py/<version>` | Identify your application |
167
+ | `max_retries` | `2` | Retry budget on top of the initial attempt |
168
+ | `backoff` | 500ms → 8s, jittered | Callable replacing the retry delay schedule |
169
+ | `timeout` | `30` | Seconds per HTTP attempt |
170
+
171
+ Invalid options raise `ValueError` immediately. (The Go client defers them to the first request because its constructor cannot fail; Python constructors can.)
172
+
173
+ ### Timeouts and retries
174
+
175
+ Rate limits (429), server errors (5xx), and transport failures are retried with exponential backoff and jitter; a `Retry-After` header takes precedence when present. Other 4xx responses are returned immediately — they would fail identically on a retry and would still consume quota.
176
+
177
+ `Client(timeout=...)` covers a single HTTP attempt, like `http.Client.Timeout` in Go. Pass the per-call `timeout` argument to bound the whole retry sequence:
178
+
179
+ ```python
180
+ route = client.routes.get(req, timeout=30) # 30s total, retries included
181
+ ```
182
+
183
+ Proxy configuration follows the standard `HTTP_PROXY` / `HTTPS_PROXY` environment variables.
184
+
185
+ ## Things to know
186
+
187
+ ### Coordinates are `[longitude, latitude]`
188
+
189
+ This is the GeoJSON order, and the reverse of the "lat, lng" used by most map UIs. Swapped coordinates are usually caught locally — a longitude in the latitude slot fails the `[-90, 90]` check before a request is sent — but a swap that stays in range will silently route somewhere unexpected.
190
+
191
+ `Point` is a `list` subclass, so both of these work:
192
+
193
+ ```python
194
+ origin=[103.8198, 1.3521]
195
+ origin=justrouting.Point([103.8198, 1.3521])
196
+ ```
197
+
198
+ ### Coordinates must share a country
199
+
200
+ Every coordinate in a single request must fall within one country; the API routes each request to a per-country engine. A Singapore → Kuala Lumpur request fails with `CrossCountryError`.
201
+
202
+ Supported countries: Brunei, Cambodia, Indonesia, Laos, Malaysia, Myanmar, the Philippines, Singapore, Thailand, and Vietnam.
203
+
204
+ ### Plan limits
205
+
206
+ | | Free | Hobby |
207
+ | --- | --- | --- |
208
+ | Requests per day | 100 | 10,000 |
209
+ | Requests per second | 5 | 10 |
210
+ | Matrix coordinates | 100 | 500 |
211
+ | Jobs per optimization | 100 | 1,000 |
212
+ | Vehicles per optimization | 10 | 50 |
213
+
214
+ Exceeding a size limit raises `PlanLimitExceededError`; exhausting the daily allowance raises `QuotaExceededError`.
215
+
216
+ ## Examples
217
+
218
+ Runnable scripts live in [`examples/`](./examples):
219
+
220
+ ```shell
221
+ export JUSTROUTING_API_KEY=<your key>
222
+ python examples/route.py
223
+ python examples/matrix.py
224
+ python examples/optimization.py
225
+ ```
226
+
227
+ ## Development
228
+
229
+ ```shell
230
+ pip install -e '.[dev]'
231
+ pytest
232
+ ```
233
+
234
+ The default suite runs entirely against local `http.server` instances — no network access and no API key.
235
+
236
+ Integration tests hit a live API and are behind a marker, so they never run by accident:
237
+
238
+ ```shell
239
+ pytest -m integration # health and auth only
240
+ JUSTROUTING_API_KEY=<key> pytest -m integration
241
+ JUSTROUTING_BASE_URL=http://localhost:8080 pytest -m integration
242
+ ```
243
+
244
+ ## Differences from the Go client
245
+
246
+ * Exceptions replace sentinel errors and `errors.Is`; `QuotaExceededError` subclasses `RateLimitedError`.
247
+ * Constructor options are keyword arguments; invalid ones raise `ValueError` immediately instead of being deferred to the first request.
248
+ * There is no `context.Context`; the per-call `timeout` argument plays the role of a context deadline.
249
+ * Matrix accessors return `float | None` instead of `(float, ok)`.
250
+ * There is no custom-HTTP-client option; use `base_url`, `timeout`, and the standard proxy environment variables instead.
251
+
252
+ ## License
253
+
254
+ [MIT](./LICENSE)
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "justrouting"
7
+ version = "0.1.0"
8
+ description = "Official Python client for the JustRouting API — routing, distance matrices, and vehicle routing optimization across Southeast Asia."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [{ name = "justrouting" }]
12
+ keywords = ["routing", "osrm", "vroom", "matrix", "optimization", "logistics"]
13
+ requires-python = ">=3.9"
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Scientific/Engineering :: GIS",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://justrouting.tech"
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=7", "build>=1.2", "twine>=5"]
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
38
+ pythonpath = ["src"]
39
+ addopts = "-m 'not integration'"
40
+ markers = [
41
+ "integration: tests that hit the live JustRouting API (excluded by default)",
42
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+