coinex-api 0.0.69__py3-none-any.whl → 0.0.70__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.
coinex/ccxt/__init__.py CHANGED
@@ -26,7 +26,7 @@ sys.modules['ccxt'] = ccxt_module
26
26
 
27
27
  # ----------------------------------------------------------------------------
28
28
 
29
- __version__ = '4.4.90'
29
+ __version__ = '4.4.91'
30
30
 
31
31
  # ----------------------------------------------------------------------------
32
32
 
@@ -8,7 +8,7 @@ sys.modules['ccxt'] = ccxt_module
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
11
- __version__ = '4.4.90'
11
+ __version__ = '4.4.91'
12
12
 
13
13
  # -----------------------------------------------------------------------------
14
14
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # -----------------------------------------------------------------------------
4
4
 
5
- __version__ = '4.4.90'
5
+ __version__ = '4.4.91'
6
6
 
7
7
  # -----------------------------------------------------------------------------
8
8
 
@@ -34,7 +34,7 @@ from ccxt.base.exchange import Exchange as BaseExchange, ArgumentsRequired
34
34
  # -----------------------------------------------------------------------------
35
35
 
36
36
  from ccxt.async_support.base.ws.functions import inflate, inflate64, gunzip
37
- from ccxt.async_support.base.ws.aiohttp_client import AiohttpClient
37
+ from ccxt.async_support.base.ws.client import Client
38
38
  from ccxt.async_support.base.ws.future import Future
39
39
  from ccxt.async_support.base.ws.order_book import OrderBook, IndexedOrderBook, CountedOrderBook
40
40
 
@@ -419,7 +419,7 @@ class Exchange(BaseExchange):
419
419
  }, ws_options)
420
420
  # we use aiohttp instead of fastClient now because of this
421
421
  # https://github.com/ccxt/ccxt/pull/25995
422
- self.clients[url] = AiohttpClient(url, on_message, on_error, on_close, on_connected, options)
422
+ self.clients[url] = Client(url, on_message, on_error, on_close, on_connected, options)
423
423
  # set http/s proxy (socks proxy should be set in other place)
424
424
  httpProxy, httpsProxy, socksProxy = self.check_ws_proxy_settings()
425
425
  if (httpProxy or httpsProxy):
@@ -638,6 +638,9 @@ class Exchange(BaseExchange):
638
638
  async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
639
639
  raise NotSupported(self.id + ' watchTrades() is not supported yet')
640
640
 
641
+ async def un_watch_orders(self, symbol: Str = None, params={}):
642
+ raise NotSupported(self.id + ' unWatchOrders() is not supported yet')
643
+
641
644
  async def un_watch_trades(self, symbol: str, params={}):
642
645
  raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
643
646
 
@@ -1,21 +1,31 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- from asyncio import sleep, ensure_future, wait_for, TimeoutError
4
- from .functions import milliseconds, iso8601, deep_extend
5
- from ccxt import NetworkError, RequestTimeout, NotSupported
3
+ orjson = None
4
+ try:
5
+ import orjson as orjson
6
+ except ImportError:
7
+ pass
8
+
9
+ import json
10
+
11
+ from asyncio import sleep, ensure_future, wait_for, TimeoutError, BaseEventLoop, Future as asyncioFuture
12
+ from .functions import milliseconds, iso8601, deep_extend, is_json_encoded_object
13
+ from ccxt import NetworkError, RequestTimeout
6
14
  from ccxt.async_support.base.ws.future import Future
7
- from collections import deque
15
+ from ccxt.async_support.base.ws.functions import gunzip, inflate
16
+ from typing import Dict
17
+
18
+ from aiohttp import WSMsgType
19
+
8
20
 
9
21
  class Client(object):
10
22
 
11
23
  url = None
12
24
  ws = None
13
- futures = {}
25
+ futures: Dict[str, Future] = {}
14
26
  options = {} # ws-specific options
15
27
  subscriptions = {}
16
28
  rejections = {}
17
- message_queue = {}
18
- useMessageQueue = False
19
29
  on_message_callback = None
20
30
  on_error_callback = None
21
31
  on_close_callback = None
@@ -32,14 +42,14 @@ class Client(object):
32
42
  maxPingPongMisses = 2.0 # how many missed pongs to raise a timeout
33
43
  lastPong = None
34
44
  ping = None # ping-function if defined
45
+ proxy = None
35
46
  verbose = False # verbose output
36
47
  gunzip = False
37
48
  inflate = False
38
49
  throttle = None
39
50
  connecting = False
