hive-nectar 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hive-nectar might be problematic. Click here for more details.

Files changed (86) hide show
  1. hive_nectar-0.0.2.dist-info/METADATA +182 -0
  2. hive_nectar-0.0.2.dist-info/RECORD +86 -0
  3. hive_nectar-0.0.2.dist-info/WHEEL +4 -0
  4. hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +32 -0
  7. nectar/account.py +4371 -0
  8. nectar/amount.py +475 -0
  9. nectar/asciichart.py +270 -0
  10. nectar/asset.py +82 -0
  11. nectar/block.py +446 -0
  12. nectar/blockchain.py +1178 -0
  13. nectar/blockchaininstance.py +2284 -0
  14. nectar/blockchainobject.py +221 -0
  15. nectar/blurt.py +563 -0
  16. nectar/cli.py +6285 -0
  17. nectar/comment.py +1217 -0
  18. nectar/community.py +513 -0
  19. nectar/constants.py +111 -0
  20. nectar/conveyor.py +309 -0
  21. nectar/discussions.py +1709 -0
  22. nectar/exceptions.py +149 -0
  23. nectar/hive.py +546 -0
  24. nectar/hivesigner.py +420 -0
  25. nectar/imageuploader.py +72 -0
  26. nectar/instance.py +129 -0
  27. nectar/market.py +1013 -0
  28. nectar/memo.py +449 -0
  29. nectar/message.py +357 -0
  30. nectar/nodelist.py +444 -0
  31. nectar/price.py +557 -0
  32. nectar/profile.py +65 -0
  33. nectar/rc.py +308 -0
  34. nectar/snapshot.py +726 -0
  35. nectar/steem.py +582 -0
  36. nectar/storage.py +53 -0
  37. nectar/transactionbuilder.py +622 -0
  38. nectar/utils.py +545 -0
  39. nectar/version.py +2 -0
  40. nectar/vote.py +557 -0
  41. nectar/wallet.py +472 -0
  42. nectar/witness.py +617 -0
  43. nectarapi/__init__.py +11 -0
  44. nectarapi/exceptions.py +123 -0
  45. nectarapi/graphenerpc.py +589 -0
  46. nectarapi/node.py +178 -0
  47. nectarapi/noderpc.py +229 -0
  48. nectarapi/rpcutils.py +97 -0
  49. nectarapi/version.py +2 -0
  50. nectarbase/__init__.py +14 -0
  51. nectarbase/ledgertransactions.py +75 -0
  52. nectarbase/memo.py +243 -0
  53. nectarbase/objects.py +429 -0
  54. nectarbase/objecttypes.py +22 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1297 -0
  57. nectarbase/signedtransactions.py +48 -0
  58. nectarbase/transactions.py +11 -0
  59. nectarbase/version.py +2 -0
  60. nectargrapheneapi/__init__.py +6 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +846 -0
  63. nectargraphenebase/aes.py +52 -0
  64. nectargraphenebase/base58.py +192 -0
  65. nectargraphenebase/bip32.py +494 -0
  66. nectargraphenebase/bip38.py +134 -0
  67. nectargraphenebase/chains.py +149 -0
  68. nectargraphenebase/dictionary.py +3 -0
  69. nectargraphenebase/ecdsasig.py +326 -0
  70. nectargraphenebase/objects.py +123 -0
  71. nectargraphenebase/objecttypes.py +6 -0
  72. nectargraphenebase/operationids.py +3 -0
  73. nectargraphenebase/operations.py +23 -0
  74. nectargraphenebase/prefix.py +11 -0
  75. nectargraphenebase/py23.py +38 -0
  76. nectargraphenebase/signedtransactions.py +201 -0
  77. nectargraphenebase/types.py +419 -0
  78. nectargraphenebase/unsignedtransactions.py +283 -0
  79. nectargraphenebase/version.py +2 -0
  80. nectarstorage/__init__.py +38 -0
  81. nectarstorage/base.py +306 -0
  82. nectarstorage/exceptions.py +16 -0
  83. nectarstorage/interfaces.py +237 -0
  84. nectarstorage/masterpassword.py +239 -0
  85. nectarstorage/ram.py +30 -0
  86. nectarstorage/sqlite.py +334 -0
