25
25
from datetime import timedelta
26
26
import functools
27
27
import requests
28
+ import json
28
29
import random
29
30
import time
30
- import collections
31
+ import warnings
31
32
32
33
from openrouteservice import exceptions , __version__
33
34
45
46
class Client (object ):
46
47
"""Performs requests to the ORS API services."""
47
48
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 ,
50
52
timeout = 60 ,
51
53
retry_timeout = 60 ,
52
54
requests_kwargs = None ,
53
- queries_per_minute = 40 ,
54
- retry_over_query_limit = False ):
55
+ retry_over_query_limit = True ):
55
56
"""
56
- :param key: ORS API key. Required.
57
+ :param key: ORS API key.
57
58
:type key: string
58
59
59
60
:param base_url: The base URL for the request. Defaults to the ORS API
@@ -68,52 +69,59 @@ def __init__(self, key=None,
68
69
seconds.
69
70
:type retry_timeout: int
70
71
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
+
71
78
:param queries_per_minute: Number of queries per second permitted.
72
79
If the rate limit is reached, the client will sleep for the
73
80
appropriate amount of time before it runs the current query.
74
81
Note, it won't help to initiate another client. This saves you the
75
82
trouble of raised exceptions.
76
83
:type queries_per_second: int
77
84
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.
83
87
"""
84
88
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 ({
94
101
"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 ,
97
105
})
98
106
99
- self .queries_per_minute = queries_per_minute
100
- self .sent_times = collections .deque ("" , queries_per_minute )
107
+ self ._req = None
101
108
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 ):
109
117
"""Performs HTTP GET/POST with credentials, returning the body as
110
118
JSON.
111
119
112
120
:param url: URL path for the request. Should begin with a slash.
113
121
:type url: string
114
122
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
117
125
118
126
:param first_request_time: The time of the first request (None if no
119
127
retries have occurred).
@@ -135,8 +143,6 @@ def request(self,
135
143
136
144
:raises ApiError: when the API returns an error.
137
145
:raises Timeout: if the request timed out.
138
- :raises TransportError: when something went wrong while trying to
139
- execute a request.
140
146
141
147
:rtype: dict from JSON response.
142
148
"""
@@ -145,7 +151,7 @@ def request(self,
145
151
first_request_time = datetime .now ()
146
152
147
153
elapsed = datetime .now () - first_request_time
148
- if elapsed > self .retry_timeout :
154
+ if elapsed > self ._retry_timeout :
149
155
raise exceptions .Timeout ()
150
156
151
157
if retry_counter > 0 :
@@ -158,69 +164,64 @@ def request(self,
158
164
time .sleep (delay_seconds * (random .random () + 0.5 ))
159
165
160
166
authed_url = self ._generate_auth_url (url ,
161
- params ,
167
+ get_params ,
162
168
)
163
169
164
170
# Default to the client-level self.requests_kwargs, with method-level
165
171
# requests_kwargs arg overriding.
166
172
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 )
178
174
179
175
# 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
182
177
183
178
if post_json is not None :
184
- requests_method = self .session .post
179
+ requests_method = self ._session .post
185
180
final_requests_kwargs ["json" ] = post_json
186
181
187
182
# Only print URL and parameters for dry_run
188
183
if dry_run :
189
- print ("url:\n {}\n Parameters :\n {}" .format (self .base_url + authed_url ,
190
- final_requests_kwargs ))
184
+ print ("url:\n {}\n Headers :\n {}" .format (self ._base_url + authed_url ,
185
+ json . dumps ( final_requests_kwargs , indent = 2 ) ))
191
186
return
192
187
193
188
try :
194
- response = requests_method (self .base_url + authed_url ,
189
+ response = requests_method (self ._base_url + authed_url ,
195
190
** final_requests_kwargs )
191
+ self ._req = response .request
192
+
196
193
except requests .exceptions .Timeout :
197
194
raise exceptions .Timeout ()
198
- except Exception as e :
199
- raise exceptions .TransportError (e )
200
195
201
196
if response .status_code in _RETRIABLE_STATUSES :
202
197
# Retry request.
203
- print ('Server down.\n Retrying for the {}th time.' .format (retry_counter + 1 ))
198
+ warnings .warn ('Server down.\n Retrying for the {}th time.' .format (retry_counter + 1 ),
199
+ UserWarning ,
200
+ stacklevel = 1 )
204
201
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 )
207
204
208
205
try :
209
206
result = self ._get_body (response )
210
- self . sent_times . append ( time . time ())
207
+
211
208
return result
212
209
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 :
214
211
raise
215
212
216
- print ('Rate limit exceeded.\n Retrying for the {}th time.' .format (retry_counter + 1 ))
213
+ warnings .warn ('Rate limit exceeded.\n Retrying for the {}th time.' .format (retry_counter + 1 ),
214
+ UserWarning ,
215
+ stacklevel = 1 )
217
216
# 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 )
223
220
221
+ @property
222
+ def req (self ):
223
+ """Returns request object. Can be used in case of request failure."""
224
+ return self ._req
224
225
225
226
def _get_body (self , response ):
226
227
body = response .json ()
@@ -229,14 +230,17 @@ def _get_body(self, response):
229
230
230
231
if status_code == 429 :
231
232
raise exceptions ._OverQueryLimit (
232
- str (status_code ), body )
233
+ status_code ,
234
+ body
235
+ )
233
236
if status_code != 200 :
234
- raise exceptions .ApiError (status_code ,
235
- body )
237
+ raise exceptions .ApiError (
238
+ status_code ,
239
+ body
240
+ )
236
241
237
242
return body
238
243
239
-
240
244
def _generate_auth_url (self , path , params ):
241
245
"""Returns the path and query string portion of the request URL, first
242
246
adding any necessary parameters.
@@ -254,17 +258,7 @@ def _generate_auth_url(self, path, params):
254
258
if type (params ) is dict :
255
259
params = sorted (dict (** params ).items ())
256
260
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 )
268
262
269
263
270
264
from openrouteservice .directions import directions
0 commit comments