40
- asyncio_loop = None
51
+ asyncio_loop: BaseEventLoop = None
41
52
  ping_looper = None
42
- receive_looper = None
43
53
 
44
54
  def __init__(self, url, on_message_callback, on_error_callback, on_close_callback, on_connected_callback, config={}):
45
55
  defaults = {
@@ -70,37 +80,19 @@ class Client(object):
70
80
  if message_hash in self.rejections:
71
81
  future.reject(self.rejections[message_hash])
72
82
  del self.rejections[message_hash]
73
- del self.message_queue[message_hash]
74
- return future
75
- if self.useMessageQueue and message_hash in self.message_queue:
76
- queue = self.message_queue[message_hash]
77
- if len(queue):
78
- future.resolve(queue.popleft())
79
- del self.futures[message_hash]
80
83
  return future
81
84
 
82
85
  def resolve(self, result, message_hash):
83
86
  if self.verbose and message_hash is None:
84
87
  self.log(iso8601(milliseconds()), 'resolve received None messageHash')
85
-
86
- if self.useMessageQueue:
87
- if message_hash not in self.message_queue:
88
- self.message_queue[message_hash] = deque(maxlen=10)
89
- queue = self.message_queue[message_hash]
90
- queue.append(result)
91
- if message_hash in self.futures:
92
- future = self.futures[message_hash]
93
- future.resolve(queue.popleft())
94
- del self.futures[message_hash]
95
- else:
96
- if message_hash in self.futures:
97
- future = self.futures[message_hash]
98
- future.resolve(result)
99
- del self.futures[message_hash]
88
+ if message_hash in self.futures:
89
+ future = self.futures[message_hash]
90
+ future.resolve(result)
91
+ del self.futures[message_hash]
100
92
  return result
101
93
 
102
94
  def reject(self, result, message_hash=None):
103
- if message_hash:
95
+ if message_hash is not None:
104
96
  if message_hash in self.futures:
105
97
  future = self.futures[message_hash]
106
98
  future.reject(result)
@@ -113,19 +105,38 @@ class Client(object):
113
105
  self.reject(result, message_hash)
114
106
  return result
115
107
 
116
- async def receive_loop(self):
108
+ def receive_loop(self):
117
109
  if self.verbose:
118
110
  self.log(iso8601(milliseconds()), 'receive loop')
119
- while not self.closed():
120
- try:
121
- message = await self.receive()
122
- # self.log(iso8601(milliseconds()), 'received', message)
123
- self.handle_message(message)
124
- except Exception as e:
125
- error = NetworkError(str(e))
126
- if self.verbose:
127
- self.log(iso8601(milliseconds()), 'receive_loop', 'Exception', error)
128
- self.reset(error)
111
+ if not self.closed():
112
+ # let's drain the aiohttp buffer to avoid latency
113
+ if len(self.buffer) > 1:
114
+ size_delta = 0
115
+ while len(self.buffer) > 1:
116
+ message, size = self.buffer.popleft()
117
+ size_delta += size
118
+ self.handle_message(message)
119
+ # we must update the size of the last message inside WebSocketDataQueue
120
+ # self.receive() calls WebSocketDataQueue.read() that calls WebSocketDataQueue._read_from_buffer()
121
+ # which updates the size of the buffer, the _size will overflow and pause the transport
122
+ # make sure to set the enviroment variable AIOHTTP_NO_EXTENSIONS=Y to check
123
+ # print(self.connection._conn.protocol._payload._size)
124
+ self.buffer[0] = (self.buffer[0][0], self.buffer[0][1] + size_delta)
125
+
126
+ task = self.asyncio_loop.create_task(self.receive())
127
+
128
+ def after_interrupt(resolved: asyncioFuture):
129
+ exception = resolved.exception()
130
+ if exception is None:
131
+ self.handle_message(resolved.result())
132
+ self.asyncio_loop.call_soon(self.receive_loop)
133
+ else:
134
+ error = NetworkError(str(exception))
135
+ if self.verbose:
136
+ self.log(iso8601(milliseconds()), 'receive_loop', 'Exception', error)
137
+ self.reset(error)
138
+
139
+ task.add_done_callback(after_interrupt)
129
140
 
130
141
  async def open(self, session, backoff_delay=0):
131
142
  # exponential backoff for consequent connections if necessary
@@ -146,7 +157,7 @@ class Client(object):
146
157
  self.on_connected_callback(self)
147
158
  # run both loops forever
148
159
  self.ping_looper = ensure_future(self.ping_loop(), loop=self.asyncio_loop)
149
- self.receive_looper = ensure_future(self.receive_loop(), loop=self.asyncio_loop)
160
+ self.asyncio_loop.call_soon(self.receive_loop)
150
161
  except TimeoutError:
151
162
  # connection timeout
152
163
  error = RequestTimeout('Connection timeout')
@@ -160,6 +171,13 @@ class Client(object):
160
171
  self.log(iso8601(milliseconds()), 'NetworkError', error)
161
172
  self.on_error(error)
162
173
 
174
+ @property
175
+ def buffer(self):
176
+ # looks like they exposed it in C
177
+ # this means we can bypass it
178
+ # https://github.com/aio-libs/aiohttp/blob/master/aiohttp/_websocket/reader_c.pxd#L53C24-L53C31
179
+ return self.connection._conn.protocol._payload._buffer
180
+
163
181
  def connect(self, session, backoff_delay=0):
164
182
  if not self.connection and not self.connecting:
165
183
  self.connecting = True
@@ -170,7 +188,7 @@ class Client(object):
170
188
  if self.verbose:
171
189
  self.log(iso8601(milliseconds()), 'on_error', error)
172
190
  self.error = error
173
- self.reset(error)
191
+ self.reject(error)
174
192
  self.on_error_callback(self, error)
175
193
  if not self.closed():
176
194
  ensure_future(self.close(1006), loop=self.asyncio_loop)
@@ -179,36 +197,127 @@ class Client(object):
179
197
  if self.verbose:
180
198
  self.log(iso8601(milliseconds()), 'on_close', code)
181
199
  if not self.error:
182
- self.reset(NetworkError('Connection closed by remote server, closing code ' + str(code)))
200
+ self.reject(NetworkError('Connection closed by remote server, closing code ' + str(code)))
183
201
  self.on_close_callback(self, code)
184
- if not self.closed():
185
- ensure_future(self.close(code), loop=self.asyncio_loop)
202
+ ensure_future(self.aiohttp_close(), loop=self.asyncio_loop)
186
203
 
187
- def reset(self, error):
188
- self.message_queue = {}
189
- self.reject(error)
204
+ def log(self, *args):
205
+ print(*args)
190
206
 
191
- async def ping_loop(self):
192
- if self.verbose:
193
- self.log(iso8601(milliseconds()), 'ping loop')
207
+ def closed(self):
208
+ return (self.connection is None) or self.connection.closed
194
209
 
195
210
  def receive(self):
196
- raise NotSupported('receive() not implemented')
211
+ return self.connection.receive()
212
+
213
+ # helper method for binary and text messages
214
+ def handle_text_or_binary_message(self, data):
215
+ if self.verbose:
216
+ self.log(iso8601(milliseconds()), 'message', data)
217
+ if isinstance(data, bytes):
218
+ data = data.decode()
219
+ # decoded = json.loads(data) if is_json_encoded_object(data) else data
220
+ decode = None
221
+ if is_json_encoded_object(data):
222
+ if orjson is None:
223
+ decode = json.loads(data)
224
+ else:
225
+ decode = orjson.loads(data)
226
+ else:
227
+ decode = data
228
+ self.on_message_callback(self, decode)
197
229
 
198
230
  def handle_message(self, message):
199
- raise NotSupported('handle_message() not implemented')
231
+ # self.log(iso8601(milliseconds()), message)
232
+ if message.type == WSMsgType.TEXT:
233
+ self.handle_text_or_binary_message(message.data)
234
+ elif message.type == WSMsgType.BINARY:
235
+ data = message.data
236
+ if self.gunzip:
237
+ data = gunzip(data)
238
+ elif self.inflate:
239
+ data = inflate(data)
240
+ self.handle_text_or_binary_message(data)
241
+ # autoping is responsible for automatically replying with pong
242
+ # to a ping incoming from a server, we have to disable autoping
243
+ # with aiohttp's websockets and respond with pong manually
244
+ # otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
245
+ elif message.type == WSMsgType.PING:
246
+ if self.verbose:
247
+ self.log(iso8601(milliseconds()), 'ping', message)
248
+ ensure_future(self.connection.pong(message.data), loop=self.asyncio_loop)
249
+ elif message.type == WSMsgType.PONG:
250
+ self.lastPong = milliseconds()
251
+ if self.verbose:
252
+ self.log(iso8601(milliseconds()), 'pong', message)
253
+ pass
254
+ elif message.type == WSMsgType.CLOSE:
255
+ if self.verbose:
256
+ self.log(iso8601(milliseconds()), 'close', self.closed(), message)
257
+ self.on_close(message.data)
258
+ elif message.type == WSMsgType.ERROR:
259
+ if self.verbose:
260
+ self.log(iso8601(milliseconds()), 'error', message)
261
+ error = NetworkError(str(message))
262
+ self.on_error(error)
200
263
 
201
- def closed(self):
202
- raise NotSupported('closed() not implemented')
264
+ def create_connection(self, session):
265
+ # autoping is responsible for automatically replying with pong
266
+ # to a ping incoming from a server, we have to disable autoping
267
+ # with aiohttp's websockets and respond with pong manually
268
+ # otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
269
+ # call aenter here to simulate async with otherwise we get the error "await not called with future"
270
+ # if connecting to a non-existent endpoint
271
+ if (self.proxy):
272
+ return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), proxy=self.proxy, max_msg_size=10485760).__aenter__()
273
+ return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), max_msg_size=10485760).__aenter__()
203
274
 