@@ -0,0 +1,589 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import logging
4
+ import re
5
+ import ssl
6
+
7
+ from nectargraphenebase.chains import known_chains
8
+ from nectargraphenebase.version import version as nectar_version
9
+
10
+ from .exceptions import (
11
+ CallRetriesReached,
12
+ RPCConnection,
13
+ RPCError,
14
+ RPCErrorDoRetry,
15
+ UnauthorizedError,
16
+ WorkingNodeMissing,
17
+ )
18
+ from .node import Nodes
19
+ from .rpcutils import get_api_name, get_query, is_network_appbase_ready
20
+
21
+ WEBSOCKET_MODULE = None
22
+ if not WEBSOCKET_MODULE:
23
+ try:
24
+ import websocket
25
+ from websocket._exceptions import (
26
+ WebSocketConnectionClosedException,
27
+ WebSocketTimeoutException,
28
+ )
29
+
30
+ WEBSOCKET_MODULE = "websocket"
31
+ except ImportError:
32
+ WEBSOCKET_MODULE = None
33
+ REQUEST_MODULE = None
34
+ if not REQUEST_MODULE:
35
+ try:
36
+ import requests
37
+ from requests.adapters import HTTPAdapter
38
+ from requests.exceptions import ConnectionError
39
+ from requests.packages.urllib3.util.retry import Retry
40
+
41
+ REQUEST_MODULE = "requests"
42
+ except ImportError:
43
+ REQUEST_MODULE = None
44
+
45
+ log = logging.getLogger(__name__)
46
+
47
+
48
+ class SessionInstance(object):
49
+ """Singelton for the Session Instance"""
50
+
51
+ instance = None
52
+
53
+
54
+ def set_session_instance(instance):
55
+ """Set session instance"""
56
+ SessionInstance.instance = instance
57
+
58
+
59
+ def shared_session_instance():
60
+ """Get session instance"""
61
+ if REQUEST_MODULE is None:
62
+ raise Exception()
63
+ if not SessionInstance.instance:
64
+ SessionInstance.instance = requests.Session()
65
+ return SessionInstance.instance
66
+
67
+
68
+ def create_ws_instance(use_ssl=True, enable_multithread=True):
69
+ """Get websocket instance"""
70
+ if WEBSOCKET_MODULE is None:
71
+ raise Exception()
72
+ if use_ssl:
73
+ ssl_defaults = ssl.get_default_verify_paths()
74
+ sslopt_ca_certs = {"ca_certs": ssl_defaults.cafile}
75
+ return websocket.WebSocket(sslopt=sslopt_ca_certs, enable_multithread=enable_multithread)
76
+ else:
77
+ return websocket.WebSocket(enable_multithread=enable_multithread)
78
+
79
+
80
+ class GrapheneRPC(object):
81
+ """
82
+ This class allows to call API methods synchronously, without callbacks.
83
+
84
+ It logs warnings and errors.
85
+
86
+ :param str urls: Either a single Websocket/Http URL, or a list of URLs
87
+ :param str user: Username for Authentication
88
+ :param str password: Password for Authentication
89
+ :param int num_retries: Try x times to num_retries to a node on disconnect, -1 for indefinitely (default is 100)
90
+ :param int num_retries_call: Repeat num_retries_call times a rpc call on node error (default is 5)
91
+ :param int timeout: Timeout setting for https nodes (default is 60)
92
+ :param bool autoconnect: When set to false, connection is performed on the first rpc call (default is True)
93
+ :param bool use_condenser: Use the old condenser_api rpc protocol on nodes with version
94
+ 0.19.4 or higher. The settings has no effect on nodes with version of 0.19.3 or lower.
95
+ :param bool use_tor: When set to true, 'socks5h://localhost:9050' is set as proxy
96
+ :param dict custom_chains: custom chain which should be added to the known chains
97
+
98
+ Available APIs:
99
+
100
+ * database
101
+ * network_node
102
+ * network_broadcast
103
+
104
+ Usage:
105
+
106
+ .. code-block:: python
107
+
108
+ from nectarapi.graphenerpc import GrapheneRPC
109
+ ws = GrapheneRPC("wss://steemd.pevo.science","","")
110
+ print(ws.get_account_count())
111
+
112
+ ws = GrapheneRPC("https://api.steemit.com","","")
113
+ print(ws.get_account_count())
114
+
115
+ .. note:: This class allows to call methods available via
116
+ websocket. If you want to use the notification
117
+ subsystem, please use ``GrapheneWebsocket`` instead.
118
+
119
+ """
120
+
121
+ def __init__(self, urls, user=None, password=None, **kwargs):
122
+ """Init."""
123
+ self.rpc_methods = {"offline": -1, "ws": 0, "jsonrpc": 1, "wsappbase": 2, "appbase": 3}
124
+ self.current_rpc = self.rpc_methods["ws"]
125
+ self._request_id = 0
126
+ self.timeout = kwargs.get("timeout", 60)
127
+ num_retries = kwargs.get("num_retries", 100)
128
+ num_retries_call = kwargs.get("num_retries_call", 5)
129
+ self.use_condenser = kwargs.get("use_condenser", False)
130
+ self.use_tor = kwargs.get("use_tor", False)
131
+ self.disable_chain_detection = kwargs.get("disable_chain_detection", False)
132
+ self.known_chains = known_chains
133
+ custom_chain = kwargs.get("custom_chains", {})
134
+ if len(custom_chain) > 0:
135
+ for c in custom_chain:
136
+ if c not in self.known_chains:
137
+ self.known_chains[c] = custom_chain[c]
138
+
139
+ self.nodes = Nodes(urls, num_retries, num_retries_call)
140
+ if self.nodes.working_nodes_count == 0:
141
+ self.current_rpc = self.rpc_methods["offline"]
142
+
143
+ self.user = user
144
+ self.password = password
145
+ self.ws = None
146
+ self.url = None
147
+ self.session = None
148
+ self.rpc_queue = []
149
+ if kwargs.get("autoconnect", True):
150
+ self.rpcconnect()
151
+
152
+ @property
153
+ def num_retries(self):
154
+ return self.nodes.num_retries
155
+
156
+ @property
157
+ def num_retries_call(self):
158
+ return self.nodes.num_retries_call
159
+
160
+ @property
161
+ def error_cnt_call(self):
162
+ return self.nodes.error_cnt_call
163
+
164
+ @property
165
+ def error_cnt(self):
166
+ return self.nodes.error_cnt
167
+
168
+ def get_request_id(self):
169
+ """Get request id."""
170
+ self._request_id += 1
171
+ return self._request_id
172
+
173
+ def next(self):
174
+ """Switches to the next node url"""
175
+ if self.ws:
176
+ try:
177
+ self.rpcclose()
178
+ except Exception as e:
179
+ log.warning(str(e))
180
+ self.rpcconnect()
181
+
182
+ def is_appbase_ready(self):
183
+ """Check if node is appbase ready"""
184
+ return self.current_rpc in [self.rpc_methods["wsappbase"], self.rpc_methods["appbase"]]
185
+
186
+ def get_use_appbase(self):
187
+ """Returns True if appbase ready and appbase calls are set"""
188
+ return not self.use_condenser and self.is_appbase_ready()
189
+
190
+ def rpcconnect(self, next_url=True):
191
+ """Connect to next url in a loop."""
192
+ if self.nodes.working_nodes_count == 0:
193
+ return
194
+ while True:
195
+ if next_url:
196
+ self.url = next(self.nodes)
197
+ self.nodes.reset_error_cnt_call()
198
+ log.debug("Trying to connect to node %s" % self.url)
199
+ if self.url[:3] == "wss":
200
+ self.ws = create_ws_instance(use_ssl=True)
201
+ self.ws.settimeout(self.timeout)
202
+ self.current_rpc = self.rpc_methods["wsappbase"]
203
+ elif self.url[:2] == "ws":
204
+ self.ws = create_ws_instance(use_ssl=False)
205
+ self.ws.settimeout(self.timeout)
206
+ self.current_rpc = self.rpc_methods["wsappbase"]
207
+ else:
208
+ self.ws = None
209
+ self.session = shared_session_instance()
210
+ if self.use_tor:
211
+ self.session.proxies = {}
212
+ self.session.proxies["http"] = "socks5h://localhost:9050"
213
+ self.session.proxies["https"] = "socks5h://localhost:9050"
214
+ self.current_rpc = self.rpc_methods["appbase"]
215
+ self.headers = {
216
+ "User-Agent": "nectar v%s" % (nectar_version),
217
+ "content-type": "application/json; charset=utf-8",
218
+ }
219
+ try:
220
+ if self.ws:
221
+ self.ws.connect(self.url)
222
+ self.rpclogin(self.user, self.password)
223
+ if self.disable_chain_detection:
224
+ # Set to appbase rpc format
225
+ if self.current_rpc == self.rpc_methods["ws"]:
226
+ self.current_rpc = self.rpc_methods["wsappbase"]
227
+ else:
228
+ self.current_rpc = self.rpc_methods["appbase"]
229
+ break
230
+ try:
231
+ props = None
232
+ if not self.use_condenser:
233
+ props = self.get_config(api="database")
234
+ else:
235
+ props = self.get_config()
236
+ except Exception as e:
237
+ if re.search("Bad Cast:Invalid cast from type", str(e)):
238
+ # retry with not appbase
239
+ if self.current_rpc == self.rpc_methods["wsappbase"]:
240
+ self.current_rpc = self.rpc_methods["ws"]
241
+ else:
242
+ self.current_rpc = self.rpc_methods["appbase"]
243
+ props = self.get_config(api="database")
244
+ if props is None:
245
+ raise RPCError("Could not receive answer for get_config")
246
+ if is_network_appbase_ready(props):
247
+ if self.ws:
248
+ self.current_rpc = self.rpc_methods["wsappbase"]
249
+ else:
250
+ self.current_rpc = self.rpc_methods["appbase"]
251
+ break
252
+ except KeyboardInterrupt:
253
+ raise
254
+ except Exception as e:
255
+ self.nodes.increase_error_cnt()
256
+ do_sleep = not next_url or (next_url and self.nodes.working_nodes_count == 1)
257
+ self.nodes.sleep_and_check_retries(str(e), sleep=do_sleep)
258
+ next_url = True
259
+
260
+ def rpclogin(self, user, password):
261
+ """Login into Websocket"""
262
+ if self.ws and self.current_rpc == self.rpc_methods["ws"] and user and password:
263
+ self.login(user, password, api="login_api")
264
+
265
+ def rpcclose(self):
266
+ """Close Websocket"""
267
+ if self.ws is None:
268
+ return
269
+ # if self.ws.connected:
270
+ self.ws.close()
271
+
272
+ def request_send(self, payload):
273
+ if self.user is not None and self.password is not None:
274
+ response = self.session.post(
275
+ self.url,
276
+ data=payload,
277
+ headers=self.headers,
278
+ timeout=self.timeout,
279
+ auth=(self.user, self.password),
280
+ )
281
+ else:
282
+ response = self.session.post(
283
+ self.url, data=payload, headers=self.headers, timeout=self.timeout
284
+ )
285
+ if response.status_code == 401:
286
+ raise UnauthorizedError
287
+ return response
288
+
289
+ def ws_send(self, payload):
290
+ if self.ws is None:
291
+ raise RPCConnection("No websocket available!")
292
+ self.ws.send(payload)
293
+ reply = self.ws.recv()
294
+ return reply
295
+
296
+ def version_string_to_int(self, network_version):
297
+ version_list = network_version.split(".")
298
+ return int(int(version_list[0]) * 1e8 + int(version_list[1]) * 1e4 + int(version_list[2]))
299
+
300
+ def get_network(self, props=None):
301
+ """Identify the connected network. This call returns a
302
+ dictionary with keys chain_id, core_symbol and prefix
303
+ """
304
+ if props is None:
305
+ props = self.get_config(api="database")
306
+ chain_id = None
307
+ network_version = None
308
+ blockchain_name = None
309
+ chain_config = None
310
+ prefix = None
311
+ symbols = []
312
+ chain_assets = []
313
+
314
+ prefix_count = {}
315
+ for key in props:
316
+ if key.split("_")[0] in prefix_count:
317
+ prefix_count[key.split("_")[0]] += 1
318
+ else:
319
+ prefix_count[key.split("_")[0]] = 1
320
+ if len(prefix_count) > 0:
321
+ sorted_prefix_count = sorted(prefix_count.items(), key=lambda x: x[1], reverse=True)
322
+ if sorted_prefix_count[0][1] > 1:
323
+ blockchain_name = sorted_prefix_count[0][0]
324
+ if blockchain_name is None and "HIVE_CHAIN_ID" in props and "STEEM_CHAIN_ID" in props:
325
+ del props["STEEM_CHAIN_ID"]
326
+
327
+ for key in props:
328
+ if key[-8:] == "CHAIN_ID" and blockchain_name is None:
329
+ chain_id = props[key]
330
+ blockchain_name = key.split("_")[0]
331
+ elif key[-8:] == "CHAIN_ID" and key.split("_")[0] == blockchain_name:
332
+ chain_id = props[key]
333
+ elif key[-13:] == "CHAIN_VERSION" and blockchain_name is None:
334
+ network_version = props[key]
335
+ elif key[-13:] == "CHAIN_VERSION" and key.split("_")[0] == blockchain_name:
336
+ network_version = props[key]
337
+ elif key[-14:] == "ADDRESS_PREFIX" and blockchain_name is None:
338
+ prefix = props[key]
339
+ elif key[-14:] == "ADDRESS_PREFIX" and key.split("_")[0] == blockchain_name:
340
+ prefix = props[key]
341
+ elif key[-6:] == "SYMBOL":
342
+ value = {}
343
+ value["asset"] = props[key]["nai"]
344
+ value["precision"] = props[key]["decimals"]
345
+ if (
346
+ "IS_TEST_NET" in props
347
+ and props["IS_TEST_NET"]
348
+ and "nai" in props[key]
349
+ and props[key]["nai"] == "@@000000013"
350
+ ):
351
+ value["symbol"] = "TBD"
352
+ elif (
353
+ "IS_TEST_NET" in props
354
+ and props["IS_TEST_NET"]
355
+ and "nai" in props[key]
356
+ and props[key]["nai"] == "@@000000021"
357
+ ):
358
+ value["symbol"] = "TESTS"
359
+ else:
360
+ value["symbol"] = key[:-7]
361
+ value["id"] = -1
362
+ symbols.append(value)
363
+ symbol_id = 0
364
+ if len(symbols) == 2:
365
+ symbol_id = 1
366
+ for s in sorted(symbols, key=lambda self: self["asset"], reverse=False):
367
+ s["id"] = symbol_id
368
+ symbol_id += 1
369
+ chain_assets.append(s)
370
+ if (
371
+ chain_id is not None
372
+ and network_version is not None
373
+ and len(chain_assets) > 0
374
+ and prefix is not None
375
+ ):
376
+ chain_config = {
377
+ "prefix": prefix,
378
+ "chain_id": chain_id,
379
+ "min_version": network_version,
380
+ "chain_assets": chain_assets,
381
+ }
382
+
383
+ if chain_id is None:
384
+ raise RPCError("Connecting to unknown network!")
385
+ highest_version_chain = None
386
+ for k, v in list(self.known_chains.items()):
387
+ if (
388
+ blockchain_name is not None
389
+ and blockchain_name not in k
390
+ and blockchain_name != "STEEMIT"
391
+ and blockchain_name != "CHAIN"
392
+ ):
393
+ continue
394
+ if v["chain_id"] == chain_id and self.version_string_to_int(
395
+ v["min_version"]
396
+ ) <= self.version_string_to_int(network_version):
397
+ if highest_version_chain is None:
398
+ highest_version_chain = v
399
+ elif self.version_string_to_int(v["min_version"]) > self.version_string_to_int(
400
+ highest_version_chain["min_version"]
401
+ ):
402
+ highest_version_chain = v
403
+ if highest_version_chain is None and chain_config is not None:
404
+ return chain_config
405
+ elif highest_version_chain is None:
406
+ raise RPCError("Connecting to unknown network!")
407
+ else:
408
+ return highest_version_chain
409
+
410
+ def _check_for_server_error(self, reply):
411
+ """Checks for server error message in reply"""
412
+ if re.search("Internal Server Error", reply) or re.search("500", reply):
413
+ raise RPCErrorDoRetry("Internal Server Error")
414
+ elif re.search("Not Implemented", reply) or re.search("501", reply):
415
+ raise RPCError("Not Implemented")
416
+ elif re.search("Bad Gateway", reply) or re.search("502", reply):
417
+ raise RPCErrorDoRetry("Bad Gateway")
418
+ elif re.search("Too Many Requests", reply) or re.search("429", reply):
419
+ raise RPCErrorDoRetry("Too Many Requests")
420
+ elif (
421
+ re.search("Service Temporarily Unavailable", reply)
422
+ or re.search("Service Unavailable", reply)
423
+ or re.search("503", reply)
424
+ ):
425
+ raise RPCErrorDoRetry("Service Temporarily Unavailable")
426
+ elif (
427
+ re.search("Gateway Time-out", reply)
428
+ or re.search("Gateway Timeout", reply)
429
+ or re.search("504", reply)
430
+ ):
431
+ raise RPCErrorDoRetry("Gateway Time-out")
432
+ elif re.search("HTTP Version not supported", reply) or re.search("505", reply):
433
+ raise RPCError("HTTP Version not supported")
434
+ elif re.search("Variant Also Negotiates", reply) or re.search("506", reply):
435
+ raise RPCError("Variant Also Negotiates")
436
+ elif re.search("Insufficient Storage", reply) or re.search("507", reply):
437
+ raise RPCError("Insufficient Storage")
438
+ elif re.search("Loop Detected", reply) or re.search("508", reply):
439
+ raise RPCError("Loop Detected")
440
+ elif re.search("Bandwidth Limit Exceeded", reply) or re.search("509", reply):
441
+ raise RPCError("Bandwidth Limit Exceeded")
442
+ elif re.search("Not Extended", reply) or re.search("510", reply):
443
+ raise RPCError("Not Extended")
444
+ elif re.search("Network Authentication Required", reply) or re.search("511", reply):
445
+ raise RPCError("Network Authentication Required")
446
+ else:
447
+ raise RPCError("Client returned invalid format. Expected JSON!")
448
+
449
+ def rpcexec(self, payload):
450
+ """
451
+ Execute a call by sending the payload.
452
+
453
+ :param json payload: Payload data
454
+ :raises ValueError: if the server does not respond in proper JSON format
455
+ :raises RPCError: if the server returns an error
456
+ """
457
+ log.debug(json.dumps(payload))
458
+ if self.nodes.working_nodes_count == 0:
459
+ raise WorkingNodeMissing
460
+ if self.url is None:
461
+ raise RPCConnection("RPC is not connected!")
462
+ reply = {}
463
+ response = None
464
+ while True:
465
+ self.nodes.increase_error_cnt_call()
466
+ try:
467
+ if (
468
+ self.current_rpc == self.rpc_methods["ws"]
469
+ or self.current_rpc == self.rpc_methods["wsappbase"]
470
+ ):
471
+ reply = self.ws_send(json.dumps(payload, ensure_ascii=False).encode("utf8"))
472
+ else:
473
+ response = self.request_send(
474
+ json.dumps(payload, ensure_ascii=False).encode("utf8")
475
+ )
476
+ reply = response.text
477
+ if not bool(reply):
478
+ try:
479
+ self.nodes.sleep_and_check_retries("Empty Reply", call_retry=True)
480
+ except CallRetriesReached:
481
+ self.nodes.increase_error_cnt()
482
+ self.nodes.sleep_and_check_retries(
483
+ "Empty Reply", sleep=False, call_retry=False
484
+ )
485
+ self.rpcconnect()
486
+ else:
487
+ break
488
+ except KeyboardInterrupt:
489
+ raise
490
+ except WebSocketConnectionClosedException as e:
491
+ if self.nodes.num_retries_call_reached:
492
+ self.nodes.increase_error_cnt()
493
+ self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
494
+ self.rpcconnect()
495
+ else:
496
+ # self.nodes.sleep_and_check_retries(str(e), sleep=True, call_retry=True)
497
+ self.rpcconnect(next_url=False)
498
+ except ConnectionError as e:
499
+ self.nodes.increase_error_cnt()
500
+ self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
501
+ self.rpcconnect()
502
+ except WebSocketTimeoutException as e:
503
+ self.nodes.increase_error_cnt()
504
+ self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
505
+ self.rpcconnect()
506
+ except Exception as e:
507
+ self.nodes.increase_error_cnt()
508
+ self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
509
+ self.rpcconnect()
510
+
511
+ ret = {}
512
+ try:
513
+ if response is None:
514
+ ret = json.loads(reply, strict=False, encoding="utf-8")
515
+ else:
516
+ ret = response.json()
517
+ except ValueError:
518
+ self._check_for_server_error(reply)
519
+
520
+ log.debug(json.dumps(reply))
521
+
522
+ if isinstance(ret, dict) and "error" in ret:
523
+ if "detail" in ret["error"]:
524
+ raise RPCError(ret["error"]["detail"])
525
+ else:
526
+ raise RPCError(ret["error"]["message"])
527
+ else:
528
+ if isinstance(ret, list):
529
+ ret_list = []
530
+ for r in ret:
531
+ if isinstance(r, dict) and "error" in r:
532
+ if "detail" in r["error"]:
533
+ raise RPCError(r["error"]["detail"])
534
+ else:
535
+ raise RPCError(r["error"]["message"])
536
+ elif isinstance(r, dict) and "result" in r:
537
+ ret_list.append(r["result"])
538
+ else:
539
+ ret_list.append(r)
540
+ self.nodes.reset_error_cnt_call()
541
+ return ret_list
542
+ elif isinstance(ret, dict) and "result" in ret:
543
+ self.nodes.reset_error_cnt_call()
544
+ return ret["result"]
545
+ elif isinstance(ret, int):
546
+ raise RPCError(
547
+ "Client returned invalid format. Expected JSON! Output: %s" % (str(ret))
548
+ )
549
+ else:
550
+ self.nodes.reset_error_cnt_call()
551
+ return ret
552
+ return ret
553
+
554
+ # End of Deprecated methods
555
+ ####################################################################
556
+ def __getattr__(self, name):
557
+ """Map all methods to RPC calls and pass through the arguments."""
558
+
559
+ def method(*args, **kwargs):
560
+ api_name = get_api_name(self.is_appbase_ready(), *args, **kwargs)
561
+ if self.is_appbase_ready() and self.use_condenser and api_name != "bridge":
562
+ api_name = "condenser_api"
563
+ if api_name is None:
564
+ api_name = "database_api"
565
+
566
+ # let's be able to define the num_retries per query
567
+ stored_num_retries_call = self.nodes.num_retries_call
568
+ self.nodes.num_retries_call = kwargs.get("num_retries_call", stored_num_retries_call)
569
+ add_to_queue = kwargs.get("add_to_queue", False)
570
+ query = get_query(
571
+ self.is_appbase_ready() and not self.use_condenser or api_name == "bridge",
572
+ self.get_request_id(),
573
+ api_name,
574
+ name,
575
+ args,
576
+ )
577
+ if add_to_queue:
578
+ self.rpc_queue.append(query)
579
+ self.nodes.num_retries_call = stored_num_retries_call
580
+ return None
581
+ elif len(self.rpc_queue) > 0:
582
+ self.rpc_queue.append(query)
583
+ query = self.rpc_queue
584
+ self.rpc_queue = []
585
+ r = self.rpcexec(query)
586
+ self.nodes.num_retries_call = stored_num_retries_call
587
+ return r
588
+
589
+ return method