I'm trying to write a nice auth helper for kraken. I want it to be as automatic as possible, so it needs to:
time.time()*1000
) to the POST bodyI wrote the obvious code based on this answer:
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
#print("Auth got a %r" % type(request))
nonce = int(1000*time.time())
request.data = getattr(request, 'data', {})
request.data['nonce'] = nonce
request.prepare()
message = request.path_url + hashlib.sha256(str(nonce) + request.body).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
and them I'm calling it (from a wrapper method on another object) like:
def _request(self, method, url, **kwargs):
if not self._auth:
self._auth = KrakenAuth(key, secret)
if 'auth' not in kwargs:
kwargs['auth'] = self._auth
return self._session.request(method, URL + url, **kwargs)
...but it doesn't work. The commented-out print()
statement shows that it's getting a PreparedRequest
object not a Request
object, and thus the call to request.prepare()
is a call to PreparedRequest.prepare
does nothing useful because there's no request.data
because it's already been converted into a body
attribute.
You can't access the data
attribute of the request, because the authentication object is applied to a requests.PreparedRequest()
instance , which has no .data
attribute .
The normal flow for a Session.request()
call (used by all the request.<method>
and session.<method>
calls), is as follows:
Request()
instance is created with all the same arguments as the original callSession.prepare_request()
, which merges in session-stored base values with the arguments of the original call first, thenPreparedRequest()
instance is createdPreparedRequest.prepare()
method is called on that prepared request instance, passing in the merged data from the Request
instance and the session. prepare()
method calls the various self.prepare_*
methods, including PreparedRequest.prepare_auth()
. PreparedRequest.prepare_auth()
calls auth(self)
to give the authentication object a chance to attach information to the request. Unfortunately for you, at no point during this flow will the original data
mapping be available to anyone else but PreparedRequest.prepare()
and PreparedRequest.prepare_body()
, and in those methods the mapping is a local variable. You can't access it from the authentication object.
Your options are then:
To decode the body again, and call prepare_body()
with the updated mapping.
To not use an authentication object, but use the other path from my answer; to explicitly create a prepared request and manipulate data
first.
To play merry hell with the Python stack and extract locals from the prepare()
method that is two frames up. I really can't recommend this path.
To keep the authentication method encapsulated nicely, I'd go with decoding / re-encoding; the latter is simple enough by reusing PreparedRequest.prepare_body()
:
import base64
import hashlib
import hmac
import time
try:
# Python 3
from urllib.parse import parse_qs
except ImportError:
# Python 2
from urlparse import parse_qs
from requests import AuthBase
URL_ENCODED = 'application/x-www-form-urlencoded'
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
ctheader = request.headers.get('Content-Type')
assert (
request.method == 'POST' and (
ctheader == URL_ENCODED or
requests.headers.get('Content-Length') == '0'
)
), "Must be a POST request using form data, or empty"
# insert the nonce in the encoded body
data = parse_qs(request.body)
data['nonce'] = nonce
request.prepare_body(data, None, None)
body = request.body
if not isinstance(body, bytes): # Python 3
body = body.encode('latin1') # standard encoding for HTTP
message = request.path_url + hashlib.sha256(b'%s%s' % (nonce, body)).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.