204
275
  async def send(self, message):
205
- raise NotSupported('send() not implemented')
276
+ if self.verbose:
277
+ self.log(iso8601(milliseconds()), 'sending', message)
278
+ send_msg = None
279
+ if isinstance(message, str):
280
+ send_msg = message
281
+ else:
282
+ if orjson is None:
283
+ send_msg = json.dumps(message, separators=(',', ':'))
284
+ else:
285
+ send_msg = orjson.dumps(message).decode('utf-8')
286
+ return await self.connection.send_str(send_msg)
206
287
 
207
288
  async def close(self, code=1000):
208
- raise NotSupported('close() not implemented')
289
+ if self.verbose:
290
+ self.log(iso8601(milliseconds()), 'closing', code)
291
+ for future in self.futures.values():
292
+ future.cancel()
293
+ await self.aiohttp_close()
209
294
 
210
- def create_connection(self, session):
211
- raise NotSupported('create_connection() not implemented')
295
+ async def aiohttp_close(self):
296
+ if not self.closed():
297
+ await self.connection.close()
298
+ # these will end automatically once self.closed() = True
299
+ # so we don't need to cancel them
300
+ if self.ping_looper:
301
+ self.ping_looper.cancel()
212
302
 
213
- def log(self, *args):
214
- print(*args)
303
+ async def ping_loop(self):
304
+ if self.verbose:
305
+ self.log(iso8601(milliseconds()), 'ping loop')
306
+ while self.keepAlive and not self.closed():
307
+ now = milliseconds()
308
+ self.lastPong = now if self.lastPong is None else self.lastPong
309
+ if (self.lastPong + self.keepAlive * self.maxPingPongMisses) < now:
310
+ self.on_error(RequestTimeout('Connection to ' + self.url + ' timed out due to a ping-pong keepalive missing on time'))
311
+ # the following ping-clause is not necessary with aiohttp's built-in ws
312
+ # since it has a heartbeat option (see create_connection above)
313
+ # however some exchanges require a text-type ping message
314
+ # therefore we need this clause anyway
315
+ else:
316
+ if self.ping:
317
+ try:
318
+ await self.send(self.ping(self))
319
+ except Exception as e:
320
+ self.on_error(e)
321
+ else:
322
+ await self.connection.ping()
323
+ await sleep(self.keepAlive / 1000)
@@ -1,69 +1,42 @@
1
1
  import asyncio
