From a261c53ed744ca91124e5d6d0f5cc5d198843578 Mon Sep 17 00:00:00 2001 From: Holger Nahrstaedt <holger@nahrstaedt.de> Date: Thu, 24 May 2018 14:41:37 +0200 Subject: [PATCH] New nodes class for better node url handling Wallet * getKeysForAccount added * getOwnerKeysForAccount, getActiveKeysForAccount, getPostingKeysForAccount added beemapi * WorkingNodeMissing is raised when no working node could be found GrapheneRPC * cycle([urls]) is replaced by the nodes class Nodes * Node handling and management of url and error_counts is performed by the nodes class * sleep_and_check_retries is moved to the nodes class * Websocket, steemnodrpc were adpapted to the changes Unit tests * new tests for the nodes class * tests adapted for websocket and rpcutils --- beem/notify.py | 2 +- beem/wallet.py | 51 +++++++++++ beemapi/exceptions.py | 4 + beemapi/graphenerpc.py | 93 ++++++++++--------- beemapi/node.py | 156 ++++++++++++++++++++++++++++++++ beemapi/rpcutils.py | 28 +----- beemapi/steemnoderpc.py | 18 ++-- beemapi/websocket.py | 20 ++-- tests/beemapi/test_node.py | 56 ++++++++++++ tests/beemapi/test_rpcutils.py | 10 +- tests/beemapi/test_websocket.py | 4 +- 11 files changed, 337 insertions(+), 105 deletions(-) create mode 100644 beemapi/node.py create mode 100644 tests/beemapi/test_node.py diff --git a/beem/notify.py b/beem/notify.py index 9dbddc8d..652dc307 100644 --- a/beem/notify.py +++ b/beem/notify.py @@ -61,7 +61,7 @@ class Notify(Events): # Open the websocket self.websocket = SteemWebsocket( - urls=self.steem.rpc.urls, + urls=self.steem.rpc.nodes, user=self.steem.rpc.user, password=self.steem.rpc.password, only_block_id=only_block_id, diff --git a/beem/wallet.py b/beem/wallet.py index 42f9ed6d..cea4562c 100644 --- a/beem/wallet.py +++ b/beem/wallet.py @@ -386,6 +386,42 @@ class Wallet(object): raise MissingKeyError("No private key for {} found".format(name)) return + def getKeysForAccount(self, name, key_type): + """ Obtain a List of `key_type` Private Keys for an account from the wallet database + """ + if key_type not in ["owner", "active", "posting", "memo"]: + raise AssertionError("Wrong key type") + if key_type in Wallet.keyMap: + return Wallet.keyMap.get(key_type) + else: + if self.rpc.get_use_appbase(): + account = self.rpc.find_accounts({'accounts': [name]}, api="database")['accounts'] + else: + account = self.rpc.get_account(name) + if not account: + return + if len(account) == 0: + return + if key_type == "memo": + key = self.getPrivateKeyForPublicKey( + account[0]["memo_key"]) + if key: + return [key] + else: + keys = [] + key = None + for authority in account[0][key_type]["key_auths"]: + try: + key = self.getPrivateKeyForPublicKey(authority[0]) + if key: + keys.append(key) + except MissingKeyError: + key = None + if key is None: + raise MissingKeyError("No private key for {} found".format(name)) + return keys + return + def getOwnerKeyForAccount(self, name): """ Obtain owner Private Key for an account from the wallet database """ @@ -406,6 +442,21 @@ class Wallet(object): """ return self.getKeyForAccount(name, "posting") + def getOwnerKeysForAccount(self, name): + """ Obtain list of all owner Private Keys for an account from the wallet database + """ + return self.getKeysForAccount(name, "owner") + + def getActiveKeysForAccount(self, name): + """ Obtain list of all owner Active Keys for an account from the wallet database + """ + return self.getKeysForAccount(name, "active") + + def getPostingKeysForAccount(self, name): + """ Obtain list of all owner Posting Keys for an account from the wallet database + """ + return self.getKeysForAccount(name, "posting") + def getAccountFromPrivateKey(self, wif): """ Obtain account name from private key """ diff --git a/beemapi/exceptions.py b/beemapi/exceptions.py index 50a16c6d..58a9f215 100644 --- a/beemapi/exceptions.py +++ b/beemapi/exceptions.py @@ -94,3 +94,7 @@ class InvalidEndpointUrl(Exception): class UnnecessarySignatureDetected(Exception): pass + + +class WorkingNodeMissing(Exception): + pass diff --git a/beemapi/graphenerpc.py b/beemapi/graphenerpc.py index 9722287a..99b5574e 100644 --- a/beemapi/graphenerpc.py +++ b/beemapi/graphenerpc.py @@ -16,12 +16,13 @@ import re import time import warnings from .exceptions import ( - UnauthorizedError, RPCConnection, RPCError, RPCErrorDoRetry, NumRetriesReached, CallRetriesReached + UnauthorizedError, RPCConnection, RPCError, RPCErrorDoRetry, NumRetriesReached, CallRetriesReached, WorkingNodeMissing ) from .rpcutils import ( - is_network_appbase_ready, sleep_and_check_retries, + is_network_appbase_ready, get_api_name, get_query ) +from .node import Nodes from beemgraphenebase.version import version as beem_version from beemgraphenebase.chains import known_chains @@ -123,38 +124,39 @@ class GrapheneRPC(object): self.rpc_methods = {'offline': -1, 'ws': 0, 'jsonrpc': 1, 'wsappbase': 2, 'appbase': 3} self.current_rpc = self.rpc_methods["ws"] self._request_id = 0 - if isinstance(urls, str): - url_list = re.split(r",|;", urls) - self.n_urls = len(url_list) - self.urls = cycle(url_list) - if self.urls is None: - self.n_urls = 1 - self.urls = cycle([urls]) - elif isinstance(urls, (list, tuple, set)): - self.n_urls = len(urls) - self.urls = cycle(urls) - elif urls is not None: - self.n_urls = 1 - self.urls = cycle([urls]) - else: - self.n_urls = 0 - self.urls = None + self.timeout = kwargs.get('timeout', 60) + num_retries = kwargs.get("num_retries", -1) + num_retries_call = kwargs.get("num_retries_call", 5) + self.use_condenser = kwargs.get("use_condenser", False) + self.nodes = Nodes(urls, num_retries, num_retries_call) + if self.nodes.working_nodes_count == 0: self.current_rpc = self.rpc_methods["offline"] + self.user = user self.password = password self.ws = None self.url = None self.session = None self.rpc_queue = [] - self.timeout = kwargs.get('timeout', 60) - self.num_retries = kwargs.get("num_retries", -1) - self.use_condenser = kwargs.get("use_condenser", False) - self.error_cnt = {} - self.num_retries_call = kwargs.get("num_retries_call", 5) - self.error_cnt_call = 0 if kwargs.get("autoconnect", True): self.rpcconnect() + @property + def num_retries(self): + return self.nodes.num_retries + + @property + def num_retries_call(self): + return self.nodes.num_retries_call + + @property + def error_cnt_call(self): + return self.nodes.error_cnt_call + + @property + def error_cnt(self): + return self.nodes.error_cnt + def get_request_id(self): """Get request id.""" self._request_id += 1 @@ -179,14 +181,12 @@ class GrapheneRPC(object): def rpcconnect(self, next_url=True): """Connect to next url in a loop.""" - if self.urls is None: + if self.nodes.working_nodes_count == 0: return while True: if next_url: - self.url = next(self.urls) - self.error_cnt_call = 0 - if self.url not in self.error_cnt: - self.error_cnt[self.url] = 0 + self.url = next(self.nodes) + self.nodes.reset_error_cnt_call() log.debug("Trying to connect to node %s" % self.url) if self.url[:3] == "wss": self.ws = create_ws_instance(use_ssl=True) @@ -226,9 +226,9 @@ class GrapheneRPC(object): except KeyboardInterrupt: raise except Exception as e: - self.error_cnt[self.url] += 1 - do_sleep = not next_url or (next_url and self.n_urls == 1) - sleep_and_check_retries(self.num_retries, self.error_cnt[self.url], self.url, str(e), sleep=do_sleep) + self.nodes.increase_error_cnt() + do_sleep = not next_url or (next_url and self.nodes.working_nodes_count == 1) + self.nodes.sleep_and_check_retries(str(e), sleep=do_sleep) next_url = True def rpclogin(self, user, password): @@ -322,11 +322,13 @@ class GrapheneRPC(object): :raises RPCError: if the server returns an error """ log.debug(json.dumps(payload)) + if self.nodes.working_nodes_count == 0: + raise WorkingNodeMissing if self.url is None: self.rpcconnect() reply = {} while True: - self.error_cnt_call += 1 + self.nodes.increase_error_cnt_call() try: if self.current_rpc == 0 or self.current_rpc == 2: reply = self.ws_send(json.dumps(payload, ensure_ascii=False).encode('utf8')) @@ -334,10 +336,10 @@ class GrapheneRPC(object): reply = self.request_send(json.dumps(payload, ensure_ascii=False).encode('utf8')) if not bool(reply): try: - sleep_and_check_retries(self.num_retries_call, self.error_cnt_call, self.url, "Empty Reply", call_retry=True) + self.nodes.sleep_and_check_retries("Empty Reply", call_retry=True) except CallRetriesReached: - self.error_cnt[self.url] += 1 - sleep_and_check_retries(self.num_retries, self.error_cnt[self.url], self.url, "Empty Reply", sleep=False, call_retry=False) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries("Empty Reply", sleep=False, call_retry=False) self.rpcconnect() else: break @@ -347,12 +349,12 @@ class GrapheneRPC(object): # self.error_cnt[self.url] += 1 self.rpcconnect(next_url=False) except ConnectionError as e: - self.error_cnt[self.url] += 1 - sleep_and_check_retries(self.num_retries, self.error_cnt[self.url], self.url, str(e), sleep=False, call_retry=False) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False) self.rpcconnect() except Exception as e: - self.error_cnt[self.url] += 1 - sleep_and_check_retries(self.num_retries, self.error_cnt[self.url], self.url, str(e), sleep=False, call_retry=False) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False) self.rpcconnect() ret = {} @@ -381,15 +383,15 @@ class GrapheneRPC(object): ret_list.append(r["result"]) else: ret_list.append(r) - self.error_cnt_call = 0 + self.nodes.reset_error_cnt_call() return ret_list elif isinstance(ret, dict) and "result" in ret: - self.error_cnt_call = 0 + self.nodes.reset_error_cnt_call() return ret["result"] elif isinstance(ret, int): raise RPCError("Client returned invalid format. Expected JSON! Output: %s" % (str(ret))) else: - self.error_cnt_call = 0 + self.nodes.reset_error_cnt_call() return ret return ret @@ -404,16 +406,19 @@ class GrapheneRPC(object): api_name = "condenser_api" # let's be able to define the num_retries per query - self.num_retries_call = kwargs.get("num_retries_call", self.num_retries_call) + stored_num_retries_call = self.nodes.num_retries_call + self.nodes.num_retries_call = kwargs.get("num_retries_call", stored_num_retries_call) add_to_queue = kwargs.get("add_to_queue", False) query = get_query(self.is_appbase_ready() and not self.use_condenser, self.get_request_id(), api_name, name, args) if add_to_queue: self.rpc_queue.append(query) + self.nodes.num_retries_call = stored_num_retries_call return None elif len(self.rpc_queue) > 0: self.rpc_queue.append(query) query = self.rpc_queue self.rpc_queue = [] r = self.rpcexec(query) + self.nodes.num_retries_call = stored_num_retries_call return r return method diff --git a/beemapi/node.py b/beemapi/node.py new file mode 100644 index 00000000..ad64638e --- /dev/null +++ b/beemapi/node.py @@ -0,0 +1,156 @@ +# This Python file uses the following encoding: utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from builtins import str +import json +import re +import time +import logging +from .exceptions import ( + UnauthorizedError, RPCConnection, RPCError, NumRetriesReached, CallRetriesReached +) +log = logging.getLogger(__name__) + + +class Node(object): + def __init__( + self, + url + ): + self.url = url + self.error_cnt = 0 + self.error_cnt_call = 0 + + def __repr__(self): + return self.url + + +class Nodes(list): + """Stores Node URLs and error counts""" + def __init__(self, urls, num_retries, num_retries_call): + if isinstance(urls, str): + url_list = re.split(r",|;", urls) + if url_list is None: + url_list = [urls] + elif isinstance(urls, Nodes): + url_list = [urls[i].url for i in range(len(urls))] + elif isinstance(urls, (list, tuple, set)): + url_list = urls + elif urls is not None: + url_list = [urls] + else: + url_list = [] + super(Nodes, self).__init__([Node(x) for x in url_list]) + self.num_retries = num_retries + self.num_retries_call = num_retries_call + self.current_node_index = -1 + + def __iter__(self): + return self + + def __next__(self): + next_node_count = 0 + while next_node_count == 0 and (self.num_retries < 0 or self.node.error_cnt < self.num_retries): + self.current_node_index += 1 + if self.current_node_index >= self.working_nodes_count: + self.current_node_index = 0 + next_node_count += 1 + if next_node_count > self.working_nodes_count + 1: + raise StopIteration + + return self.url + + def __repr__(self): + nodes_list = [] + for i in range(len(self)): + if self.num_retries < 0 or self[i].error_cnt <= self.num_retries: + nodes_list.append(self[i].url) + return str(nodes_list) + + @property + def working_nodes_count(self): + n = 0 + for i in range(len(self)): + if self.num_retries < 0 or self[i].error_cnt <= self.num_retries: + n += 1 + return n + + @property + def url(self): + if self.node is None: + return '' + return self.node.url + + @property + def node(self): + if self.current_node_index < 0: + return self[0] + return self[self.current_node_index] + + @property + def error_cnt(self): + if self.node is None: + return 0 + return self.node.error_cnt + + @property + def error_cnt_call(self): + if self.node is None: + return 0 + return self.node.error_cnt_call + + @property + def num_retries_call_reached(self): + return self.error_cnt_call >= self.num_retries_call + + def increase_error_cnt(self): + """Increase node error count for current node""" + if self.node is not None: + self.node.error_cnt += 1 + + def increase_error_cnt_call(self): + """Increase call error count for current node""" + if self.node is not None: + self.node.error_cnt_call += 1 + + def reset_error_cnt_call(self): + """Set call error count for current node to zero""" + if self.node is not None: + self.node.error_cnt_call = 0 + + def reset_error_cnt(self): + """Set node error count for current node to zero""" + if self.node is not None: + self.node.error_cnt = 0 + + def sleep_and_check_retries(self, errorMsg=None, sleep=True, call_retry=False, showMsg=True): + """Sleep and check if num_retries is reached""" + if errorMsg: + log.warning("Error: {}".format(errorMsg)) + if call_retry: + cnt = self.error_cnt_call + if (self.num_retries_call >= 0 and self.error_cnt_call > self.num_retries_call): + raise CallRetriesReached() + else: + cnt = self.error_cnt + if (self.num_retries >= 0 and self.error_cnt > self.num_retries): + raise NumRetriesReached() + + if showMsg: + if call_retry: + log.warning("Retry RPC Call on node: %s (%d/%d) \n" % (self.url, cnt, self.num_retries_call)) + else: + log.warning("Lost connection or internal error on node: %s (%d/%d) \n" % (self.url, cnt, self.num_retries)) + if not sleep: + return + if cnt < 1: + sleeptime = 0 + elif cnt < 10: + sleeptime = (cnt - 1) * 1.5 + 0.5 + else: + sleeptime = 10 + if sleeptime: + log.warning("Retrying in %d seconds\n" % sleeptime) + time.sleep(sleeptime) diff --git a/beemapi/rpcutils.py b/beemapi/rpcutils.py index 65575cab..cc0dde51 100644 --- a/beemapi/rpcutils.py +++ b/beemapi/rpcutils.py @@ -9,6 +9,7 @@ import logging from .exceptions import ( UnauthorizedError, RPCConnection, RPCError, NumRetriesReached, CallRetriesReached ) +from .node import Nodes log = logging.getLogger(__name__) @@ -81,30 +82,3 @@ def get_api_name(appbase, *args, **kwargs): else: api_name = "condenser_api" return api_name - - -def sleep_and_check_retries(num_retries, cnt, url, errorMsg=None, sleep=True, call_retry=False, showMsg=True): - """Sleep and check if num_retries is reached""" - if errorMsg: - log.warning("Error: {}".format(errorMsg)) - if (num_retries >= 0 and cnt > num_retries): - if not call_retry: - raise NumRetriesReached() - else: - raise CallRetriesReached() - if showMsg: - if call_retry: - log.warning("Retry RPC Call on node: %s (%d/%d) \n" % (url, cnt, num_retries)) - else: - log.warning("Lost connection or internal error on node: %s (%d/%d) \n" % (url, cnt, num_retries)) - if not sleep: - return - if cnt < 1: - sleeptime = 0 - elif cnt < 10: - sleeptime = (cnt - 1) * 1.5 + 0.5 - else: - sleeptime = 10 - if sleeptime: - log.warning("Retrying in %d seconds\n" % sleeptime) - time.sleep(sleeptime) diff --git a/beemapi/steemnoderpc.py b/beemapi/steemnoderpc.py index 26f786d2..575cdae5 100644 --- a/beemapi/steemnoderpc.py +++ b/beemapi/steemnoderpc.py @@ -6,7 +6,6 @@ from builtins import bytes, int, str import re import sys from .graphenerpc import GrapheneRPC -from .rpcutils import sleep_and_check_retries from beemgraphenebase.chains import known_chains from . import exceptions import logging @@ -72,7 +71,7 @@ class SteemNodeRPC(GrapheneRPC): except exceptions.RPCErrorDoRetry as e: msg = exceptions.decodeRPCErrorMsg(e).strip() try: - sleep_and_check_retries(self.num_retries_call, self.error_cnt_call, self.url, str(msg), call_retry=True) + self.nodes.sleep_and_check_retries(str(msg), call_retry=True) doRetry = True except exceptions.CallRetriesReached: if self.n_urls > 1: @@ -92,12 +91,11 @@ class SteemNodeRPC(GrapheneRPC): raise exceptions.CallRetriesReached except Exception as e: raise e - if self.error_cnt_call >= self.num_retries_call: - maxRetryCountReached = True + maxRetryCountReached = self.nodes.num_retries_call_reached def _retry_on_next_node(self, error_msg): - self.error_cnt[self.url] += 1 - sleep_and_check_retries(self.num_retries, self.error_cnt[self.url], self.url, error_msg, sleep=False, call_retry=False) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries(error_msg, sleep=False, call_retry=False) self.next() def _check_error_message(self, e, cnt): @@ -122,16 +120,16 @@ class SteemNodeRPC(GrapheneRPC): elif re.search("WinError", msg): raise exceptions.RPCError(msg) elif re.search("Unable to acquire database lock", msg): - sleep_and_check_retries(self.num_retries_call, cnt, self.url, str(msg), call_retry=True) + self.nodes.sleep_and_check_retries(str(msg), call_retry=True) doRetry = True elif re.search("Internal Error", msg) or re.search("Unknown exception", msg): - sleep_and_check_retries(self.num_retries_call, cnt, self.url, str(msg), call_retry=True) + self.nodes.sleep_and_check_retries(str(msg), call_retry=True) doRetry = True elif re.search("!check_max_block_age", str(e)): if self.n_urls == 1: raise exceptions.UnhandledRPCError(msg) - self.error_cnt[self.url] += 1 - sleep_and_check_retries(self.num_retries, self.error_cnt[self.url], self.url, str(msg), sleep=False) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries(str(msg), sleep=False) self.next() doRetry = True elif re.search("out_of_rangeEEEE: unknown key", msg) or re.search("unknown key:unknown key", msg): diff --git a/beemapi/websocket.py b/beemapi/websocket.py index 7ad5558c..998e35cf 100644 --- a/beemapi/websocket.py +++ b/beemapi/websocket.py @@ -14,10 +14,11 @@ import websocket from itertools import cycle from threading import Thread from beemapi.rpcutils import ( - is_network_appbase_ready, sleep_and_check_retries, + is_network_appbase_ready, get_api_name, get_query, UnauthorizedError, RPCConnection, RPCError, NumRetriesReached ) +from beemapi.node import Nodes from events import Events log = logging.getLogger(__name__) @@ -75,12 +76,7 @@ class SteemWebsocket(Events): self.keep_alive = keep_alive self.run_event = threading.Event() self.only_block_id = only_block_id - if isinstance(urls, cycle): - self.urls = urls - elif isinstance(urls, list): - self.urls = cycle(urls) - else: - self.urls = cycle([urls]) + self.nodes = Nodes(urls, num_retries, 5) # Instanciate Events Events.__init__(self) @@ -232,10 +228,8 @@ class SteemWebsocket(Events): It will execute callbacks as defined and try to stay connected with the provided APIs """ - cnt = 0 while not self.run_event.is_set(): - cnt += 1 - self.url = next(self.urls) + self.url = next(self.nodes) log.debug("Trying to connect to node %s" % self.url) try: # websocket.enableTrace(True) @@ -248,9 +242,11 @@ class SteemWebsocket(Events): ) self.ws.run_forever() except websocket.WebSocketException as exc: - sleep_and_check_retries(self.num_retries, cnt, self.url) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries() except websocket.WebSocketTimeoutException as exc: - sleep_and_check_retries(self.num_retries, cnt, self.url) + self.nodes.increase_error_cnt() + self.nodes.sleep_and_check_retries() except KeyboardInterrupt: self.ws.keep_running = False raise diff --git a/tests/beemapi/test_node.py b/tests/beemapi/test_node.py new file mode 100644 index 00000000..fd69de93 --- /dev/null +++ b/tests/beemapi/test_node.py @@ -0,0 +1,56 @@ +# This Python file uses the following encoding: utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import pytest +import unittest +from beemapi.node import Nodes +from beemapi.rpcutils import ( + is_network_appbase_ready, + get_api_name, get_query, UnauthorizedError, + RPCConnection, RPCError, NumRetriesReached +) + + +class Testcases(unittest.TestCase): + def test_sleep_and_check_retries(self): + nodes = Nodes("test", -1, 5) + nodes.sleep_and_check_retries("error") + nodes = Nodes("test", 1, 5) + nodes.increase_error_cnt() + nodes.increase_error_cnt() + with self.assertRaises( + NumRetriesReached + ): + nodes.sleep_and_check_retries() + + def test_next(self): + nodes = Nodes(["a", "b", "c"], -1, -1) + self.assertEqual(nodes.working_nodes_count, len(nodes)) + self.assertEqual(nodes.url, nodes[0].url) + next(nodes) + self.assertEqual(nodes.url, nodes[0].url) + next(nodes) + self.assertEqual(nodes.url, nodes[1].url) + next(nodes) + self.assertEqual(nodes.url, nodes[2].url) + next(nodes) + self.assertEqual(nodes.url, nodes[0].url) + + nodes = Nodes("a,b,c", 5, 5) + self.assertEqual(nodes.working_nodes_count, len(nodes)) + self.assertEqual(nodes.url, nodes[0].url) + next(nodes) + self.assertEqual(nodes.url, nodes[0].url) + next(nodes) + self.assertEqual(nodes.url, nodes[1].url) + next(nodes) + self.assertEqual(nodes.url, nodes[2].url) + next(nodes) + self.assertEqual(nodes.url, nodes[0].url) + + def test_init(self): + nodes = Nodes(["a", "b", "c"], 5, 5) + nodes2 = Nodes(nodes, 5, 5) + self.assertEqual(nodes.url, nodes2.url) diff --git a/tests/beemapi/test_rpcutils.py b/tests/beemapi/test_rpcutils.py index 5f64d59f..7c610b30 100644 --- a/tests/beemapi/test_rpcutils.py +++ b/tests/beemapi/test_rpcutils.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import pytest import unittest from beemapi.rpcutils import ( - is_network_appbase_ready, sleep_and_check_retries, + is_network_appbase_ready, get_api_name, get_query, UnauthorizedError, RPCConnection, RPCError, NumRetriesReached ) @@ -81,11 +81,3 @@ class Testcases(unittest.TestCase): self.assertEqual(query["id"], 1) self.assertTrue(isinstance(query["params"], list)) self.assertEqual(query["params"], ["test_api", "test", ["b"]]) - - def test_sleep_and_check_retries(self): - sleep_and_check_retries(-1, 0, "test", "error") - sleep_and_check_retries(-1, -1, "test") - with self.assertRaises( - NumRetriesReached - ): - sleep_and_check_retries(1, 2, "test") diff --git a/tests/beemapi/test_websocket.py b/tests/beemapi/test_websocket.py index 82a3a060..a57f8717 100644 --- a/tests/beemapi/test_websocket.py +++ b/tests/beemapi/test_websocket.py @@ -29,10 +29,10 @@ class Testcases(unittest.TestCase): stm = Steem(node=get_node_list(appbase=False)) self.ws = SteemWebsocket( - urls=stm.rpc.urls, + urls=stm.rpc.nodes, num_retries=10 ) def test_connect(self): ws = self.ws - self.assertTrue(len(next(ws.urls)) > 0) + self.assertTrue(len(next(ws.nodes)) > 0) -- GitLab