py-near 1.1.38__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,115 +93,100 @@ 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"]
113
102
  )
103
+ diff = (
104
+ datetime.datetime.utcnow().timestamp()
105
+ - last_block_ts.timestamp()
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"}
124
- res = {}
125
- if broadcast or threshold:
126
134
 
127
- async def f(rpc_call_addr):
128
- async with aiohttp.ClientSession() as session:
129
- r = await session.post(
130
- rpc_call_addr,
131
- json=j,
132
- timeout=timeout,
133
- headers={
134
- "Referer": "https://tgapp.herewallet.app"
135
- }, # NEAR RPC requires Referer header
136
- )
137
- if r.status == 200:
138
- return json.loads(await r.text())
135
+ async def f(rpc_call_addr):
136
+ auth_key = "py-near"
137
+ if "@" in rpc_call_addr:
138
+ auth_key = rpc_call_addr.split("//")[1].split("@")[0]
139
+ rpc_call_addr = rpc_call_addr.replace(auth_key + "@", "")
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())
139
151
 
140
- tasks = [
152
+ if broadcast or threshold:
153
+ pending = [
141
154
  asyncio.create_task(f(rpc_addr)) for rpc_addr in self._available_rpcs
142
155
  ]
156
+
143
157
  responses = []
144
- for t in tasks:
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
180
+ else:
181
+ for rpc_addr in self._available_rpcs:
145
182
  try:
146
- responses.append(await t)
183
+ res = await f(rpc_addr)
184
+ if "error" not in res:
185
+ return res
147
186
  except Exception as e:
148
187
  logger.error(f"Rpc error: {e}")
149
188
  continue
150
-
151
- def most_frequent_by_hash(array):
152
- counter = Counter(array)
153
- most_frequent = counter.most_common(1)[0][0]
154
- return most_frequent
155
-
156
- if threshold:
157
- # return first most frequent response
158
- array = [hash(json.dumps(x)) for x in responses]
159
- most_frequent_element = most_frequent_by_hash(array)
160
- correct_responses = [
161
- x for x in responses if hash(json.dumps(x)) == most_frequent_element
162
- ]
163
- if len(correct_responses) >= threshold:
164
- return responses[0]
165
- raise Exception(
166
- f"Threshold not reached: {len(correct_responses)}/{threshold}"
167
- )
168
-
169
- if broadcast:
170
- # return first response without errors
171
- for res in responses:
172
- if "error" not in res:
173
- return res
174
- return responses[0]
175
-
176
- for rpc_addr in self._available_rpcs:
177
- try:
178
- async with aiohttp.ClientSession() as session:
179
- r = await session.post(
180
- rpc_addr,
181
- json=j,
182
- timeout=timeout,
183
- headers={
184
- "Referer": "https://tgapp.herewallet.app/"
185
- }, # NEAR RPC requires Referer header
186
- )
187
- r.raise_for_status()
188
- res = json.loads(await r.text())
189
- return res
190
- except (
191
- RPCTimeoutError,
192
- ClientResponseError,
193
- ClientConnectorError,
194
- ServerDisconnectedError,
195
- ConnectionError,
196
- ) as e:
197
- logger.error(f"Rpc error: {e}")
198
- continue
199
- return res
189
+ raise RpcEmptyResponse("RPC returned empty response")
200
190
 
201
191
  @staticmethod
202
192
  def get_error_from_response(content: dict):
@@ -220,11 +210,9 @@ class JsonProvider(object):
220
210
  break
221
211
  return error
222
212
 
223
- async def json_rpc(
224
- self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
225
- ):
213
+ async def json_rpc(self, method, params, broadcast=False, threshold=None):
226
214
  content = await self.call_rpc_request(
227
- method, params, timeout, broadcast=broadcast, threshold=threshold
215
+ method, params, broadcast=broadcast, threshold=threshold
228
216
  )
229
217
  if not content:
230
218
  raise RpcEmptyResponse("RPC returned empty response")
@@ -234,7 +222,7 @@ class JsonProvider(object):
234
222
  raise error
235
223
  return content["result"]
236
224
 
237
- async def send_tx(self, signed_tx: str, timeout: int = constants.TIMEOUT_WAIT_RPC):
225
+ async def send_tx(self, signed_tx: str):
238
226
  """
239
227
  Send a signed transaction to the network and return the hash of the transaction
240
228
  :param signed_tx: base64 encoded signed transaction, str.
@@ -244,13 +232,10 @@ class JsonProvider(object):
244
232
  return await self.json_rpc(
245
233
  "broadcast_tx_async",
246
234
  [signed_tx],
247
- timeout=timeout,
248
235
  broadcast=self.allow_broadcast,
249
236
  )
250
237
 
251
- async def send_tx_included(
252
- self, signed_tx: str, timeout: int = constants.TIMEOUT_WAIT_RPC
253
- ):
238
+ async def send_tx_included(self, signed_tx: str):
254
239
  """
255
240
  Send a signed transaction to the network and return the hash of the transaction
256
241
  :param signed_tx: base64 encoded signed transaction, str.
@@ -261,7 +246,6 @@ class JsonProvider(object):
261
246
  return await self.json_rpc(
262
247
  "send_tx",
263
248
  {"signed_tx_base64": signed_tx, "wait_until": "INCLUDED"},
264
- timeout=timeout,
265
249
  broadcast=self.allow_broadcast,
266
250
  )
267
251
  except InvalidNonce:
@@ -285,7 +269,6 @@ class JsonProvider(object):
285
269
  async def send_tx_and_wait(
286
270
  self,
287
271
  signed_tx: str,
288
- timeout: int = constants.TIMEOUT_WAIT_RPC,
289
272
  trx_hash: Optional[str] = None,
290
273
  receiver_id: Optional[str] = None,
291
274
  ) -> TransactionResult:
@@ -299,7 +282,6 @@ class JsonProvider(object):
299
282
  res = await self.json_rpc(
300
283
  "broadcast_tx_commit",
301
284
  [signed_tx],
302
- timeout=timeout,
303
285
  broadcast=self.allow_broadcast,
304
286
  )
305
287
  return TransactionResult(**res)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-near
3
- Version: 1.1.38
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=A_h587h5e-HXJeko7z2YdDw0SkC3snNJ8hk90POOge8,16190
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.38.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
- py_near-1.1.38.dist-info/METADATA,sha256=YD1v8lUXSE_IgAcDNcwkEPbY16lL12k3Ri_YORwqmO0,4713
28
- py_near-1.1.38.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- py_near-1.1.38.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,,