2
- from ccxt import ExchangeClosedByUser
3
2
 
4
- class Future(asyncio.Future):
5
3
 
6
- is_race_future = False
4
+ class Future(asyncio.Future):
7
5
 
8
6
  def resolve(self, result=None):
9
7
  if not self.done():
10
- try:
11
- self.set_result(result)
12
- except BaseException as e:
13
- print("Error in Future.resolve")
14
- raise e
8
+ self.set_result(result)
15
9
 
16
10
  def reject(self, error=None):
17
11
  if not self.done():
18
- # If not an exception, wrap it in a generic Exception
19
- if not isinstance(error, BaseException):
20
- error = Exception(error)
21
- try:
22
- self.set_exception(error)
23
- except BaseException as e:
24
- print("Error in Future.reject")
25
- raise e
12
+ self.set_exception(error)
26
13
 
27
14
  @classmethod
28
15
  def race(cls, futures):
29
16
  future = Future()
30
- for f in futures:
31
- f.is_race_future = True
32
17
  coro = asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED)
33
18
  task = asyncio.create_task(coro)
34
19
 
35
20
  def callback(done):
36
- try:
37
- complete, pending = done.result()
38
- # check for exceptions
39
- for i, f in enumerate(complete):
40
- try:
41
- f.result()
42
- except ExchangeClosedByUser as e:
43
- if len(pending) == 0 and i == len(complete) - 1:
44
- future.reject(e)
45
- # wait for all the sub promises to be reject before rejecting future
46
- continue
47
- except asyncio.CancelledError as e:
48
- continue
49
- except Exception as e:
50
- future.reject(e)
51
- return
52
- # no exceptions return first result
53
- futures_list = list(complete)
54
-
55
- are_all_canceled = all([f.cancelled() for f in futures_list])
56
- if are_all_canceled:
57
- future.reject(ExchangeClosedByUser('Connection closed by the user'))
58
- return
59
-
60
- first = futures_list[0]
61
-
62
- first_result = first.result()
63
- future.resolve(first_result)
64
- except asyncio.CancelledError as e:
65
- future.reject(e)
66
- except Exception as e:
67
- future.reject(e)
21
+ complete, _ = done.result()
22
+ # check for exceptions
23
+ exceptions = []
24
+ cancelled = False
25
+ for f in complete:
26
+ if f.cancelled():
27
+ cancelled = True
28
+ else:
29
+ err = f.exception()
30
+ if err:
31
+ exceptions.append(err)
32
+ # if any exceptions return with first exception
33
+ if len(exceptions) > 0:
34
+ future.set_exception(exceptions[0])
35
+ # else return first result
36
+ elif cancelled:
37
+ future.cancel()
38
+ else:
39
+ first_result = list(complete)[0].result()
40
+ future.set_result(first_result)
68
41
  task.add_done_callback(callback)
