crypto-ws-api 2.0.19__tar.gz → 2.1.0__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.
@@ -1,3 +1,21 @@
1
+ ## 2.1.0 - 2025-06-22
2
+ ✨ refactor(ws_session.py): simplify `ws_login` and `request` methods by removing redundant `send_api_key` parameter.
3
+
4
+ ## 2.0.21 - 2025-06-22
5
+ 🔧 fix(README.md): Improve documentation layout for better readability
6
+ 🔧 fix(ws_session.py): Handle `asyncio.CancelledError` gracefully in `start_wss` method
7
+ 🔧 fix(ws_session.py): Replace `asyncio.CancelledError` with `KeyboardInterrupt` exception in `handle_request` method
8
+ 🔧 fix(demo.py): Remove unnecessary `asyncio.CancelledError` handling to clean up error logging
9
+ 🔧 fix(ws_api.toml.template): Update testnet URL for Binance websocket API
10
+
11
+ ## 2.0.20 - 2025-04-24
12
+ * On `Binance` now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
13
+ ✨ feat(ws_session): implement `compose_binance_ws_auth` for Binance WebSocket authentication
14
+ 🔧 fix(ws_session): update `generate_signature` for improved signature handling
15
+ 🔧 fix(ws_session): handle `None` return in `request` method
16
+ 🚀 chore(requirements): add `cryptography` dependency for signature handling
17
+ 🔧 fix(pyproject): require Python version 3.10 or higher
18
+
1
19
  ## 2.0.19 - 2025-03-28
2
20
  ### Fix
3
21
  * `ws_session.ws_login`: Incorrect exit from authentication procedure in case of unsuccessful attempt
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: crypto-ws-api
3
- Version: 2.0.19
3
+ Version: 2.1.0
4
4
  Summary: Crypto WS API connector for ASYNC requests
5
5
  Author-email: Jerry Fedorenko <jerry.fedorenko@yahoo.com>
6
- Requires-Python: >=3.8
6
+ Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -16,6 +16,7 @@ Requires-Dist: platformdirs==4.3.6
16
16
  Requires-Dist: toml~=0.10.2
17
17
  Requires-Dist: websockets==15.0.1
18
18
  Requires-Dist: ujson~=5.10.0
19
+ Requires-Dist: cryptography~=44.0.2
19
20
  Project-URL: Source, https://github.com/DogsTailFarmer/crypto-ws-api
20
21
 
21
22
  <h1 align="center"><img align="center" src="https://user-images.githubusercontent.com/77513676/250364389-cbedc171-a930-4467-a0cd-21627a6a41ed.svg" width="75">Crypto WS API connector for ASYNC requests</h1>
@@ -25,14 +26,15 @@ Project-URL: Source, https://github.com/DogsTailFarmer/crypto-ws-api
25
26
  <h3 align="center">Provides of connection management, keepalive and rate limits control</h3>
26
27
 
27
28
  ***
28
- <a href="https://pypi.org/project/crypto-ws-api/"><img src="https://img.shields.io/pypi/v/crypto-ws-api" alt="PyPI version"></a>
29
- <a href="https://codeclimate.com/github/DogsTailFarmer/crypto-ws-api/maintainability"><img src="https://api.codeclimate.com/v1/badges/2d2a654ba393eb88d911/maintainability" /></a>
29
+ <h1 align="center"><a href="https://pypi.org/project/crypto-ws-api/"><img src="https://img.shields.io/pypi/v/crypto-ws-api" alt="PyPI version"></a>
30
30
  <a href="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api.svg/?label=resolved+issues&token=TXghPzbi0YWhkCLU8Q1tmDyQ"/></a>
31
31
  <a href="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api.svg/?label=active+issues&token=TXghPzbi0YWhkCLU8Q1tmDyQ"/></a>
32
32
  <a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_crypto-ws-api" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_crypto-ws-api&metric=alert_status"/></a>
33
33
  <a href="https://pepy.tech/project/crypto-ws-api" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/crypto-ws-api"/></a>
34
+ </h1>
35
+
34
36
  ***
