Skip to content
Open
10 changes: 7 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ to `semantic versioning`_.
.. _Keep a Changelog: http://keepachangelog.com/
.. _semantic versioning: http://semver.org/

[vX.Y.Z] - Unreleased
---------------------
[vX.Y.Z] - TODO
---------------

* Nothing (yet).
Added
^^^^^
* Allow configuring the ``krakenex.API`` object to retry the query
on certain HTTP error codes. By default, no retries will be attempted.
* TODO: docs on how, link here.

[v2.1.0] - 2018-04-20 (Fryday)
------------------------------
Expand Down
39 changes: 31 additions & 8 deletions krakenex/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import hashlib
import hmac
import base64
import logging

from . import version

Expand Down Expand Up @@ -66,7 +67,15 @@ def __init__(self, key='', secret=''):
'User-Agent': 'krakenex/' + version.__version__ + ' (+' + version.__url__ + ')'
})
self.response = None

# retry-on-failure configuration
self.retries = 0
self.cooldown = 15
self.successcodes = [200, 201, 202]
self.retrycodes = [504, 520]

self._json_options = {}

return

def json_options(self, **kwargs):
Expand Down Expand Up @@ -124,22 +133,36 @@ def _query(self, urlpath, data, headers=None, timeout=None):
:raises: :py:exc:`requests.HTTPError`: if response status not successful

"""
logger = logging.getLogger('krakenex.api')

if data is None:
data = {}
if headers is None:
headers = {}

url = self.uri + urlpath

self.response = self.session.post(url, data = data, headers = headers,
timeout = timeout)

if self.response.status_code not in (200, 201, 202):
self.response.raise_for_status()
attempts = 0
while attempts <= self.retries:
nonce = -1 if 'nonce' not in data.keys() else data['nonce'] # UGLY
logger.debug('Posting query: nonce %d, attempt %d.', nonce, attempts)
self.response = self.session.post(url, data=data, headers=headers,
timeout=timeout)
status = self.response.status_code
attempts += 1

if status in self.successcodes:
break
elif status in self.retrycodes and attempts <= self.retries:
logger.debug('HTTP error %d', status)
logger.debug('Sleeping for %d seconds', self.cooldown)
time.sleep(self.cooldown)
continue
else:
self.response.raise_for_status()

return self.response.json(**self._json_options)


def query_public(self, method, data=None, timeout=None):
""" Performs an API query that does not require a valid key/secret pair.

Expand All @@ -159,7 +182,7 @@ def query_public(self, method, data=None, timeout=None):

urlpath = '/' + self.apiversion + '/public/' + method

return self._query(urlpath, data, timeout = timeout)
return self._query(urlpath, data, timeout=timeout)

def query_private(self, method, data=None, timeout=None):
""" Performs an API query that requires a valid key/secret pair.
Expand Down Expand Up @@ -190,7 +213,7 @@ def query_private(self, method, data=None, timeout=None):
'API-Sign': self._sign(data, urlpath)
}

return self._query(urlpath, data, headers, timeout = timeout)
return self._query(urlpath, data, headers, timeout=timeout)

def _nonce(self):
""" Nonce counter.
Expand Down