69
42
  return future
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.4.90'
7
+ __version__ = '4.4.91'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -2580,6 +2580,9 @@ class Exchange(object):
2580
2580
  def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2581
2581
  raise NotSupported(self.id + ' watchTrades() is not supported yet')
2582
2582
 
2583
+ def un_watch_orders(self, symbol: Str = None, params={}):
2584
+ raise NotSupported(self.id + ' unWatchOrders() is not supported yet')
2585
+
2583
2586
  def un_watch_trades(self, symbol: str, params={}):
2584
2587
  raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
2585
2588
 
@@ -6894,7 +6897,7 @@ class Exchange(object):
6894
6897
  symbolAndTimeFrame = symbolsAndTimeFrames[i]
6895
6898
  symbol = self.safe_string(symbolAndTimeFrame, 0)
6896
6899
  timeframe = self.safe_string(symbolAndTimeFrame, 1)
6897
- if symbol in self.ohlcvs:
6900
+ if (self.ohlcvs is not None) and (symbol in self.ohlcvs):
6898
6901
  if timeframe in self.ohlcvs[symbol]:
6899
6902
  del self.ohlcvs[symbol][timeframe]
6900
6903
  elif symbolsLength > 0:
@@ -6910,7 +6913,7 @@ class Exchange(object):
6910
6913
  if symbol in self.tickers:
6911
6914
  del self.tickers[symbol]
6912
6915
  else:
6913
- if topic == 'myTrades':
6916
+ if topic == 'myTrades' and (self.myTrades is not None):
6914
6917
  # don't reset self.myTrades directly here
6915
6918
  # because in c# we need to use a different object(thread-safe dict)
6916
6919
  keys = list(self.myTrades.keys())
@@ -6918,13 +6921,13 @@ class Exchange(object):
6918
6921
  key = keys[i]
6919
6922
  if key in self.myTrades:
6920
6923
  del self.myTrades[key]
6921
- elif topic == 'orders':
6924
+ elif topic == 'orders' and (self.orders is not None):
6922
6925
  orderSymbols = list(self.orders.keys())
6923
6926
  for i in range(0, len(orderSymbols)):
6924
6927
  orderSymbol = orderSymbols[i]
6925
6928
  if orderSymbol in self.orders:
6926
6929
  del self.orders[orderSymbol]
6927
- elif topic == 'ticker':
6930
+ elif topic == 'ticker' and (self.tickers is not None):
6928
6931
  tickerSymbols = list(self.tickers.keys())
6929
6932
  for i in range(0, len(tickerSymbols)):
6930
6933
  tickerSymbol = tickerSymbols[i]
@@ -8,7 +8,7 @@ sys.modules['ccxt'] = ccxt_module
8
8
 
9
9
  # ----------------------------------------------------------------------------
10
10
 
11
- __version__ = '4.4.90'
11
+ __version__ = '4.4.91'
12
12
 
13
13
  # ----------------------------------------------------------------------------
14
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coinex-api
3
- Version: 0.0.69
3
+ Version: 0.0.70
4
4
  Summary: coinex crypto exchange api client