35
- For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex, :heavy_check_mark:HTX
37
+ For :heavy_check_mark:[Binance](https://accounts.binance.com/en/register?ref=FXQ6HY5O), :heavy_check_mark:[OKX](https://okx.com/join/2607649), :heavy_check_mark:[Bitfinex](https://www.bitfinex.com/sign-up?refcode=v_4az2nCP), :heavy_check_mark:[HTX](https://www.htx.com/invite/en-us/1f?invite_code=9uaw3223)
36
38
  ***
37
39
 
38
40
  ## Features
@@ -5,14 +5,15 @@
5
5
  <h3 align="center">Provides of connection management, keepalive and rate limits control</h3>
6
6
 
7
7
  ***
8
- <a href="https://pypi.org/project/crypto-ws-api/"><img src="https://img.shields.io/pypi/v/crypto-ws-api" alt="PyPI version"></a>
9
- <a href="https://codeclimate.com/github/DogsTailFarmer/crypto-ws-api/maintainability"><img src="https://api.codeclimate.com/v1/badges/2d2a654ba393eb88d911/maintainability" /></a>
8
+ <h1 align="center"><a href="https://pypi.org/project/crypto-ws-api/"><img src="https://img.shields.io/pypi/v/crypto-ws-api" alt="PyPI version"></a>
10
9
  <a href="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api.svg/?label=resolved+issues&token=TXghPzbi0YWhkCLU8Q1tmDyQ"/></a>
11
10
  <a href="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/DogsTailFarmer/crypto-ws-api.svg/?label=active+issues&token=TXghPzbi0YWhkCLU8Q1tmDyQ"/></a>
12
11
  <a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_crypto-ws-api" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_crypto-ws-api&metric=alert_status"/></a>
13
12
  <a href="https://pepy.tech/project/crypto-ws-api" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/crypto-ws-api"/></a>
13
+ </h1>
14
+
14
15
  ***
15
- For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex, :heavy_check_mark:HTX
16
+ For :heavy_check_mark:[Binance](https://accounts.binance.com/en/register?ref=FXQ6HY5O), :heavy_check_mark:[OKX](https://okx.com/join/2607649), :heavy_check_mark:[Bitfinex](https://www.bitfinex.com/sign-up?refcode=v_4az2nCP), :heavy_check_mark:[HTX](https://www.htx.com/invite/en-us/1f?invite_code=9uaw3223)
16
17
  ***
17
18
 
18
19
  ## Features
@@ -13,7 +13,7 @@ __maintainer__ = "Jerry Fedorenko"
13
13
  __contact__ = "https://github.com/DogsTailFarmer"
14
14
  __email__ = "jerry.fedorenko@yahoo.com"
15
15
  __credits__ = ["https://github.com/DanyaSWorlD"]
16
- __version__ = "2.0.19"
16
+ __version__ = "2.1.0"
17
17
 
18
18
  from pathlib import Path
19
19
  import shutil
@@ -66,8 +66,6 @@ async def get_time(user_session: UserWSSession, _trade_id):
66
66
  )
67
67
  if res is None:
68
68
  logger.warning(CONST_1)
69
- except asyncio.CancelledError:
70
- pass # Task cancellation should not be logged as an error
71
69
  except Exception as _ex:
72
70
  logger.error(CONST_2, _ex)
73
71
  else:
@@ -87,8 +85,6 @@ async def current_average_price(user_session: UserWSSession, _trade_id):
87
85
  )
88
86
  if res is None:
89
87
  logger.warning(CONST_1)
90
- except asyncio.CancelledError:
91
- pass # Task cancellation should not be logged as an error
92
88
  except Exception as _ex:
93
89
  logger.error(CONST_2, _ex)
94
90
  else:
@@ -101,13 +97,10 @@ async def account_information(user_session: UserWSSession, _trade_id):
101
97
  res = await user_session.handle_request(
102
98
  _trade_id,
103
99
  "account.status",
104
- send_api_key=True,
105
100
  _signed=True
106
101
  )
107
102
  if res is None:
108
103
  logger.warning(CONST_1)
109
- except asyncio.CancelledError:
110
- pass # Task cancellation should not be logged as an error
111
104
  except Exception as _ex:
112
105
  logger.error(CONST_2, _ex)
113
106
  else:
@@ -0,0 +1,25 @@
1
+ # Parameters for crypto-ws-api connector
2
+ # Copyright © 2023-2025 Jerry Fedorenko aka VM
3
+ # __version__ = "2.0.21"
4
+
5
+ # region endpoint
6
+ [endpoint]
7
+ [endpoint.binance]
8
+ ws_api = 'wss://ws-api.binance.com:443/ws-api/v3'
9
+ ws_api_test = 'wss://ws-api.testnet.binance.vision:9443/ws-api/v3'
10
+ # endregion
11
+
12
+ # region Binance accounts
13
+ # How to Generate an Ed25519 Key Pair and Register then on Binance:
14
+ # https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a
15
+ [[accounts]]
16
+ exchange = 'binance'
17
+ name = 'Demo - Binance'
18
+ api_key = '*********** Place API key there ************'
19
+ api_secret = """
20
+ -----BEGIN PRIVATE KEY-----
21
+ *********** Place Private Ed25519 key there ************
22
+ -----END PRIVATE KEY-----
23
+ """
24
+ test_net = true
25
+ # endregion
@@ -15,6 +15,7 @@ import string
15
15
  import random
16
16
  from datetime import datetime, timezone
17
17
  from urllib.parse import urlencode, urlparse
18
+ from cryptography.hazmat.primitives.serialization import load_pem_private_key
18
19
 
19
20
  from websockets.asyncio.client import connect
20
21
  from websockets import ConnectionClosed
@@ -49,20 +50,40 @@ def set_logger(name, log_file, file_level=logging.INFO, propagate=False):
49
50
 
50
51
  logger = set_logger(__name__, Path(LOG_PATH, 'ws_session.log'))
51
52
 
53
+
52
54
  def generate_signature(exchange: str, secret: str, data: str) -> str:
53
- digest_func = hashlib.sha384 if exchange == 'bitfinex' else hashlib.sha256
54
- encoding = "ascii" if exchange == 'binance_ws' else "utf-8"
55
- key = secret.encode(encoding=encoding)
56
- signature = hmac.new(
57
- bytes(key) if exchange == 'bybit' else key,
58
- data.encode(encoding=encoding),
59
- digest_func
60
- )
55
+ encoding = "utf-8"
56
+ if exchange == 'binance':
57
+ signature = load_pem_private_key(
58
+ data=bytes(secret, encoding=encoding), password=None).sign(data.encode("ascii")
59
+ )
60
+ else:
61
+ digest_func = hashlib.sha384 if exchange == 'bitfinex' else hashlib.sha256
62
+ key = secret.encode(encoding=encoding)
63
+ signature = hmac.new(
64
+ bytes(key) if exchange == 'bybit' else key,
65
+ data.encode(encoding=encoding),
66
+ digest_func
67
+ )
61
68
  if exchange in {"huobi", "okx"}:
62
- return base64.b64encode(signature.digest()).decode()
69
+ signature = signature.digest()
70
+
71
+ if exchange in {"binance", "huobi", "okx"}:
72
+ return base64.b64encode(signature).decode()
63
73
  else:
64
74
  return signature.hexdigest()
65
75
 
76
+ def compose_binance_ws_auth(_id: str, api_key: str, api_secret: str) -> dict:
77
+ req = {"id": _id, "method": "session.logon"}
78
+ params = {
79
+ "apiKey": api_key,
80
+ "timestamp": int(time.time() * 1000)
81
+ }
82
+ payload = '&'.join(f"{key}={value}" for key, value in sorted(params.items()))
83
+ params["signature"] = generate_signature('binance', api_secret, payload)
84
+ req["params"] = params
85
+ return req
86
+
66
87
  def compose_htx_ws_auth(endpoint, exchange, api_key, api_secret):
