py-near 1.1.39__py3-none-any.whl → 1.1.40__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.
py_near/account.py CHANGED
@@ -93,6 +93,13 @@ class Account(object):
93
93
  self._lock_by_pk = collections.defaultdict(asyncio.Lock)
94
94
  self.chain_id = (await self._provider.get_status())["chain_id"]
95
95
 
96
+ async def shutdown(self):
97
+ """
98
+ Close async object
99
+ :return:
100
+ """
101
+ await self._provider.shutdown()
102
+
96
103
  async def _update_last_block_hash(self):
97
104
  """
98
105
  Update last block hash& If it's older than 50 block before, transaction will fail
@@ -406,7 +413,11 @@ class Account(object):
406
413
  :return: result of view function call
407
414
  """
408
415
  result = await self._provider.view_call(
409
- contract_id, method_name, json.dumps(args).encode("utf8"), block_id=block_id, threshold=threshold
416
+ contract_id,
417
+ method_name,
418
+ json.dumps(args).encode("utf8"),
419
+ block_id=block_id,
420
+ threshold=threshold,
410
421
  )
411
422
  if "error" in result:
412
423
  raise ViewFunctionError(result["error"])
py_near/providers.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import base64
3
+ import datetime
3
4
  import json
4
5
  from collections import Counter
5
6
  from typing import Optional
@@ -7,8 +8,7 @@ from typing import Optional
7
8
  import aiohttp
8
9
  from aiohttp import ClientResponseError, ClientConnectorError, ServerDisconnectedError
9
10
  from loguru import logger
10
- import datetime
11
- from py_near import constants
11
+
12
12
  from py_near.constants import TIMEOUT_WAIT_RPC
13
13
  from py_near.exceptions.exceptions import RpcNotAvailableError, RpcEmptyResponse