5
5
  Project-URL: Homepage, https://github.com/ccxt/ccxt
6
6
  Project-URL: Issues, https://github.com/ccxt/ccxt
@@ -1,27 +1,26 @@
1
1
  coinex/__init__.py,sha256=d633U2PpNFHvpDWLb3lItS0ObcBN0E2XgS5QkOEejI8,246
2
- coinex/ccxt/__init__.py,sha256=T_c8ubbO0URpgxghKteY6ZLaOaEsGrZpIwxDnXelYUw,6048
2
+ coinex/ccxt/__init__.py,sha256=7pmttuHlwm2_RP5dguv9X0IuFggsxsNzr2m8EoC8nCs,6048
3
3
  coinex/ccxt/coinex.py,sha256=CoCBNE0SWXgXBurhdqcsMuMsj3TnYALKFUz-_gLz2XA,267281
4
4
  coinex/ccxt/abstract/coinex.py,sha256=4TRXtWgONqkm3eSL55Y5T7Q4QxJrnOTuhP0ugsKHAWo,34856
5
- coinex/ccxt/async_support/__init__.py,sha256=bzX_4j6m_CDvygCtjeR9PUktVz6--UYe5AJMDu0SE5I,4781
5
+ coinex/ccxt/async_support/__init__.py,sha256=myEiUuL75tiub92DBGA-6boG8TfiwjKr8Cx4mqlj838,4781
6
6
  coinex/ccxt/async_support/coinex.py,sha256=lj_2qf1gnqmXbVf_pXBZddBFsHeVmh_N2JIxBHhmPwM,268569
7
7
  coinex/ccxt/async_support/base/__init__.py,sha256=aVYSsFi--b4InRs9zDN_wtCpj8odosAB726JdUHavrk,67
8
- coinex/ccxt/async_support/base/exchange.py,sha256=3qo41BpgIc0nGYI3Fka9-_Wodr_xbzh47eW0jAuMsUQ,119161
8
+ coinex/ccxt/async_support/base/exchange.py,sha256=RBB3v5Eqi6U0twVnaocSBCk0MD87meBQdYIz_mGCRUs,119286
9
9
  coinex/ccxt/async_support/base/throttler.py,sha256=tvDVcdRUVYi8fZRlEcnqtgzcgB_KMUMRs5Pu8tuU-tU,1847
10
10
  coinex/ccxt/async_support/base/ws/__init__.py,sha256=uockzpLuwntKGZbs5EOWFe-Zg-k6Cj7GhNJLc_RX0so,1791
11
- coinex/ccxt/async_support/base/ws/aiohttp_client.py,sha256=Y5HxAVXyyYduj6b6SbbUZETlq3GrVMzrkW1r-TMgpb8,6329
12
11
  coinex/ccxt/async_support/base/ws/cache.py,sha256=xf2VOtfUwloxSlIQ39M1RGZHWQzyS9IGhB5NX6cDcAc,8370
13
- coinex/ccxt/async_support/base/ws/client.py,sha256=J5lTz3QGTaURZYeqW4R5xNw1orDlHYoOVXIJIX6d5Zc,8188
12
+ coinex/ccxt/async_support/base/ws/client.py,sha256=3H6NnHk7GIC6wqJ49eshuvy5MnT3ma4cnAPSWKNy6S8,13475
14
13
  coinex/ccxt/async_support/base/ws/functions.py,sha256=qwvEnjtINWL5ZU-dbbeIunjyBxzFqbGWHfVhxqAcKug,1499
15
- coinex/ccxt/async_support/base/ws/future.py,sha256=WhAJ7wdEiLdfgl5tfGHv6HgLxAN0tTc9xL4gbkKVOaE,2409
14
+ coinex/ccxt/async_support/base/ws/future.py,sha256=9yFyxqT7cl-7ZFM6LM4b6UPXyO2FGIbAhs5uoJ3-Smo,1271
16
15
  coinex/ccxt/async_support/base/ws/order_book.py,sha256=uBUaIHhzMRykpmo4BCsdJ-t_HozS6VxhEs8x-Kbj-NI,2894
17
16
  coinex/ccxt/async_support/base/ws/order_book_side.py,sha256=GhnGUt78pJ-AYL_Dq9produGjmBJLCI5FHIRdMz1O-g,6551
18
17
  coinex/ccxt/base/__init__.py,sha256=eTx1OE3HJjspFUQjGm6LBhaQiMKJnXjkdP-JUXknyQ0,1320