67
88
  _params = {
68
89
  "accessKey": api_key,
@@ -98,7 +119,6 @@ class UserWSS:
98
119
  "_api_secret",
99
120
  "_passphrase",
100
121
  "_ws",
101
- "_listen_key",
102
122
  "_retry_after",
103
123
  "ws_id",
104
124
  "operational_status",
@@ -120,7 +140,6 @@ class UserWSS:
120
140
  self._api_secret = api_secret
121
141
  self._passphrase = passphrase
122
142
  self._ws = None
123
- self._listen_key = None
124
143
  self._response_pool = {}
125
144
  self._retry_after = int(time.time() * 1000) - 1
126
145
  self.ws_id = ws_id
@@ -148,7 +167,7 @@ class UserWSS:
148
167
  async def _ws_listener(self):
149
168
  self.tasks_manage(self.ws_login())
150
169
  async for msg in self._ws:
151
- # logger.info(f"_ws_listener: msg: {self.ws_id}: {msg}")
170
+ # logger.info(f"_ws_listener: ws_id: {self.ws_id} msg: {msg}")
152
171
  if isinstance(msg, str):
153
172
  try:
154
173
  _msg = json.loads(msg)
@@ -172,40 +191,39 @@ class UserWSS:
172
191
  else:
173
192
  logger.warning(f"UserWSS: {self.ws_id}: {msg}")
174
193
  await self.stop()
175
- logger.warning(f"UserWSS: {self.ws_id}: _ws_listener stopped")
176
- await self.stop()
177
194
 
178
195
  async def start_wss(self):
179
- async for self._ws in connect(
180
- self.endpoint,
181
- logger=self.logger,
182
- ping_interval=None if self.exchange == 'huobi' else 20
183
- ):
184
- try:
185
- await self._ws_listener()
186
- except ConnectionClosed as ex:
187
- if ex.rcvd and ex.rcvd.code == 4000:
188
- logger.info(f"WSS closed for {self.ws_id}")
189
- break
190
- else:
191
- self.operational_status = False
192
- [task.cancel() for task in self.tasks if not task.done()]
193
- self.tasks.clear()
194
- logger.warning(f"Restart UserWSS for {self.ws_id}")
195
- continue
196
- except Exception as ex:
197
- logger.error(f"UserWSS start other exception: {ex}")
196
+ try:
197
+ async for self._ws in connect(
198
+ self.endpoint,
199
+ logger=self.logger,
200
+ ping_interval=None if self.exchange in ('binance', 'huobi') else 20
201
+ ):
202
+ try:
203
+ await self._ws_listener()
204
+ except ConnectionClosed as ex:
205
+ if ex.rcvd and ex.rcvd.code == 4000:
206
+ logger.info(f"WSS closed for {self.ws_id}")
207
+ break
208
+ else:
209
+ self.operational_status = False
210
+ [task.cancel() for task in self.tasks if not task.done()]
211
+ self.tasks.clear()
212
+ logger.warning(f"Restart UserWSS for {self.ws_id}")
213
+ continue
214
+ except Exception as ex:
215
+ logger.error(f"UserWSS start other exception: {ex}")
216
+ except asyncio.CancelledError:
217
+ await self.stop()
218
+ raise
198
219
 
199
220
  async def ws_login(self):
200
- res = await self.request(CONST_WS_START, send_api_key=True)
221
+ res = await self.request(CONST_WS_START)
201
222
  if res is None:
202
223
  logger.warning(f"UserWSS: Not 'logged in' for {self.ws_id}")
203
224
  await self.stop()
204
225
  else:
205
- if self.exchange == 'binance':
206
- self._listen_key = res.get('listenKey')
207
- self.tasks_manage(self.heartbeat(), f"heartbeat-{self.ws_id}")
208
- elif self.exchange == 'huobi':
226
+ if self.exchange == 'huobi':
209
227
  self.tasks_manage(self.htx_keepalive(), f"htx_keepalive-{self.ws_id}")
210
228
 
211
229
  self.operational_status = True
@@ -213,10 +231,10 @@ class UserWSS:
213
231
  self.tasks_manage(self._keepalive(), f"keepalive-{self.ws_id}")
214
232
  logger.info(f"UserWSS: 'logged in' for {self.ws_id}")
215
233
 
216
- async def request(self, method, _params=None, send_api_key=False, _signed=False):
234
+ async def request(self, method, _params=None, _signed=False):
217
235
  """
218
236
  Construct and handling request/response to WS API endpoint, use a description of the methods on
219
- https://developers.binance.com/docs/binance-trading-api/websocket_api#request-format
237
+ https://developers.binance.com/docs/binance-spot-api-docs/websocket-api
220
238
  :return: result: {} or None if temporary Out-of-Service state
221
239
  """
222
240
  if self.request_limit_reached:
@@ -235,7 +253,7 @@ class UserWSS:
235
253
  else:
236
254
  _id = ''.join(e for e in r_id if e.isalnum())[-ID_LEN_LIMIT.get(self.exchange, 64):]
237
255
  await self._ws.send(
238
- json.dumps(self.compose_request(_id, send_api_key, method, params, _signed))
256
+ json.dumps(self.compose_request(_id, method, params, _signed))
239
257
  )
240
258
  await asyncio.sleep(0)
241
259
  try:
@@ -243,8 +261,7 @@ class UserWSS:
243
261
  except asyncio.exceptions.TimeoutError:
244
262
  logger.warning(f"UserWSS: get response timeout error: {self.ws_id}")
245
263
  await self.stop()
246
- except asyncio.CancelledError:
247
- pass # Task cancellation should not be logged as an error
264
+ return None
248
265
  else:
249
266
  return res
250
267
 
@@ -256,10 +273,11 @@ class UserWSS:
256
273
  return self._response_pool.pop(_id)
257
274
  elif f"NoneResponse{self.ws_id}" in self._response_pool:
258
275
  return self._response_pool.pop(f"NoneResponse{self.ws_id}", None)
276
+ return None
259
277
 
260
- def compose_request(self, _id, send_api_key, method, params, signed):
278
+ def compose_request(self, _id, method, params, signed):
261
279
  if self.exchange == "binance":
262
- return self._compose_binance_request(_id, send_api_key, method, params, signed)
280
+ return self._compose_binance_request(_id, method, params, signed)
263
281
  elif self.exchange == "okx":
264
282
  return self._compose_okx_request(_id, method, params)
265
283
  elif self.exchange == "bitfinex":
@@ -275,18 +293,17 @@ class UserWSS:
275
293
  else:
276
294
  return {"cid": _id, "ch": method, "params": params}
277
295
 
278
- def _compose_binance_request(self, _id, send_api_key, method, params, signed):
279
- req = {"id": _id, "method": method}
280
- params = params or {}
281
- if send_api_key:
282
- params["apiKey"] = self._api_key
283
- if signed:
284
- params["timestamp"] = int(time.time() * 1000)
285
- payload = '&'.join(f"{key}={value}" for key, value in sorted(params.items()))
286
- params["signature"] = generate_signature('binance_ws', self._api_secret, payload)
287
- if params:
288
- req["params"] = params
289
- return req
296
+ def _compose_binance_request(self, _id, method, params, signed):
297
+ if method == CONST_WS_START:
298
+ return compose_binance_ws_auth(_id, self._api_key, self._api_secret)
299
+ else:
300
+ req = {"id": _id, "method": method}
301
+ params = params or {}
302
+ if signed:
303
+ params["timestamp"] = int(time.time() * 1000)
304
+ if params:
305
+ req["params"] = params
306
+ return req
290
307
 
291
308
  def _compose_okx_request(self, _id, method, params):
292
309
  if method == CONST_WS_START:
@@ -329,18 +346,6 @@ class UserWSS:
329
346
  logger.info(f"UserWSS order handling status restored for {self.ws_id}")
330
347
  await asyncio.sleep(interval)
331
348
 
332
- async def heartbeat(self, interval=60 * 30):
333
- params = {
334
- "listenKey": self._listen_key,
335
- }
336
- while self.operational_status is not None:
337
- await self.request(
338
- "userDataStream.ping",
339
- params,
340
- send_api_key=True,
341
- )
342
- await asyncio.sleep(interval)
343
-
344
349
  async def htx_keepalive(self, interval=60):
345
350
  await asyncio.sleep(interval * 10)
346
351
  while True:
@@ -395,9 +400,10 @@ class UserWSS:
395
400
  if msg.get('code') == 200 or msg.get('status') == 'ok':
396
401
  return msg
397
402
 
398
- await self.htx_error_handle(msg)
403
+ self.htx_error_handle(msg)
404
+ return None
399
405
 
400
- async def htx_error_handle(self, msg):
406
+ def htx_error_handle(self, msg):
401
407
  if msg.get('code') == 500:
402
408
  logger.warning(f"An issue occurred on exchange's side: {msg}")
403
409
  elif msg.get('code') in {429, 4000}:
@@ -474,13 +480,13 @@ class UserWSS:
474
480
  async def binance_error_handle(self, msg):
475
481
  error_msg = msg.get('error')
476
482
  logger.error(f"Malformed request: status: {error_msg}")
477
- if msg.get('status') == 403:
483
+ if msg.get('status') in (401, 403):
478
484
  await self.stop()
479
485
  if msg.get('status') in (418, 429):
480
486
  self._retry_after = error_msg.get('data', {}).get('retryAfter', int((time.time() + TIMEOUT) * 1000))
481
487
  self.request_limit_reached = True
482
488
 
483
- def _handle_rate_limits(self, rate_limits: []):
489
+ def _handle_rate_limits(self, rate_limits: list):
484
490
  def retry_after():
485
491
  return (int(time.time() / interval) + 1) * interval * 1000
486
492
  for rl in rate_limits:
@@ -521,11 +527,14 @@ class UserWSSession:
521
527
  trade_id: str,
522
528
  method: str,
523
529
  _params=None,
524
- send_api_key=False,
525
530
  _signed=False,
526
531
  ):
