Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Retry-on-failure using NIH approach #100

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
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