19
18
  coinex/ccxt/base/decimal_to_precision.py,sha256=fgWRBzRTtsf3r2INyS4f7WHlzgjB5YM1ekiwqD21aac,6634
20
19
  coinex/ccxt/base/errors.py,sha256=MvCrL_sAM3de616T6RE0PSxiF2xV6Qqz5b1y1ghidbk,4888
21
- coinex/ccxt/base/exchange.py,sha256=b6TLBQicsWseX5icZF9BC89wQfUaqN5IPBSEcNOq1SM,328431
20
+ coinex/ccxt/base/exchange.py,sha256=wjO_dKXa2T3CqHP7jZtL7grD_tifCcNn_wew8PNCotc,328697
22
21
  coinex/ccxt/base/precise.py,sha256=koce64Yrp6vFbGijJtUt-QQ6XhJgeGTCksZ871FPp_A,8886
23
22
  coinex/ccxt/base/types.py,sha256=IbLO7Ni-plO36xlOdJQFqujSJBq0q9qll009ShZ0M_U,11468
24
- coinex/ccxt/pro/__init__.py,sha256=XsqfwQUcuIZcHsBnurz6OO6sjdH1cE5NEXVfJxwWMoA,4095
23
+ coinex/ccxt/pro/__init__.py,sha256=uRQDrYnIHi3nuDyFBc1mTLrYjxRSMYdHkHaRQu1PSoc,4095
25
24
  coinex/ccxt/pro/coinex.py,sha256=aQ6Xa4ML0PTCgGleDJuhjqntspAREz6XxQwX9IcD6OY,56616
26
25
  coinex/ccxt/static_dependencies/README.md,sha256=3TCvhhn09_Cqf9BDDpao1V7EfKHDpQ6k9oWRsLFixpU,18
27
26
  coinex/ccxt/static_dependencies/__init__.py,sha256=tzFje8cloqmiIE6kola3EaYC0SnD1izWnri69hzHsSw,168
@@ -282,6 +281,6 @@ coinex/ccxt/static_dependencies/toolz/curried/exceptions.py,sha256=gKFOHDIayAWnX
282
281
  coinex/ccxt/static_dependencies/toolz/curried/operator.py,sha256=ML92mknkAwzBl2NCm-4werSUmJEtSHNY9NSzhseNM9s,525
283
282
  coinex/ccxt/static_dependencies/typing_inspect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
284
283
  coinex/ccxt/static_dependencies/typing_inspect/typing_inspect.py,sha256=5gIWomLPfuDpgd3gX1GlnX0MuXM3VorR4j2W2qXORiQ,28269
