exchanges-wrapper 2.1.8__tar.gz → 2.1.9__tar.gz

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.
Files changed (20) hide show
  1. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/PKG-INFO +2 -2
  2. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/__init__.py +1 -1
  3. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/client.py +14 -8
  4. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/events.py +2 -9
  5. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv.py +6 -4
  6. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/web_sockets.py +10 -7
  7. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/pyproject.toml +1 -1
  8. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/LICENSE.md +0 -0
  9. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/README.md +0 -0
  10. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/definitions.py +0 -0
  11. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/errors.py +0 -0
  12. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
  13. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/http_client.py +0 -0
  14. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/lib.py +0 -0
  15. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/martin/__init__.py +0 -0
  16. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/bitfinex_parser.py +0 -0
  17. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/bybit_parser.py +0 -0
  18. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/huobi_parser.py +0 -0
  19. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/okx_parser.py +0 -0
  20. {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/proto/martin.proto +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: exchanges-wrapper
3
- Version: 2.1.8
3
+ Version: 2.1.9
4
4
  Summary: REST API and WebSocket asyncio wrapper with grpc powered multiplexer server
5
5
  Author-email: Thomas Marchand <thomas.marchand@tuta.io>, Jerry Fedorenko <jerry.fedorenko@yahoo.com>
6
6
  Requires-Python: >=3.9
@@ -11,7 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: Unix
12
12
  Classifier: Operating System :: Microsoft :: Windows
13
13
  Classifier: Operating System :: MacOS
14
- Requires-Dist: crypto-ws-api==2.0.8
14
+ Requires-Dist: crypto-ws-api==2.0.9
15
15
  Requires-Dist: grpcio==1.62.0
16
16
  Requires-Dist: pyotp~=2.9.0
17
17
  Requires-Dist: simplejson==3.19.2
@@ -12,7 +12,7 @@ __maintainer__ = "Jerry Fedorenko"
12
12
  __contact__ = "https://github.com/DogsTailFarmer"
13
13
  __email__ = "jerry.fedorenko@yahoo.com"
14
14
  __credits__ = ["https://github.com/DanyaSWorlD"]
15
- __version__ = "2.1.8"
15
+ __version__ = "2.1.9"
16
16
 
17
17
  from pathlib import Path
18
18
  import shutil
@@ -108,6 +108,12 @@ class Client:
108
108
  self.main_account_uid = None
109
109
  self.ledgers_id = []
110
110
  self.ts_start = {}
111
+ self.tasks = set()
112
+
113
+ def tasks_manage(self, coro):
114
+ _t = asyncio.create_task(coro)
115
+ self.tasks.add(_t)
116
+ _t.add_done_callback(self.tasks.discard)
111
117
 
112
118
  async def fetch_object(self, key):
113
119
  res = None
@@ -145,7 +151,7 @@ class Client:
145
151
  # load rate limits
146
152
  self.rate_limits = infos["rateLimits"]
147
153
  self.loaded = True
148
- logger.info(f"Info for {self.exchange}:{symbol} loaded success")
154
+ logger.info(f"Info for {self.exchange}:{symbol} loaded successfully")
149
155
 
150
156
  async def close(self):
151
157
  await self.session.close()
@@ -176,7 +182,7 @@ class Client:
176
182
  user_data_stream = BBTPrivateEventsDataStream(self, self.endpoint_ws_auth, self.exchange, _trade_id)
177
183
  if user_data_stream:
178
184
  self.data_streams[_trade_id] |= {user_data_stream}
179
- asyncio.ensure_future(user_data_stream.start())
185
+ self.tasks_manage(user_data_stream.start())
180
186
  timeout = STATUS_TIMEOUT / 0.1
181
187
  while not user_data_stream.wss_started:
182
188
  timeout -= 1
@@ -190,8 +196,7 @@ class Client:
190
196
  if self.exchange == 'binance':
191
197
  market_data_stream = MarketEventsDataStream(self, self.endpoint_ws_public, self.exchange, _trade_id)
192
198
  self.data_streams[_trade_id] |= {market_data_stream}
193
- asyncio.ensure_future(market_data_stream.start())
194
- # start_list.append(market_data_stream.start())
199
+ self.tasks_manage(market_data_stream.start())
195
200
  else:
196
201
  for channel in _events:
197
202
  # https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url
@@ -202,7 +207,7 @@ class Client:
202
207
  #
203
208
  market_data_stream = MarketEventsDataStream(self, _endpoint, self.exchange, _trade_id, channel)
204
209
  self.data_streams[_trade_id] |= {market_data_stream}
205
- asyncio.ensure_future(market_data_stream.start())
210
+ self.tasks_manage(market_data_stream.start())
206
211
 
207
212
  async def stop_events_listener(self, _trade_id):
208
213
  logger.info(f"Stop events listener data streams for {_trade_id}")
@@ -1249,12 +1254,12 @@ class Client:
1249
1254
  params = {'category': 'spot', 'symbol': symbol}
1250
1255
  res, _ = await self.http.send_api_call("/v5/order/cancel-all", method="POST", signed=True, **params)
1251
1256
 
1252
- tasks = []
1257
+ tasks = set()
1253
1258
  for order in res.get('list', []):
1254
1259
  _id = order.get('orderId')
1255
- task = asyncio.ensure_future(self.fetch_object(f"oc-{_id}"))
1260
+ task = asyncio.create_task(self.fetch_object(f"oc-{_id}"))
1256
1261
  task.set_name(f"{_id}")
1257
- tasks.append(task)
1262
+ tasks.add(task)
1258
1263
 
1259
1264
  if tasks:
1260
1265
  done, pending = await asyncio.wait(tasks, timeout=STATUS_TIMEOUT)
@@ -1267,6 +1272,7 @@ class Client:
1267
1272
  _res = await self.fetch_order(trade_id, symbol, order_id=_id, response_type=True)
1268
1273
  binance_res.append(_res)
1269
1274
  pending.clear()
1275
+ tasks.clear()
1270
1276
 
1271
1277
  return binance_res
1272
1278
 
@@ -1,5 +1,4 @@
1
- import asyncio
2
- import functools
1
+
3
2
  from collections import defaultdict
4
3
  import logging
5
4
 
@@ -11,7 +10,6 @@ logger = logging.getLogger(__name__)
11
10
  # based on: https://stackoverflow.com/a/2022629/10144963
12
11
  class Handlers(list):
13
12
  async def __call__(self, *args, **kwargs):
14
- loop = asyncio.get_running_loop()
15
13
  trade_id = kwargs.pop('trade_id', None)
16
14
  _trade_id = None
17
15
  for func in self:
@@ -20,12 +18,7 @@ class Handlers(list):
20
18
  except Exception as ex: # skipcq: PYL-W0703
21
19
  logger.warning(f"Handlers error when try get trade_id: {ex}")
22
20
  if trade_id is None or trade_id == _trade_id:
23
- if asyncio.iscoroutinefunction(func):
24
- await func(*args, **kwargs)
25
- continue
26
- if kwargs:
27
- func = functools.partial(func, **kwargs)
28
- loop.run_in_executor(None, func, *args)
21
+ await func(*args, **kwargs)
29
22
 
30
23
  def __repr__(self):
31
24
  return f"Handlers({list.__repr__(self)})"
@@ -185,14 +185,16 @@ class Martin(mr.MartinBase):
185
185
  raise GRPCError(status=Status.FAILED_PRECONDITION, message=str(ex))
186
186
 
187
187
  try:
188
- await open_client.client.load(request.symbol)
188
+ await asyncio.wait_for(open_client.client.load(request.symbol), timeout=HEARTBEAT * 60)
189
189
  except asyncio.CancelledError:
190
190
  pass # Task cancellation should not be logged as an error
191
+ except asyncio.exceptions.TimeoutError:
192
+ await OpenClient.get_client(client_id).client.session.close()
193
+ OpenClient.remove_client(request.account_name)
194
+ raise GRPCError(status=Status.UNAVAILABLE, message=f"'{open_client.name}' timeout error")
191
195
  except Exception as ex:
192
196
  logger.warning(f"OpenClientConnection for '{open_client.name}' exception: {ex}")
193
197
  logger.debug(f"Exception traceback: {traceback.format_exc()}")
194
- await OpenClient.get_client(client_id).client.session.close()
195
- OpenClient.remove_client(request.account_name)
196
198
  raise GRPCError(status=Status.RESOURCE_EXHAUSTED, message=str(ex))
197
199
 
198
200
  # Set rate_limiter
@@ -851,7 +853,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
851
853
  for oc in OpenClient.open_clients:
852
854
  await oc.client.session.close()
853
855
 
854
- [task.cancel() for task in asyncio.all_tasks() if not task.done()]
856
+ [task.cancel() for task in asyncio.all_tasks() if not task.done() and task is not asyncio.current_task()]
855
857
 
856
858
 
857
859
  def main():
@@ -48,7 +48,7 @@ class EventsDataStream:
48
48
  self.wss_event_buffer = {}
49
49
  self._order_book = None
50
50
  self._price = None
51
- self.tasks_list = []
51
+ self.tasks = set()
52
52
  self.wss_started = False
53
53
 
54
54
  async def start(self):
@@ -84,8 +84,13 @@ class EventsDataStream:
84
84
  await self.websocket.close(code=4000)
85
85
 
86
86
  def tasks_cancel(self):
87
- [task.cancel() for task in self.tasks_list if not task.done()]
88
- self.tasks_list.clear()
87
+ [task.cancel() for task in self.tasks if not task.done()]
88
+ self.tasks.clear()
89
+
90
+ def tasks_manage(self, coro):
91
+ _t = asyncio.create_task(coro)
92
+ self.tasks.add(_t)
93
+ _t.add_done_callback(self.tasks.discard)
89
94
 
90
95
  async def _handle_event(self, *args):
91
96
  pass # meant to be overridden in a subclass
@@ -110,8 +115,7 @@ class EventsDataStream:
110
115
  return
111
116
  elif ((msg_data.get("ret_msg") == "subscribe" or msg_data.get("op") in ("auth", "subscribe"))
112
117
  and msg_data.get("success")):
113
- _task = asyncio.ensure_future(self.bybit_heartbeat(ch_type or "private"))
114
- self.tasks_list.append(_task)
118
+ self.tasks_manage(self.bybit_heartbeat(ch_type or "private"))
115
119
  if msg_data["op"] == "subscribe" and msg_data["success"] and not msg_data["ret_msg"]:
116
120
  self.wss_started = True
117
121
  elif ((msg_data.get("ret_msg") == "subscribe" or msg_data.get("op") in ("auth", "subscribe"))
@@ -602,8 +606,7 @@ class UserEventsDataStream(EventsDataStream):
602
606
 
603
607
  async def start_wss(self):
604
608
  logger.info(f"Start User WSS for {self.exchange}")
605
- _task = asyncio.ensure_future(self._heartbeat(self.listen_key))
606
- self.tasks_list.append(_task)
609
+ self.tasks_manage(self._heartbeat(self.listen_key))
607
610
  try:
608
611
  await self.ws_listener()
609
612
  finally:
@@ -17,7 +17,7 @@ dynamic = ["version", "description"]
17
17
  requires-python = ">=3.9"
18
18
 
19
19
  dependencies = [
20
- "crypto-ws-api==2.0.8",
20
+ "crypto-ws-api==2.0.9",
21
21
  "grpcio==1.62.0",
22
22
  "pyotp~=2.9.0",
23
23
  "simplejson==3.19.2",