Skip to content

Commit bd15b2d

Browse files
geoffreybauduinyadutaf
authored andcommitted
feat(raw_call): adding raw_call method to retrieve directly the vendored requests.Response object
Signed-off-by: Geoffrey Bauduin <[email protected]>
1 parent 5959136 commit bd15b2d

File tree

2 files changed

+65
-38
lines changed

2 files changed

+65
-38
lines changed

ovh/client.py

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def delete(self, _target, _need_auth=True):
388388

389389
def call(self, method, path, data=None, need_auth=True):
390390
"""
391-
Lowest level call helper. If ``consumer_key`` is not ``None``, inject
391+
Low level call helper. If ``consumer_key`` is not ``None``, inject
392392
authentication headers and sign the request.
393393
394394
Request signature is a sha1 hash on following fields, joined by '+'
@@ -399,51 +399,16 @@ def call(self, method, path, data=None, need_auth=True):
399399
- body
400400
- server current time (takes time delta into account)
401401
402-
:param str method: HTTP verb. Usualy one of GET, POST, PUT, DELETE
402+
:param str method: HTTP verb. Usually one of GET, POST, PUT, DELETE
403403
:param str path: api entrypoint to call, relative to endpoint base path
404404
:param data: any json serializable data to send as request's body
405405
:param boolean need_auth: if False, bypass signature
406406
:raises HTTPError: when underlying request failed for network reason
407407
:raises InvalidResponse: when API response could not be decoded
408408
"""
409-
body = ''
410-
target = self._endpoint + path
411-
headers = {
412-
'X-Ovh-Application': self._application_key
413-
}
414-
415-
# include payload
416-
if data is not None:
417-
headers['Content-type'] = 'application/json'
418-
body = json.dumps(data)
419-
420-
# sign request. Never sign 'time' or will recuse infinitely
421-
if need_auth:
422-
if not self._application_secret:
423-
raise InvalidKey("Invalid ApplicationSecret '%s'" %
424-
self._application_secret)
425-
426-
if not self._consumer_key:
427-
raise InvalidKey("Invalid ConsumerKey '%s'" %
428-
self._consumer_key)
429-
430-
now = str(int(time.time()) + self.time_delta)
431-
signature = hashlib.sha1()
432-
signature.update("+".join([
433-
self._application_secret, self._consumer_key,
434-
method.upper(), target,
435-
body,
436-
now
437-
]).encode('utf-8'))
438-
439-
headers['X-Ovh-Consumer'] = self._consumer_key
440-
headers['X-Ovh-Timestamp'] = now
441-
headers['X-Ovh-Signature'] = "$1$" + signature.hexdigest()
442-
443409
# attempt request
444410
try:
445-
result = self._session.request(method, target, headers=headers,
446-
data=body, timeout=self._timeout)
411+
result = self.raw_call(method=method, path=path, data=data, need_auth=need_auth)
447412
except RequestException as error:
448413
raise HTTPError("Low HTTP request failed error", error)
449414

@@ -484,3 +449,59 @@ def call(self, method, path, data=None, need_auth=True):
484449
raise NetworkError()
485450
else:
486451
raise APIError(json_result.get('message'), response=result)
452+
453+
def raw_call(self, method, path, data=None, need_auth=True):
454+
"""
455+
Lowest level call helper. If ``consumer_key`` is not ``None``, inject
456+
authentication headers and sign the request.
457+
Will return a vendored ``requests.Response`` object or let any ``requests`` exception pass through.
458+
459+
Request signature is a sha1 hash on following fields, joined by '+'
460+
- application_secret
461+
- consumer_key
462+
- METHOD
463+
- full request url
464+
- body
465+
- server current time (takes time delta into account)
466+
467+
:param str method: HTTP verb. Usually one of GET, POST, PUT, DELETE
468+
:param str path: api entrypoint to call, relative to endpoint base path
469+
:param data: any json serializable data to send as request's body
470+
:param boolean need_auth: if False, bypass signature
471+
"""
472+
body = ''
473+
target = self._endpoint + path
474+
headers = {
475+
'X-Ovh-Application': self._application_key
476+
}
477+
478+
# include payload
479+
if data is not None:
480+
headers['Content-type'] = 'application/json'
481+
body = json.dumps(data)
482+
483+
# sign request. Never sign 'time' or will recuse infinitely
484+
if need_auth:
485+
if not self._application_secret:
486+
raise InvalidKey("Invalid ApplicationSecret '%s'" %
487+
self._application_secret)
488+
489+
if not self._consumer_key:
490+
raise InvalidKey("Invalid ConsumerKey '%s'" %
491+
self._consumer_key)
492+
493+
now = str(int(time.time()) + self.time_delta)
494+
signature = hashlib.sha1()
495+
signature.update("+".join([
496+
self._application_secret, self._consumer_key,
497+
method.upper(), target,
498+
body,
499+
now
500+
]).encode('utf-8'))
501+
502+
headers['X-Ovh-Consumer'] = self._consumer_key
503+
headers['X-Ovh-Timestamp'] = now
504+
headers['X-Ovh-Signature'] = "$1$" + signature.hexdigest()
505+
506+
return self._session.request(method, target, headers=headers,
507+
data=body, timeout=self._timeout)

tests/test_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,12 @@ def test_call_signature(self, m_time_delta, m_req):
388388
# Restore configuration
389389
config.config = self._orig_config
390390

391+
@mock.patch('ovh.client.Session.request', return_value="Let's assume requests will return this")
392+
def test_raw_call(self, m_req):
393+
api = Client(ENDPOINT, APPLICATION_KEY, APPLICATION_SECRET)
394+
r = api.raw_call(FAKE_METHOD, FAKE_PATH, None, False)
395+
self.assertEqual(r, "Let's assume requests will return this")
396+
391397
# Perform real API tests.
392398
def test_endpoints(self):
393399
for endpoint in ENDPOINTS.keys():

0 commit comments

Comments
 (0)