285
- coinex_api-0.0.69.dist-info/METADATA,sha256=T_4RyLSLaFa_t3jio6YK4UxlERIb1zEzq9b1Tfdyi20,19969
286
- coinex_api-0.0.69.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
287
- coinex_api-0.0.69.dist-info/RECORD,,
284
+ coinex_api-0.0.70.dist-info/METADATA,sha256=jNbtpJEkmSP0HhL5t6JJ1tyBpWUmLtxuXmtuP5Jp5kQ,19969
285
+ coinex_api-0.0.70.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
286
+ coinex_api-0.0.70.dist-info/RECORD,,
@@ -1,147 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- orjson = None
4
- try:
5
- import orjson as orjson
6
- except ImportError:
7
- pass
8
-
9
- import json
10
- from asyncio import sleep, ensure_future
11
- from aiohttp import WSMsgType
12
- from .functions import milliseconds, iso8601, is_json_encoded_object
13
- from ccxt.async_support.base.ws.client import Client
14
- from ccxt.async_support.base.ws.functions import gunzip, inflate
15
- from ccxt import NetworkError, RequestTimeout, ExchangeClosedByUser
16
-
17
-
18
- class AiohttpClient(Client):
19
-
20
- proxy = None
21
-
22
- def closed(self):
23
- return (self.connection is None) or self.connection.closed
24
-
25
- def receive(self):
26
- return self.connection.receive()
27
-
28
- # helper method for binary and text messages
29
- def handle_text_or_binary_message(self, data):
30
- if self.verbose:
31
- self.log(iso8601(milliseconds()), 'message', data)
32
- if isinstance(data, bytes):
33
- data = data.decode()
34
- # decoded = json.loads(data) if is_json_encoded_object(data) else data
35
- decode = None
36
- if is_json_encoded_object(data):
37
- if orjson is None:
38
- decode = json.loads(data)
39
- else:
40
- decode = orjson.loads(data)
41
- else:
42
- decode = data
43
- self.on_message_callback(self, decode)
44
-
45
- def handle_message(self, message):
46
- # self.log(iso8601(milliseconds()), message)
47
- if message.type == WSMsgType.TEXT:
48
- self.handle_text_or_binary_message(message.data)
49
- elif message.type == WSMsgType.BINARY:
50
- data = message.data
51
- if self.gunzip:
52
- data = gunzip(data)
53
- elif self.inflate:
54
- data = inflate(data)
55
- self.handle_text_or_binary_message(data)
56
- # autoping is responsible for automatically replying with pong
57
- # to a ping incoming from a server, we have to disable autoping
58
- # with aiohttp's websockets and respond with pong manually
59
- # otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
60
- elif message.type == WSMsgType.PING:
61
- if self.verbose:
62
- self.log(iso8601(milliseconds()), 'ping', message)
63
- ensure_future(self.connection.pong(message.data), loop=self.asyncio_loop)
64
- elif message.type == WSMsgType.PONG:
65
- self.lastPong = milliseconds()
66
- if self.verbose:
67
- self.log(iso8601(milliseconds()), 'pong', message)
68
- pass
69
- elif message.type == WSMsgType.CLOSE:
70
- if self.verbose:
71
- self.log(iso8601(milliseconds()), 'close', self.closed(), message)
72
- self.on_close(message.data)
73
- elif message.type == WSMsgType.CLOSED:
74
- if self.verbose:
75
- self.log(iso8601(milliseconds()), 'closed', self.closed(), message)
76
- self.on_close(1000)
77
- elif message.type == WSMsgType.ERROR:
78
- if self.verbose:
79
- self.log(iso8601(milliseconds()), 'error', message)
80
- error = NetworkError(str(message))
81
- self.on_error(error)
82
-
83
- def create_connection(self, session):
84
- # autoping is responsible for automatically replying with pong
85
- # to a ping incoming from a server, we have to disable autoping
86
- # with aiohttp's websockets and respond with pong manually
87
- # otherwise aiohttp's websockets client won't trigger WSMsgType.PONG
88
- # call aenter here to simulate async with otherwise we get the error "await not called with future"
89
- # if connecting to a non-existent endpoint
90
- if (self.proxy):
91
- return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), proxy=self.proxy, max_msg_size=10485760).__aenter__()
92
- return session.ws_connect(self.url, autoping=False, autoclose=False, headers=self.options.get('headers'), max_msg_size=10485760).__aenter__()
93
-
94
- async def send(self, message):
95
- if self.verbose:
96
- self.log(iso8601(milliseconds()), 'sending', message)
97
- send_msg = None
98
- if isinstance(message, str):
99
- send_msg = message
100
- else:
101
- if orjson is None:
102
- send_msg = json.dumps(message, separators=(',', ':'))
103
- else:
104
- send_msg = orjson.dumps(message).decode('utf-8')
105
- return await self.connection.send_str(send_msg)
106
-
107
- async def close(self, code=1000):
108
- if self.verbose:
109
- self.log(iso8601(milliseconds()), 'closing', code)
110
- if not self.closed():
111
- await self.connection.close()
112
- # these will end automatically once self.closed() = True
113
- # so we don't need to cancel them
114
- if self.ping_looper:
115
- self.ping_looper.cancel()
116
- if self.receive_looper:
117
- self.receive_looper.cancel() # cancel all pending futures stored in self.futures
118
- for key in self.futures:
119
- future = self.futures[key]
120
- if not future.done():
121
- if future.is_race_future:
122
- future.cancel() # this is an "internal" future so we want to cancel it silently
123
- else:
124
- future.reject(ExchangeClosedByUser('Connection closed by the user'))
125
-
126
-
127
- async def ping_loop(self):
128
- if self.verbose:
129
- self.log(iso8601(milliseconds()), 'ping loop')
130
- while self.keepAlive and not self.closed():
131
- now = milliseconds()
132
- self.lastPong = now if self.lastPong is None else self.lastPong
133
- if (self.lastPong + self.keepAlive * self.maxPingPongMisses) < now:
134
- self.on_error(RequestTimeout('Connection to ' + self.url + ' timed out due to a ping-pong keepalive missing on time'))
135
- # the following ping-clause is not necessary with aiohttp's built-in ws
136
- # since it has a heartbeat option (see create_connection above)
137
- # however some exchanges require a text-type ping message
138
- # therefore we need this clause anyway
139
- else:
140
- if self.ping:
141
- try:
142
- await self.send(self.ping(self))
143
- except Exception as e:
144
- self.on_error(e)
145
- else:
146
- await self.connection.ping()
147
- await sleep(self.keepAlive / 1000)