Как мне подписать тело запроса. Запрос в методе __call__ объекта Auth?
Я пытаюсь написать хороший помощник для кракена. Я хочу, чтобы это было как можно более автоматическим, поэтому необходимо:
- добавить одноразовый номер (
time.time()*1000
) к телу POST - рассчитать подпись над телом POST
- поставить подпись в заголовки
Я написал очевидный код, основанный на этом ответе:
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
и их я вызываю (из метода-обертки для другого объекта), например:
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)
... но это не работает. Закомментированный print()
Заявление показывает, что он получает PreparedRequest
объект не Request
объект, и, следовательно, вызов request.prepare()
это вызов PreparedRequest.prepare
не делает ничего полезного, потому что нет request.data
потому что он уже был преобразован в body
приписывать.
1 ответ
Вы не можете получить доступ к data
атрибут запроса, потому что объект аутентификации применяется к requests.PreparedRequest()
экземпляр, который не имеет .data
атрибут
Нормальный поток для Session.request()
вызов (используется всеми request.<method>
а также session.<method>
звонки), выглядит следующим образом:
-
Request()
экземпляр создается со всеми теми же аргументами, что и исходный вызов - Запрос передан
Session.prepare_request()
, который сначала объединяет сохраненные в сеансе базовые значения с аргументами исходного вызова, затем -
PreparedRequest()
экземпляр создан -
PreparedRequest.prepare()
метод вызывается в этом подготовленном экземпляре запроса, передавая объединенные данные изRequest
экземпляр и сессия. -
prepare()
метод вызывает различныеself.prepare_*
методы, в том числеPreparedRequest.prepare_auth()
, PreparedRequest.prepare_auth()
звонкиauth(self)
дать объекту аутентификации возможность прикрепить информацию к запросу.
К сожалению для вас, ни в коем случае во время этого потока не будет оригинального data
картография будет доступна для всех, кроме PreparedRequest.prepare()
а также PreparedRequest.prepare_body()
и в этих методах отображение является локальной переменной. Вы не можете получить к нему доступ из объекта аутентификации.
Ваши варианты тогда:
Чтобы снова расшифровать тело, и вызвать
prepare_body()
с обновленным отображением.Не использовать объект аутентификации, но использовать другой путь из моего ответа; явно создать подготовленный запрос и манипулировать
data
первый.Играть в веселый ад с помощью стека Python и извлекать местных жителей из
prepare()
метод, который на два кадра вверх. Я действительно не могу рекомендовать этот путь.
Чтобы метод аутентификации был инкапсулирован, я бы пошел с декодированием / перекодированием; последнее достаточно просто, используя повторно 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