Skip to content

Commit 78712d2

Browse files
nilsnoldenilsnolde
authored andcommitted
Ors v5 (#34)
* update validator with new restrictions * All necessary changes implemented * added self.req as a property
1 parent ec75b92 commit 78712d2

25 files changed

+1678
-1102
lines changed

CHANGELOG

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 2.0.0
2+
3+
- implement all backend changes from moving to openrouteservice v5
4+
- now all parameters are named like their backend equivalents, while keeping the old ors-py parameter names for backwards compatibility, but with deprecation warnings
5+
- validator validates ALL parameters
6+
- added a Client.req property, returning the actual `requests` request
7+
18
### v1.1.8
29

310
- make dependencies more sensible (#32)

README.rst

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ By using this library, you agree to the ORS `terms and conditions`_.
6767

6868
Requirements
6969
-----------------------------
70-
openrouteservice requires:
70+
openrouteservice-py requires:
7171

72-
- Python >= 2.7, 3.4, 3.5, 3.6
72+
- Python >= 2.7, 3.4, 3.5, 3.6, 3.7 (pip)
73+
- Python >= 2.7, 3.6, 3.7 (conda)
7374
- ``requests`` library
75+
- ``cerberus`` library
7476

75-
unit testing requires additionally the following Python libraries:
77+
Unit testing requires additionally the following Python libraries:
7678

7779
- ``nose``
7880
- ``responses``
@@ -90,7 +92,7 @@ To install the latest and greatest from source::
9092

9193
For ``conda`` users::
9294

93-
conda install -c nilsnolde openrouteservice
95+
conda install -c nilsnolde openrouteservice
9496

9597
This command group will install the library to your global environment. Also works in virtual environments.
9698

@@ -140,7 +142,7 @@ The slightly more verbose alternative, preserving your IDE's smart functions, is
140142
Decode Polyline
141143
^^^^^^^^^^^^^^^^^^^^^^^^^^
142144
By default, the directions API returns `encoded polylines <https://developers.google.com/maps/documentation/utilities/polylinealgorithm>`_.
143-
To decode to a ``dict``, which is GeoJSON-ready, simply do
145+
To decode to a ``dict``, which is a GeoJSON geometry object, simply do
144146

145147
.. code:: python
146148
@@ -168,7 +170,7 @@ Although errors in query creation should be handled quite decently, you can do a
168170
169171
coords = ((8.34234,48.23424),(8.34423,48.26424))
170172
171-
client = openrouteservice.Client(key='') # Specify your personal API key
173+
client = openrouteservice.Client()
172174
client.directions(coords, dry_run='true')
173175
174176
Local ORS instance
@@ -182,16 +184,20 @@ If you're hosting your own ORS instance, you can alter the ``base_url`` paramete
182184
coords = ((8.34234,48.23424),(8.34423,48.26424))
183185
184186
# key can be omitted for local host
185-
client = openrouteservice.Client(key='',
186-
base_url='https://foo/bar')
187-
188-
# url is the extension for your endpoint, no trailing slashes!
189-
# params has to be passed explicitly, refer to API reference for details
190-
routes = client.request(url='/directions',
191-
params={'coordinates': coords,
192-
'profile': 'driving-hgv'
193-
}
194-
)
187+
client = openrouteservice.Client(base_url='http://localhost/ors')
188+
189+
# Only works if you didn't change the ORS endpoints manually
190+
routes = client.directions(coords)
191+
192+
# If you did change the ORS endpoints for some reason
193+
# you'll have to pass url and required parameters explicitly:
194+
routes = client.request(
195+
url='/new_url',
196+
post_json={
197+
'coordinates': coords,
198+
'profile': 'driving-car',
199+
'format': 'geojson'
200+
})
195201
196202
Support
197203
--------

openrouteservice/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# the License.
1818
#
1919

20-
__version__ = "1.1.7"
20+
__version__ = "2.0.0"
2121

2222
# Make sure QGIS plugin can import openrouteservice-py
2323

openrouteservice/client.py

Lines changed: 76 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
from datetime import timedelta
2626
import functools
2727
import requests
28+
import json
2829
import random
2930
import time
30-
import collections
31+
import warnings
3132

3233
from openrouteservice import exceptions, __version__
3334

@@ -45,15 +46,15 @@
4546
class Client(object):
4647
"""Performs requests to the ORS API services."""
4748

48-
def __init__(self, key=None,
49-
base_url=_DEFAULT_BASE_URL,
49+
def __init__(self,
50+
key=None,
51+
base_url=_DEFAULT_BASE_URL,
5052
timeout=60,
5153
retry_timeout=60,
5254
requests_kwargs=None,
53-
queries_per_minute=40,
54-
retry_over_query_limit=False):
55+
retry_over_query_limit=True):
5556
"""
56-
:param key: ORS API key. Required.
57+
:param key: ORS API key.
5758
:type key: string
5859
5960
:param base_url: The base URL for the request. Defaults to the ORS API
@@ -68,52 +69,59 @@ def __init__(self, key=None,
6869
seconds.
6970
:type retry_timeout: int
7071
72+
:param requests_kwargs: Extra keyword arguments for the requests
73+
library, which among other things allow for proxy auth to be
74+
implemented. See the official requests docs for more info:
75+
http://docs.python-requests.org/en/latest/api/#main-interface
76+
:type requests_kwargs: dict
77+
7178
:param queries_per_minute: Number of queries per second permitted.
7279
If the rate limit is reached, the client will sleep for the
7380
appropriate amount of time before it runs the current query.
7481
Note, it won't help to initiate another client. This saves you the
7582
trouble of raised exceptions.
7683
:type queries_per_second: int
7784
78-
:param requests_kwargs: Extra keyword arguments for the requests
79-
library, which among other things allow for proxy auth to be
80-
implemented. See the official requests docs for more info:
81-
http://docs.python-requests.org/en/latest/api/#main-interface
82-
:type requests_kwargs: dict
85+
:param retry_over_query_limit: If True, the client will retry when query
86+
limit is reached (HTTP 429). Default False.
8387
"""
8488

85-
self.session = requests.Session()
86-
self.key = key
87-
self.base_url = base_url
88-
89-
self.timeout = timeout
90-
self.retry_over_query_limit = retry_over_query_limit
91-
self.retry_timeout = timedelta(seconds=retry_timeout)
92-
self.requests_kwargs = requests_kwargs or {}
93-
self.requests_kwargs.update({
89+
self._session = requests.Session()
90+
self._key = key
91+
self._base_url = base_url
92+
93+
if self._base_url == _DEFAULT_BASE_URL and key is None:
94+
raise ValueError("No API key was specified. Please visit https://openrouteservice.org/sign-up to create one.")
95+
96+
self._timeout = timeout
97+
self._retry_over_query_limit = retry_over_query_limit
98+
self._retry_timeout = timedelta(seconds=retry_timeout)
99+
self._requests_kwargs = requests_kwargs or {}
100+
self._requests_kwargs.update({
94101
"headers": {"User-Agent": _USER_AGENT,
95-
'Content-type': 'application/json'},
96-
"timeout": self.timeout
102+
'Content-type': 'application/json',
103+
"Authorization": self._key},
104+
"timeout": self._timeout,
97105
})
98106

99-
self.queries_per_minute = queries_per_minute
100-
self.sent_times = collections.deque("", queries_per_minute)
107+
self._req = None
101108

102-
def request(self,
103-
url, params,
104-
first_request_time=None,
105-
retry_counter=0,
106-
requests_kwargs=None,
107-
post_json=None,
108-
dry_run=None):
109+
def request(self,
110+
url,
111+
get_params=None,
112+
first_request_time=None,
113+
retry_counter=0,
114+
requests_kwargs=None,
115+
post_json=None,
116+
dry_run=None):
109117
"""Performs HTTP GET/POST with credentials, returning the body as
110118
JSON.
111119
112120
:param url: URL path for the request. Should begin with a slash.
113121
:type url: string
114122
115-
:param params: HTTP GET parameters.
116-
:type params: dict or list of key/value tuples
123+
:param get_params: HTTP GET parameters.
124+
:type get_params: dict or list of key/value tuples
117125
118126
:param first_request_time: The time of the first request (None if no
119127
retries have occurred).
@@ -135,8 +143,6 @@ def request(self,
135143
136144
:raises ApiError: when the API returns an error.
137145
:raises Timeout: if the request timed out.
138-
:raises TransportError: when something went wrong while trying to
139-
execute a request.
140146
141147
:rtype: dict from JSON response.
142148
"""
@@ -145,7 +151,7 @@ def request(self,
145151
first_request_time = datetime.now()
146152

147153
elapsed = datetime.now() - first_request_time
148-
if elapsed > self.retry_timeout:
154+
if elapsed > self._retry_timeout:
149155
raise exceptions.Timeout()
150156

151157
if retry_counter > 0:
@@ -158,69 +164,64 @@ def request(self,
158164
time.sleep(delay_seconds * (random.random() + 0.5))
159165

160166
authed_url = self._generate_auth_url(url,
161-
params,
167+
get_params,
162168
)
163169

164170
# Default to the client-level self.requests_kwargs, with method-level
165171
# requests_kwargs arg overriding.
166172
requests_kwargs = requests_kwargs or {}
167-
final_requests_kwargs = dict(self.requests_kwargs, **requests_kwargs)
168-
169-
# Check if the time of the nth previous query (where n is
170-
# queries_per_second) is under a second ago - if so, sleep for
171-
# the difference.
172-
if self.sent_times and len(self.sent_times) == self.queries_per_minute:
173-
elapsed_since_earliest = time.time() - self.sent_times[0]
174-
if elapsed_since_earliest < 60:
175-
print("Request limit of {} per minute exceeded. Wait for {} seconds".format(self.queries_per_minute,
176-
60 - elapsed_since_earliest))
177-
time.sleep(60 - elapsed_since_earliest)
173+
final_requests_kwargs = dict(self._requests_kwargs, **requests_kwargs)
178174

179175
# Determine GET/POST.
180-
# post_json is so far only sent from matrix call
181-
requests_method = self.session.get
176+
requests_method = self._session.get
182177

183178
if post_json is not None:
184-
requests_method = self.session.post
179+
requests_method = self._session.post
185180
final_requests_kwargs["json"] = post_json
186181

187182
# Only print URL and parameters for dry_run
188183
if dry_run:
189-
print("url:\n{}\nParameters:\n{}".format(self.base_url+authed_url,
190-
final_requests_kwargs))
184+
print("url:\n{}\nHeaders:\n{}".format(self._base_url + authed_url,
185+
json.dumps(final_requests_kwargs, indent=2)))
191186
return
192187

193188
try:
194-
response = requests_method(self.base_url + authed_url,
189+
response = requests_method(self._base_url + authed_url,
195190
**final_requests_kwargs)
191+
self._req = response.request
192+
196193
except requests.exceptions.Timeout:
197194
raise exceptions.Timeout()
198-
except Exception as e:
199-
raise exceptions.TransportError(e)
200195

201196
if response.status_code in _RETRIABLE_STATUSES:
202197
# Retry request.
203-
print('Server down.\nRetrying for the {}th time.'.format(retry_counter + 1))
198+
warnings.warn('Server down.\nRetrying for the {}th time.'.format(retry_counter + 1),
199+
UserWarning,
200+
stacklevel=1)
204201

205-
return self.request(url, params, first_request_time,
206-
retry_counter + 1, requests_kwargs, post_json)
202+
return self.request(url, get_params, first_request_time,
203+
retry_counter + 1, requests_kwargs, post_json)
207204

208205
try:
209206
result = self._get_body(response)
210-
self.sent_times.append(time.time())
207+
211208
return result
212209
except exceptions._RetriableRequest as e:
213-
if isinstance(e, exceptions._OverQueryLimit) and not self.retry_over_query_limit:
210+
if isinstance(e, exceptions._OverQueryLimit) and not self._retry_over_query_limit:
214211
raise
215212

216-
print('Rate limit exceeded.\nRetrying for the {}th time.'.format(retry_counter + 1))
213+
warnings.warn('Rate limit exceeded.\nRetrying for the {}th time.'.format(retry_counter + 1),
214+
UserWarning,
215+
stacklevel=1)
217216
# Retry request.
218-
return self.request(url, params, first_request_time,
219-
retry_counter + 1, requests_kwargs,
220-
post_json)
221-
except:
222-
raise
217+
return self.request(url, get_params, first_request_time,
218+
retry_counter + 1, requests_kwargs,
219+
post_json)
223220

221+
@property
222+
def req(self):
223+
"""Returns request object. Can be used in case of request failure."""
224+
return self._req
224225

225226
def _get_body(self, response):
226227
body = response.json()
@@ -229,14 +230,17 @@ def _get_body(self, response):
229230

230231
if status_code == 429:
231232
raise exceptions._OverQueryLimit(
232-
str(status_code), body)
233+
status_code,
234+
body
235+
)
233236
if status_code != 200:
234-
raise exceptions.ApiError(status_code,
235-
body)
237+
raise exceptions.ApiError(
238+
status_code,
239+
body
240+
)
236241

237242
return body
238243

239-
240244
def _generate_auth_url(self, path, params):
241245
"""Returns the path and query string portion of the request URL, first
242246
adding any necessary parameters.
@@ -254,17 +258,7 @@ def _generate_auth_url(self, path, params):
254258
if type(params) is dict:
255259
params = sorted(dict(**params).items())
256260

257-
# Only auto-add API key when using ORS. If own instance, API key must
258-
# be explicitly added to params
259-
if self.key:
260-
params.append(("api_key", self.key))
261-
return path + "?" + _urlencode_params(params)
262-
elif self.base_url != _DEFAULT_BASE_URL:
263-
return path + "?" + _urlencode_params(params)
264-
265-
raise ValueError("No API key specified. "
266-
"Visit https://go.openrouteservice.org/dev-dashboard/ "
267-
"to create one.")
261+
return path + "?" + _urlencode_params(params)
268262

269263

270264
from openrouteservice.directions import directions

openrouteservice/convert.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def _has_method(arg, method):
129129

130130

131131
def decode_polyline(polyline, is3d=False):
132-
"""Decodes a Polyline string into a GeoJSON structure.
132+
"""Decodes a Polyline string into a GeoJSON geometry.
133133
134134
:param polyline: An encoded polyline, only the geometry.
135135
:type polyline: string

0 commit comments

Comments
 (0)