Cache ws-token on `Client` and auto-refresh
Add `_ws_token` cache attr to `Client` with a `force_renewal` flag on `get_ws_token()`. Drop the `token` param threading through `handle_order_requests()` and `handle_order_updates()` — all call sites now use `await client.get_ws_token()` instead. Deats, - `api.py`: add `_ws_token: str|None = None`, return cached token unless `force_renewal`, comment out `InvalidKey`/`InvalidSession` classes and `reg_err_types()` call (WIP move). - `broker.py`: drop `token` param from `handle_order_requests()`, `handle_order_updates()`, and call sites; replace all `token` refs with `await client.get_ws_token()`. - `subscribe()`: rework `InvalidSession` handling to match on `(etype_str, ev_msg)` tuple, call `get_ws_token(force_renewal=True)` and `continue` the sub-ack loop; extract `fmt_msg` var to avoid repeated `ppfmt()` calls. (this commit msg was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codemultihomed_w_multiaddrs
parent
d326eaccca
commit
6abdce16ea
|
|
@ -35,7 +35,7 @@ import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import base64
|
import base64
|
||||||
import tractor
|
import tractor
|
||||||
from tractor._exceptions import reg_err_types
|
# from tractor._exceptions import reg_err_types
|
||||||
import trio
|
import trio
|
||||||
|
|
||||||
from piker import config
|
from piker import config
|
||||||
|
|
@ -109,37 +109,37 @@ def get_kraken_signature(
|
||||||
return sigdigest.decode()
|
return sigdigest.decode()
|
||||||
|
|
||||||
|
|
||||||
class InvalidKey(ValueError):
|
# class InvalidKey(ValueError):
|
||||||
'''
|
# '''
|
||||||
EAPI:Invalid key
|
# EAPI:Invalid key
|
||||||
|
|
||||||
This error is returned when the API key used for the call is
|
# This error is returned when the API key used for the call is
|
||||||
either expired or disabled, please review the API key in your
|
# either expired or disabled, please review the API key in your
|
||||||
Settings -> API tab of account management or generate a new one
|
# Settings -> API tab of account management or generate a new one
|
||||||
and update your application.
|
# and update your application.
|
||||||
|
|
||||||
'''
|
# '''
|
||||||
|
|
||||||
|
|
||||||
class InvalidSession(RuntimeError):
|
# class InvalidSession(RuntimeError):
|
||||||
'''
|
# '''
|
||||||
ESession:Invalid session
|
# ESession:Invalid session
|
||||||
|
|
||||||
This error is returned when the ws API key used for an authenticated
|
# This error is returned when the ws API key used for an authenticated
|
||||||
sub/endpoint becomes stale, normally after a sufficient network
|
# sub/endpoint becomes stale, normally after a sufficient network
|
||||||
disconnect/outage.
|
# disconnect/outage.
|
||||||
|
|
||||||
Normally the sub will need to be restarted, likely re-init of the
|
# Normally the sub will need to be restarted, likely re-init of the
|
||||||
auth handshake sequence.
|
# auth handshake sequence.
|
||||||
|
|
||||||
'''
|
# '''
|
||||||
subscription: dict
|
# subscription: dict
|
||||||
|
|
||||||
|
|
||||||
reg_err_types([
|
# reg_err_types([
|
||||||
InvalidKey,
|
# InvalidKey,
|
||||||
InvalidSession,
|
# InvalidSession,
|
||||||
])
|
# ])
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
|
|
@ -176,6 +176,7 @@ class Client:
|
||||||
self._api_key = api_key
|
self._api_key = api_key
|
||||||
self._secret = secret
|
self._secret = secret
|
||||||
self.conf: dict[str, str] = config
|
self.conf: dict[str, str] = config
|
||||||
|
self._ws_token: str|None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pairs(self) -> dict[str, Pair]:
|
def pairs(self) -> dict[str, Pair]:
|
||||||
|
|
@ -263,13 +264,22 @@ class Client:
|
||||||
async def get_ws_token(
|
async def get_ws_token(
|
||||||
self,
|
self,
|
||||||
params: dict = {},
|
params: dict = {},
|
||||||
|
force_renewal: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
'''
|
'''
|
||||||
Get websocket token for authenticated data stream.
|
Get websocket token for authenticated data stream and cache
|
||||||
|
it for reuse.
|
||||||
|
|
||||||
Assert a value was actually received before return.
|
Assert a value was actually received before return.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
if (
|
||||||
|
not force_renewal
|
||||||
|
and
|
||||||
|
self._ws_token
|
||||||
|
):
|
||||||
|
return self._ws_token
|
||||||
|
|
||||||
resp = await self.endpoint(
|
resp = await self.endpoint(
|
||||||
'GetWebSocketsToken',
|
'GetWebSocketsToken',
|
||||||
{},
|
{},
|
||||||
|
|
@ -280,6 +290,8 @@ class Client:
|
||||||
# resp token for ws init
|
# resp token for ws init
|
||||||
token: str = resp['result']['token']
|
token: str = resp['result']['token']
|
||||||
assert token
|
assert token
|
||||||
|
|
||||||
|
self._ws_token: str = token
|
||||||
return token
|
return token
|
||||||
|
|
||||||
async def get_assets(
|
async def get_assets(
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ async def handle_order_requests(
|
||||||
ws: NoBsWs,
|
ws: NoBsWs,
|
||||||
client: api.Client,
|
client: api.Client,
|
||||||
ems_order_stream: tractor.MsgStream,
|
ems_order_stream: tractor.MsgStream,
|
||||||
token: str,
|
|
||||||
apiflows: OrderDialogs,
|
apiflows: OrderDialogs,
|
||||||
ids: bidict[str, int],
|
ids: bidict[str, int],
|
||||||
reqids2txids: dict[int, str],
|
reqids2txids: dict[int, str],
|
||||||
|
|
@ -178,7 +177,7 @@ async def handle_order_requests(
|
||||||
# https://docs.kraken.com/websockets/#message-cancelOrder
|
# https://docs.kraken.com/websockets/#message-cancelOrder
|
||||||
await ws.send_msg({
|
await ws.send_msg({
|
||||||
'event': 'cancelOrder',
|
'event': 'cancelOrder',
|
||||||
'token': token,
|
'token': await client.get_ws_token(),
|
||||||
'reqid': reqid,
|
'reqid': reqid,
|
||||||
'txid': [txid], # should be txid from submission
|
'txid': [txid], # should be txid from submission
|
||||||
})
|
})
|
||||||
|
|
@ -252,7 +251,7 @@ async def handle_order_requests(
|
||||||
# https://docs.kraken.com/websockets/#message-addOrder
|
# https://docs.kraken.com/websockets/#message-addOrder
|
||||||
req = {
|
req = {
|
||||||
'event': ep,
|
'event': ep,
|
||||||
'token': token,
|
'token': await client.get_ws_token(),
|
||||||
|
|
||||||
'reqid': reqid, # remapped-to-int uid from ems
|
'reqid': reqid, # remapped-to-int uid from ems
|
||||||
# XXX: we set these to the same value since for us
|
# XXX: we set these to the same value since for us
|
||||||
|
|
@ -296,7 +295,8 @@ async def handle_order_requests(
|
||||||
symbol=msg['symbol'],
|
symbol=msg['symbol'],
|
||||||
reason=(
|
reason=(
|
||||||
'Invalid request msg:\n{msg}'
|
'Invalid request msg:\n{msg}'
|
||||||
))
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -328,7 +328,11 @@ async def subscribe(
|
||||||
'''
|
'''
|
||||||
# more specific logic for this in kraken's sync client:
|
# more specific logic for this in kraken's sync client:
|
||||||
# https://github.com/krakenfx/kraken-wsclient-py/blob/master/kraken_wsclient_py/kraken_wsclient_py.py#L188
|
# https://github.com/krakenfx/kraken-wsclient-py/blob/master/kraken_wsclient_py/kraken_wsclient_py.py#L188
|
||||||
assert token
|
assert (
|
||||||
|
token
|
||||||
|
and
|
||||||
|
token == await client.get_ws_token()
|
||||||
|
)
|
||||||
subnames: set[str] = set()
|
subnames: set[str] = set()
|
||||||
|
|
||||||
for name, sub_opts in subs:
|
for name, sub_opts in subs:
|
||||||
|
|
@ -336,7 +340,7 @@ async def subscribe(
|
||||||
'event': 'subscribe',
|
'event': 'subscribe',
|
||||||
'subscription': {
|
'subscription': {
|
||||||
'name': name,
|
'name': name,
|
||||||
'token': token,
|
'token': await client.get_ws_token(),
|
||||||
**sub_opts,
|
**sub_opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -351,7 +355,9 @@ async def subscribe(
|
||||||
# wait on subscriptionn acks
|
# wait on subscriptionn acks
|
||||||
with trio.move_on_after(5):
|
with trio.move_on_after(5):
|
||||||
while True:
|
while True:
|
||||||
match (msg := await ws.recv_msg()):
|
msg: dict = await ws.recv_msg()
|
||||||
|
fmt_msg: str = ppfmt(msg)
|
||||||
|
match msg:
|
||||||
case {
|
case {
|
||||||
'event': 'subscriptionStatus',
|
'event': 'subscriptionStatus',
|
||||||
'status': 'subscribed',
|
'status': 'subscribed',
|
||||||
|
|
@ -382,23 +388,36 @@ async def subscribe(
|
||||||
exc = etype(
|
exc = etype(
|
||||||
f'{ev_msg}\n'
|
f'{ev_msg}\n'
|
||||||
f'\n'
|
f'\n'
|
||||||
f'{ppfmt(msg)}'
|
f'{fmt_msg}'
|
||||||
)
|
)
|
||||||
# !TODO, for `InvalidSession` we should
|
# !TODO, for `InvalidSession` we should
|
||||||
# attempt retries to resub and ensure all
|
# attempt retries to resub and ensure all
|
||||||
# sibling (task) `token` holders update
|
# sibling (task) `token` holders update
|
||||||
# their refs accoridingly!
|
# their refs accoridingly!
|
||||||
if isinstance(exc, api.InvalidSession):
|
match (etype_str, ev_msg):
|
||||||
# attempt ws-token refresh
|
case (
|
||||||
token: str = await client.get_ws_token()
|
'ESession',
|
||||||
await tractor.pause()
|
'Invalid session',
|
||||||
|
):
|
||||||
|
# attempt ws-token refresh
|
||||||
|
token: str = await client.get_ws_token(
|
||||||
|
force_renewal=True
|
||||||
|
)
|
||||||
|
await tractor.pause()
|
||||||
|
continue
|
||||||
|
|
||||||
|
case _:
|
||||||
|
log.warning(
|
||||||
|
f'Unhandled subscription-status,\n'
|
||||||
|
f'{fmt_msg}'
|
||||||
|
)
|
||||||
|
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
log.warning(
|
log.warning(
|
||||||
f'Unknown ws event rxed?\n'
|
f'Unknown ws event rxed?\n'
|
||||||
f'{ppfmt(msg)}'
|
f'{fmt_msg}'
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
@ -666,7 +685,6 @@ async def open_trade_dialog(
|
||||||
ws,
|
ws,
|
||||||
client,
|
client,
|
||||||
ems_stream,
|
ems_stream,
|
||||||
token,
|
|
||||||
apiflows,
|
apiflows,
|
||||||
ids,
|
ids,
|
||||||
reqids2txids,
|
reqids2txids,
|
||||||
|
|
@ -685,7 +703,6 @@ async def open_trade_dialog(
|
||||||
ledger=ledger,
|
ledger=ledger,
|
||||||
acctid=acctid,
|
acctid=acctid,
|
||||||
acc_name=fqan,
|
acc_name=fqan,
|
||||||
token=token,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -705,7 +722,6 @@ async def handle_order_updates(
|
||||||
# ledger_trans: dict[str, Transaction],
|
# ledger_trans: dict[str, Transaction],
|
||||||
acctid: str,
|
acctid: str,
|
||||||
acc_name: str,
|
acc_name: str,
|
||||||
token: str,
|
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
|
|
@ -1023,7 +1039,8 @@ async def handle_order_updates(
|
||||||
# https://docs.kraken.com/websockets/#message-cancelOrder
|
# https://docs.kraken.com/websockets/#message-cancelOrder
|
||||||
await ws.send_msg({
|
await ws.send_msg({
|
||||||
'event': 'cancelOrder',
|
'event': 'cancelOrder',
|
||||||
'token': token,
|
# 'token': token,
|
||||||
|
'token': await client.get_ws_token(),
|
||||||
'reqid': reqid or 0,
|
'reqid': reqid or 0,
|
||||||
'txid': [txid],
|
'txid': [txid],
|
||||||
})
|
})
|
||||||
|
|
@ -1177,7 +1194,8 @@ async def handle_order_updates(
|
||||||
f'Cancelling {reqid}@{txid} due to:\n {event}')
|
f'Cancelling {reqid}@{txid} due to:\n {event}')
|
||||||
await ws.send_msg({
|
await ws.send_msg({
|
||||||
'event': 'cancelOrder',
|
'event': 'cancelOrder',
|
||||||
'token': token,
|
# 'token': token,
|
||||||
|
'token': await client.get_ws_token(),
|
||||||
'reqid': reqid or 0,
|
'reqid': reqid or 0,
|
||||||
'txid': [txid],
|
'txid': [txid],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue