Source code for krakenex.api

# This file is part of krakenex.
#
# krakenex is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# krakenex is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser
# General Public LICENSE along with krakenex. If not, see
# <http://www.gnu.org/licenses/lgpl-3.0.txt>.


"""Kraken.com cryptocurrency Exchange API."""


import json
import urllib.request, urllib.parse, urllib.error

# private query nonce
import time

# private query signing
import hashlib
import hmac
import base64

from . import connection


[docs]class API(object): """ Maps a key/secret pair to a connection. Specifying either is optional. .. note:: If a connection is not set, a new one will be opened on first query. If a connection is set, during creation or as a result of a previous query, it will be reused for subsequent queries. However, its state is not checked. .. note:: No timeout handling or query rate limiting is performed. .. note:: If a private query is performed without setting a key/secret pair, the effects are undefined. """
[docs] def __init__(self, key = '', secret = '', conn = None): """ Create an object with authentication information. :param key: key required to make queries to the API :type key: str :param secret: private key used to sign API messages :type secret: str :param conn: existing connection object to use :type conn: krakenex.Connection :returns: None """ self.key = key self.secret = secret self.uri = 'https://api.kraken.com' self.apiversion = '0' self.conn = conn return
[docs] def load_key(self, path): """ Load key and secret from file. Expected file format is key and secret on separate lines. :param path: path to keyfile :type path: str :returns: None """ with open(path, 'r') as f: self.key = f.readline().strip() self.secret = f.readline().strip() return
# FIXME: use @property instead?
[docs] def set_connection(self, conn): """ Set an existing connection to be used as a default in queries. .. note:: May become deprecated in future versions. :param conn: existing connection object to use :type conn: krakenex.Connection :returns: None """ self.conn = conn return
[docs] def _query(self, urlpath, req = {}, conn = None, headers = {}): """ Low-level query handling. Preferrably use :py:meth:`query_private` or :py:meth:`query_public` instead. :param urlpath: API URL path sans host :type urlpath: str :param req: additional API request parameters :type req: dict :param conn: existing connection object to use :type conn: krakenex.Connection :param headers: HTTPS headers :type headers: dict :returns: :py:func:`json.loads`-deserialised Python object """ url = self.uri + urlpath if conn is None: if self.conn is None: conn = connection.Connection() else: conn = self.conn ret = conn._request(url, req, headers) return json.loads(ret)
[docs] def query_public(self, method, req = {}, conn = None): """ API queries that do not require a valid key/secret pair. :param method: API method name :type method: str :param req: additional API request parameters :type req: dict :param conn: existing connection object to use :type conn: krakenex.Connection :returns: :py:func:`json.loads`-deserialised Python object """ urlpath = '/' + self.apiversion + '/public/' + method return self._query(urlpath, req, conn)
[docs] def query_private(self, method, req={}, conn = None): """ API queries that require a valid key/secret pair. :param method: API method name :type method: str :param req: additional API request parameters :type req: dict :param conn: existing connection object to use :type conn: krakenex.Connection :returns: :py:func:`json.loads`-deserialised Python object """ # TODO: check if self.{key,secret} are set urlpath = '/' + self.apiversion + '/private/' + method req['nonce'] = int(1000*time.time()) postdata = urllib.parse.urlencode(req) # Unicode-objects must be encoded before hashing encoded = (str(req['nonce']) + postdata).encode() message = urlpath.encode() + hashlib.sha256(encoded).digest() signature = hmac.new(base64.b64decode(self.secret), message, hashlib.sha512) sigdigest = base64.b64encode(signature.digest()) headers = { 'API-Key': self.key, 'API-Sign': sigdigest.decode() } return self._query(urlpath, req, conn, headers)