527
532
 
528
- ws_id = f"{self.exchange}-{trade_id}-{method}"
533
+ ws_id = f"{self.exchange}-{trade_id}"
534
+ if self.exchange in ('binance', 'vertex'):
535
+ ws_id = f"{ws_id}{'_ST' if _signed else '_SF'}"
536
+ else:
537
+ ws_id = f"{ws_id}-{method}"
529
538
 
530
539
  if ws_id in self.user_wss:
531
540
  user_wss = self.user_wss[ws_id]
@@ -557,8 +566,8 @@ class UserWSSession:
557
566
  duration += DELAY
558
567
 
559
568
  try:
560
- return await user_wss.request(method=method, _params=_params, send_api_key=send_api_key, _signed=_signed)
561
- except asyncio.CancelledError:
569
+ return await user_wss.request(method=method, _params=_params, _signed=_signed)
570
+ except KeyboardInterrupt:
562
571
  pass # Task cancellation should not be logged as an error
563
572
  except Exception as ex:
564
573
  logger.error(f"crypto_ws_api.ws_session.handle_request(): {ex}")
@@ -14,14 +14,15 @@ classifiers=["Programming Language :: Python :: 3",
14
14
  "Operating System :: Microsoft :: Windows",
15
15
  "Operating System :: MacOS"]