14
14
  from py_near.exceptions.provider import (
@@ -46,7 +46,7 @@ PROVIDER_CODE_TO_EXCEPTION = {
46
46
 
47
47
 
48
48
  class JsonProvider(object):
49
- def __init__(self, rpc_addr, allow_broadcast=True):
49
+ def __init__(self, rpc_addr, allow_broadcast=True, timeout=TIMEOUT_WAIT_RPC):
50
50
  """
51
51
  :param rpc_addr: str or list of str
52
52
  :param allow_broadcast: bool - submit signed transaction to all RPCs
@@ -60,6 +60,11 @@ class JsonProvider(object):
60
60
  self._available_rpcs = self._rpc_addresses.copy()
61
61
  self._last_rpc_addr_check = 0
62
62
  self.allow_broadcast = allow_broadcast
63
+ self._timeout = timeout
64
+ self.session = aiohttp.ClientSession()
65
+
66
+ async def shutdown(self):
67
+ await self.session.close()
63
68
 
64
69
  async def check_available_rpcs(self):
65
70
  if (
@@ -88,36 +93,41 @@ class JsonProvider(object):
88
93
  "params": {"finality": "final"},
89
94
  "id": 1,
90
95
  }
91
- async with aiohttp.ClientSession() as session:
92
- async with session.post(rpc_addr, json=data) as r:
93
- if r.status == 200:
94
- data = json.loads(await r.text())["result"]
95
- if data["sync_info"]["syncing"]:
96
- last_block_ts = datetime.datetime.fromisoformat(
97
- data["sync_info"]["latest_block_time"]
98
- )
99
- diff = (
100
- datetime.datetime.utcnow().timestamp()
101
- - last_block_ts.timestamp()
102
- )
103
- is_syncing = diff > 60
104
- else:
105
- is_syncing = False
106
- if is_syncing:
107
- logger.error(f"Remove async RPC : {rpc_addr} ({diff})")
108
- continue
109
- available_rpcs.append(rpc_addr)
110
- else:
111
- logger.error(
112
- f"Remove rpc because of error {r.status}: {rpc_addr}"
96
+ async with self.session.post(rpc_addr, json=data) as r:
97
+ if r.status == 200:
98
+ data = json.loads(await r.text())["result"]
99
+ if data["sync_info"]["syncing"]:
100
+ last_block_ts = datetime.datetime.fromisoformat(
101
+ data["sync_info"]["latest_block_time"]
102
+ )
103
+ diff = (
104
+ datetime.datetime.utcnow().timestamp()
105
+ - last_block_ts.timestamp()
113
106
  )
107
+ is_syncing = diff > 60
108
+ else:
109
+ is_syncing = False
110
+ if is_syncing:
111
+ logger.error(f"Remove async RPC : {rpc_addr} ({diff})")
112
+ continue
113
+ available_rpcs.append(rpc_addr)
114
+ else:
115
+ logger.error(
116
+ f"Remove rpc because of error {r.status}: {rpc_addr}"
117
+ )
114
118
  except Exception as e:
115
119
  if rpc_addr in self._available_rpcs:
116
120
  logger.error(f"Remove rpc: {e}")
117
121
  self._available_rpcs = available_rpcs
118
122
 
123
+ @staticmethod
124
+ def most_frequent_by_hash(array):
125
+ counter = Counter(array)
126
+ most_frequent = counter.most_common(1)[0][0]
127
+ return most_frequent
128
+
119
129
  async def call_rpc_request(
120
- self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
130
+ self, method, params, broadcast=False, threshold: int = 0
121
131
  ):
122
132
  await self.check_available_rpcs()
123
133
  j = {"method": method, "params": params, "id": "dontcare", "jsonrpc": "2.0"}
@@ -127,59 +137,55 @@ class JsonProvider(object):
127
137
  if "@" in rpc_call_addr:
128
138
  auth_key = rpc_call_addr.split("//")[1].split("@")[0]
129
139
  rpc_call_addr = rpc_call_addr.replace(auth_key + "@", "")
130
- async with aiohttp.ClientSession() as session:
131
- r = await session.post(
132
- rpc_call_addr,
133
- json=j,
134
- timeout=timeout,
135
- headers={
136
- "Referer": "https://tgapp.herewallet.app",
137
- "Authorization": f"Bearer {auth_key}",
138
- }, # NEAR RPC requires Referer header
139
- )
140
- if r.status == 200:
141
- return json.loads(await r.text())
140
+ r = await self.session.post(
141
+ rpc_call_addr,
142
+ json=j,
143
+ timeout=self._timeout,
144
+ headers={
145
+ "Referer": "https://tgapp.herewallet.app",
146
+ "Authorization": f"Bearer {auth_key}",
147
+ }, # NEAR RPC requires Referer header
148
+ )
149
+ if r.status == 200:
150
+ return json.loads(await r.text())
142
151
 
143
152
  if broadcast or threshold:
144
- tasks = [
153
+ pending = [
145
154
  asyncio.create_task(f(rpc_addr)) for rpc_addr in self._available_rpcs
146
155
  ]
156
+
157
+ responses = []
158
+ while len(pending):
159
+ done, pending = await asyncio.wait(
160
+ pending, return_when=asyncio.FIRST_COMPLETED
161
+ )
162
+ for task in done:
163
+ try:
164
+ result = task.result()
165
+ if "error" not in result and threshold <= 1:
166
+ return result
167
+ responses.append(result)
168
+ except Exception as e:
169
+ logger.warning(e)
170
+ if responses:
171
+ array = [hash(json.dumps(x)) for x in responses]
172
+ most_frequent_element = self.most_frequent_by_hash(array)
173
+ correct_responses = [
174
+ x for x in responses if hash(json.dumps(x)) == most_frequent_element
175
+ ]
176
+ if len(correct_responses) >= threshold:
177
+ for task in pending:
178
+ task.cancel()
179
+ return most_frequent_element
147
180
  else:
148
- tasks = [f(rpc_addr) for rpc_addr in self._available_rpcs]
149
- responses = []
150
- for t in tasks:
151
- try:
152
- res = await t
153
- if res:
154
- responses.append(res)
155
- if not (broadcast or threshold):
181
+ for rpc_addr in self._available_rpcs:
182
+ try:
183
+ res = await f(rpc_addr)
184
+ if "error" not in res:
156
185
  return res
157
- except Exception as e:
158
- logger.error(f"Rpc error: {e}")
159
- continue
160
- if not responses:
161
- raise RpcEmptyResponse("RPC returned empty response")
162
- def most_frequent_by_hash(array):
163
- counter = Counter(array)
164
- most_frequent = counter.most_common(1)[0][0]
165
- return most_frequent
166
-
167
- if threshold:
168
- # return first most frequent response
169
- array = [hash(json.dumps(x)) for x in responses]
170
- most_frequent_element = most_frequent_by_hash(array)
171
- correct_responses = [
172
- x for x in responses if hash(json.dumps(x)) == most_frequent_element
173
- ]
174
- if len(correct_responses) >= threshold:
175
- return responses[0]
176
- raise Exception(
177
- f"Threshold not reached: {len(correct_responses)}/{threshold}"
178
- )
179
-
180
- for res in responses:
181
- if "error" not in res:
182
- return res
186
+ except Exception as e:
187
+ logger.error(f"Rpc error: {e}")
188
+ continue
183
189
  raise RpcEmptyResponse("RPC returned empty response")
184
190
 
185
191
  @staticmethod
@@ -204,11 +210,9 @@ class JsonProvider(object):
204
210
  break
205
211
  return error
206
212
 
207
- async def json_rpc(
208
- self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
209
- ):
213
+ async def json_rpc(self, method, params, broadcast=False, threshold=None):
210
214
  content = await self.call_rpc_request(
211
- method, params, timeout, broadcast=broadcast, threshold=threshold
215
+ method, params, broadcast=broadcast, threshold=threshold
212
216
  )
213
217
  if not content:
214
218
  raise RpcEmptyResponse("RPC returned empty response")
@@ -218,7 +222,7 @@ class JsonProvider(object):
218
222
  raise error
219
223
  return content["result"]
220
224
 
221
- async def send_tx(self, signed_tx: str, timeout: int = constants.TIMEOUT_WAIT_RPC):
225
+ async def send_tx(self, signed_tx: str):
222
226
  """
223
227
  Send a signed transaction to the network and return the hash of the transaction
224
228
  :param signed_tx: base64 encoded signed transaction, str.
@@ -228,13 +232,10 @@ class JsonProvider(object):
228
232
  return await self.json_rpc(
229
233
  "broadcast_tx_async",
230
234
  [signed_tx],
231
- timeout=timeout,
232
235
  broadcast=self.allow_broadcast,
233
236
  )
234
237
 
235
- async def send_tx_included(
236
- self, signed_tx: str, timeout: int = constants.TIMEOUT_WAIT_RPC
237
- ):
238
+ async def send_tx_included(self, signed_tx: str):
238
239
  """
239
240
  Send a signed transaction to the network and return the hash of the transaction
240
241
  :param signed_tx: base64 encoded signed transaction, str.
@@ -245,7 +246,6 @@ class JsonProvider(object):
245
246
  return await self.json_rpc(
246
247
  "send_tx",
247
248
  {"signed_tx_base64": signed_tx, "wait_until": "INCLUDED"},
248
- timeout=timeout,
249
249
  broadcast=self.allow_broadcast,
250
250
  )
251
251
  except InvalidNonce:
@@ -269,7 +269,6 @@ class JsonProvider(object):
269
269
  async def send_tx_and_wait(
270
270
  self,
271
271
  signed_tx: str,
272
- timeout: int = constants.TIMEOUT_WAIT_RPC,
273
272
  trx_hash: Optional[str] = None,
274
273
  receiver_id: Optional[str] = None,
275
274
  ) -> TransactionResult:
@@ -283,7 +282,6 @@ class JsonProvider(object):
283
282
  res = await self.json_rpc(
284
283
  "broadcast_tx_commit",
285
284
  [signed_tx],
286
- timeout=timeout,
287
285
  broadcast=self.allow_broadcast,
288
286
  )
289
287
  return TransactionResult(**res)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-near
3
- Version: 1.1.39
3
+ Version: 1.1.40
4
4
  Summary: Pretty simple and fully asynchronous framework for working with NEAR blockchain
5
5
  Author: pvolnov
6
6
  Author-email: petr@herewallet.app
@@ -1,5 +1,5 @@
1
1
  py_near/__init__.py,sha256=t5fAxjaU8dN8xpQR2vz0ZGhfTkdVy2RCbkhJhZFglk4,50
2
- py_near/account.py,sha256=CX0fYvWJj8g7QANLUr3G5eUvO347UW1jlunphN47Heg,17617
2
+ py_near/account.py,sha256=hz-LZpGvy1cE0_iHX4_TGpOpJ5gfe0P9EAJ_0b-S8E0,17805
3
3
  py_near/constants.py,sha256=inaWIuwmF1EB5JSB0ynnZY5rKY_QsxhF9KuCOhPsM6k,164
4
4
  py_near/dapps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  py_near/dapps/core.py,sha256=LtN9aW2gw2mvEdhzQcQJIidtjv-XL1xjb0LK8DzqtqE,231
@@ -20,10 +20,10 @@ py_near/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
20
20
  py_near/exceptions/exceptions.py,sha256=DEFipaAHm0y7oCuN2QKzHsiQvUTUQVl-Ce36Ag7n7hs,5509
21
21
  py_near/exceptions/provider.py,sha256=K-wexgjPJ8sw42JePwaP7R5dJEIn9DoFJRvVcURsx6s,7718
22
22
  py_near/models.py,sha256=GZQD1TKGWlwqsJsKRXrVNBjCdAIpk7GQypU-QOtAPFs,11533
23
- py_near/providers.py,sha256=W9jquOitiBtW1ezvcpuH5xs-1z0nQYOgvNbMHyevTSA,15646
23
+ py_near/providers.py,sha256=0nv1pnWo0FZ6ZonUbhxVCmnq_gG8vBMiIQ3qhz6toBs,15477
24
24
  py_near/transactions.py,sha256=QAXegv2JpKISk92NaChtIH6-QPHrcWbrwdKH_lH4TsU,3186
25
25
  py_near/utils.py,sha256=FirRH93ydH1cwjn0-sNrZeIn3BRD6QHedrP2VkAdJ6g,126
26
- py_near-1.1.39.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
- py_near-1.1.39.dist-info/METADATA,sha256=bBtdADCm44pJN3z7ZyutWqepQGLoeaAsqX9MfuTa8Gk,4713
28
- py_near-1.1.39.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- py_near-1.1.39.dist-info/RECORD,,
26
+ py_near-1.1.40.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
+ py_near-1.1.40.dist-info/METADATA,sha256=MLUGiQjTbaNFCyV5qL_IGnHFwBpDlHeJe04WmY37DU4,4713
28
+ py_near-1.1.40.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
+ py_near-1.1.40.dist-info/RECORD,,