Skip to content
83 changes: 77 additions & 6 deletions krakenex/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"""Kraken.com cryptocurrency Exchange API."""

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

# private query nonce
import time
Expand Down Expand Up @@ -57,6 +59,12 @@ def __init__(self, key='', secret=''):
:returns: None

"""

self._retry_config = {}
self._retry_config['forcelist'] = (104, 500, 502, 503, 504, 520, 521, 522, 523, 524)
self._retry_config['retries'] = 3
self._retry_config['backoff'] = 0.5

self.key = key
self.secret = secret
self.uri = 'https://api.kraken.com'
Expand Down Expand Up @@ -103,7 +111,65 @@ def load_key(self, path):
self.secret = f.readline().strip()
return

def _query(self, urlpath, data, headers=None, timeout=None):

@property
def retry_config(self):
""" Return the current retry configuration dict as a copy

:returns: dict containing the current retry configuration
"""

return self._retry_config.copy()


@retry_config.setter
def retry_config(self, retry_dict):
""" Set the internal retry configuration dict

.. note::
Does some error checking to ensure that all the keys in the new dict match with
those in the original configuration dict.

:param retry_dict: new configuration for retries
:type retry_dict: dict

:returns: None
"""

if(len(retry_dict.keys()) == len(self._retry_config.keys())):
if all(key in retry_dict for key in self._retry_config):
self._retry_config = retry_dict.copy()


def _retry_session(self, session=None):
""" Low-level configuration for retries

..note::
for documentation of this technique, refer to:
https://www.peterbe.com/plog/best-practice-with-retries-with-requests

:param session: Use an already existing session. If None, the session created during instantiation is used.
:type session: requests session object

:returns: requests session object
"""

if not session:
session = self.session

retry = Retry(total=None, read=self._retry_config['retries'], connect=self._retry_config['retries'], redirect=0,
status=self._retry_config['retries'], backoff_factor=self._retry_config['backoff'],
status_forcelist=self._retry_config['forcelist'],
method_whitelist=frozenset(['HEAD', 'TRACE', 'GET', 'PUT', 'OPTIONS', 'DELETE', 'POST']))

adapter = HTTPAdapter(max_retries=retry)
session.mount('https://', adapter)
session.mount('http://', adapter)

return session


def _query(self, urlpath, data, headers=None, timeout=None, retry=False):
""" Low-level query handling.

.. note::
Expand All @@ -130,8 +196,13 @@ def _query(self, urlpath, data, headers=None, timeout=None):
headers = {}

url = self.uri + urlpath

if(retry):
curr_session = self._retry_session()
else:
curr_session = self.session

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

if self.response.status_code not in (200, 201, 202):
Expand All @@ -140,7 +211,7 @@ def _query(self, urlpath, data, headers=None, timeout=None):
return self.response.json(**self._json_options)


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

:param method: API method name
Expand All @@ -159,9 +230,9 @@ 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, retry = retry)

def query_private(self, method, data=None, timeout=None):
def query_private(self, method, data=None, timeout=None, retry=False):
""" Performs an API query that requires a valid key/secret pair.

:param method: API method name
Expand Down Expand Up @@ -190,7 +261,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, retry = retry)

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