16
16
  dynamic = ["version", "description"]
17
- requires-python = ">=3.8"
17
+ requires-python = ">=3.10"
18
18
 
19
19
  dependencies = [
20
20
  "shortuuid~=1.0.13",
21
21
  "platformdirs==4.3.6",
22
22
  "toml~=0.10.2",
23
23
  "websockets==15.0.1",
24
- "ujson~=5.10.0"
24
+ "ujson~=5.10.0",
25
+ "cryptography~=44.0.2"
25
26
  ]
26
27
 
27
28
  [tool.flit.module]
@@ -2,4 +2,5 @@ shortuuid~=1.0.13
2
2
  platformdirs==4.3.6
3
3
  toml~=0.10.2
4
4
  websockets==15.0.1
5
- ujson~=5.10.0
5
+ ujson~=5.10.0
6
+ cryptography~=44.0.2
@@ -1,19 +0,0 @@
1
- # Parameters for crypto-ws-api connector
2
- # Copyright © 2023 Jerry Fedorenko aka VM
3
- # __version__ = "1.0.0"
4
-
5
- # region endpoint
6
- [endpoint]
7
- [endpoint.binance]
8
- ws_api = 'wss://ws-api.binance.com:443/ws-api/v3'
9
- ws_api_test = 'wss://testnet.binance.vision/ws-api/v3'
10
- # endregion
11
-
12
- # region Binance accounts
13
- [[accounts]]
14
- exchange = 'binance'
15
- name = 'Demo - Binance'
16
- api_key = '*********** Place API key there ************'
17
- api_secret = '*********** Place secret API key there ************'
18
- test_net = true
19
- # endregion
File without changes