exchanges-wrapper 2.1.33__tar.gz → 2.1.34__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.
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/PKG-INFO +13 -10
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/README.md +9 -6
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/client.py +4 -32
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/exch_srv.py +1 -0
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/exch_srv_cfg.toml.template +22 -26
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/http_client.py +0 -1
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/lib.py +2 -5
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/parsers/bitfinex.py +18 -32
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/parsers/bybit.py +2 -3
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/parsers/huobi.py +4 -4
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/parsers/okx.py +11 -24
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/web_sockets.py +27 -29
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/pyproject.toml +3 -3
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/proto/martin.proto +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exchanges-wrapper
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.34
|
|
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
|
-
Requires-Python: >=3.
|
|
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
|
|
@@ -12,10 +12,10 @@ Classifier: Operating System :: Unix
|
|
|
12
12
|
Classifier: Operating System :: Microsoft :: Windows
|
|
13
13
|
Classifier: Operating System :: MacOS
|
|
14
14
|
License-File: LICENSE.md
|
|
15
|
-
Requires-Dist: crypto-ws-api==2.0.
|
|
15
|
+
Requires-Dist: crypto-ws-api==2.0.20
|
|
16
16
|
Requires-Dist: pyotp==2.9.0
|
|
17
17
|
Requires-Dist: simplejson==3.20.1
|
|
18
|
-
Requires-Dist: aiohttp~=3.11.
|
|
18
|
+
Requires-Dist: aiohttp~=3.11.18
|
|
19
19
|
Requires-Dist: expiringdict~=1.2.2
|
|
20
20
|
Requires-Dist: betterproto==2.0.0b7
|
|
21
21
|
Requires-Dist: grpclib~=0.4.7
|
|
@@ -35,7 +35,8 @@ Project-URL: Source, https://github.com/DogsTailFarmer/exchanges-wrapper
|
|
|
35
35
|
<a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_exchanges-wrapper" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_exchanges-wrapper&metric=alert_status"/></a>
|
|
36
36
|
<a href="https://pepy.tech/project/exchanges-wrapper" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/exchanges-wrapper/month"/></a>
|
|
37
37
|
***
|
|
38
|
-
|
|
38
|
+
On `Binance`: now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
|
|
39
|
+
From `2.1.34` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
|
|
39
40
|
***
|
|
40
41
|
|
|
41
42
|
## exchanges-wrapper vs [binance.py](https://github.com/Th0rgal/binance.py)
|
|
@@ -75,11 +76,13 @@ at real bidding. First, run applications on the [Binance Spot Test Network](http
|
|
|
75
76
|
|
|
76
77
|
## Get started
|
|
77
78
|
### Prepare exchange account
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
Create account on [Binance](https://accounts.binance.com/en/register?ref=FXQ6HY5O) and get 10% discount on all trading fee
|
|
80
|
+
Create account on [HTX](https://www.htx.com/invite/en-us/1f?invite_code=9uaw3223) From every invitee, win a Mystery Box (up to 1,500 USDT) and 30% of their trading fees.
|
|
81
|
+
Create account on [Bitfinex](https://www.bitfinex.com/sign-up?refcode=v_4az2nCP) and get 6% rebate fee
|
|
82
|
+
Create account on [OKX](https://okx.com/join/2607649) and will be in for the chance to earn up to 100 USDT
|
|
83
|
+
Create account on [Bybit](https://www.bybit.com/invite?ref=9KEW1K) and get exclusive referral rewards
|
|
84
|
+
|
|
85
|
+
Also, you can start strategy on [Hetzner](https://hetzner.cloud/?ref=uFdrF8nsdGMc) cloud VPS only for 4.75 € per month
|
|
83
86
|
|
|
84
87
|
* For test purpose log in at [Binance Spot Test Network](https://testnet.binance.vision/)
|
|
85
88
|
* Create API Key
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
<a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_exchanges-wrapper" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_exchanges-wrapper&metric=alert_status"/></a>
|
|
13
13
|
<a href="https://pepy.tech/project/exchanges-wrapper" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/exchanges-wrapper/month"/></a>
|
|
14
14
|
***
|
|
15
|
-
|
|
15
|
+
On `Binance`: now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
|
|
16
|
+
From `2.1.34` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
|
|
16
17
|
***
|
|
17
18
|
|
|
18
19
|
## exchanges-wrapper vs [binance.py](https://github.com/Th0rgal/binance.py)
|
|
@@ -52,11 +53,13 @@ at real bidding. First, run applications on the [Binance Spot Test Network](http
|
|
|
52
53
|
|
|
53
54
|
## Get started
|
|
54
55
|
### Prepare exchange account
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
Create account on [Binance](https://accounts.binance.com/en/register?ref=FXQ6HY5O) and get 10% discount on all trading fee
|
|
57
|
+
Create account on [HTX](https://www.htx.com/invite/en-us/1f?invite_code=9uaw3223) From every invitee, win a Mystery Box (up to 1,500 USDT) and 30% of their trading fees.
|
|
58
|
+
Create account on [Bitfinex](https://www.bitfinex.com/sign-up?refcode=v_4az2nCP) and get 6% rebate fee
|
|
59
|
+
Create account on [OKX](https://okx.com/join/2607649) and will be in for the chance to earn up to 100 USDT
|
|
60
|
+
Create account on [Bybit](https://www.bybit.com/invite?ref=9KEW1K) and get exclusive referral rewards
|
|
61
|
+
|
|
62
|
+
Also, you can start strategy on [Hetzner](https://hetzner.cloud/?ref=uFdrF8nsdGMc) cloud VPS only for 4.75 € per month
|
|
60
63
|
|
|
61
64
|
* For test purpose log in at [Binance Spot Test Network](https://testnet.binance.vision/)
|
|
62
65
|
* Create API Key
|
|
@@ -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.
|
|
15
|
+
__version__ = "2.1.34"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -30,7 +30,6 @@ from crypto_ws_api.ws_session import UserWSSession
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
31
31
|
|
|
32
32
|
STATUS_TIMEOUT = 5 # sec, also use for lifetime limit for inactive order (Bitfinex) as 60 * STATUS_TIMEOUT
|
|
33
|
-
USER_DATA_STREAM = "/api/v3/userDataStream"
|
|
34
33
|
ORDER_ENDPOINT = "/api/v3/order"
|
|
35
34
|
|
|
36
35
|
def fallback_warning(exchange, symbol=None):
|
|
@@ -161,7 +160,7 @@ class Client:
|
|
|
161
160
|
logger.info(f"Start '{self.exchange}' user events listener for {_trade_id}")
|
|
162
161
|
user_data_stream = None
|
|
163
162
|
if self.exchange == 'binance':
|
|
164
|
-
user_data_stream =
|
|
163
|
+
user_data_stream = UserEventsDataStream(self, self.endpoint_ws_api, self.exchange, _trade_id)
|
|
165
164
|
elif self.exchange == 'bitfinex':
|
|
166
165
|
user_data_stream = BfxPrivateEventsDataStream(self, self.endpoint_ws_auth, self.exchange, _trade_id)
|
|
167
166
|
elif self.exchange == 'huobi':
|
|
@@ -185,7 +184,7 @@ class Client:
|
|
|
185
184
|
if not timeout:
|
|
186
185
|
logger.warning(f"{self.exchange} user WSS start timeout reached for {_trade_id}")
|
|
187
186
|
break
|
|
188
|
-
await asyncio.sleep(0.
|
|
187
|
+
await asyncio.sleep(0.1)
|
|
189
188
|
|
|
190
189
|
async def start_market_events_listener(self, _trade_id):
|
|
191
190
|
_events = self.events.registered_streams.get(self.exchange, {}).get(_trade_id, set())
|
|
@@ -525,6 +524,7 @@ class Client:
|
|
|
525
524
|
self.ledgers_id.pop(0)
|
|
526
525
|
balances.append(i[_id])
|
|
527
526
|
return balances
|
|
527
|
+
return None
|
|
528
528
|
|
|
529
529
|
# https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#recent-trades-list
|
|
530
530
|
async def fetch_recent_trades_list(self, symbol, limit=500):
|
|
@@ -773,6 +773,7 @@ class Client:
|
|
|
773
773
|
params=params,
|
|
774
774
|
signed=True,
|
|
775
775
|
)
|
|
776
|
+
return None
|
|
776
777
|
|
|
777
778
|
async def fetch_api_info(self):
|
|
778
779
|
res, _ = await self.http.send_api_call("/v5/user/query-api", signed=True)
|
|
@@ -816,7 +817,6 @@ class Client:
|
|
|
816
817
|
trade_id,
|
|
817
818
|
"order.place",
|
|
818
819
|
_params=params,
|
|
819
|
-
send_api_key=True,
|
|
820
820
|
_signed=True
|
|
821
821
|
)
|
|
822
822
|
if binance_res is None:
|
|
@@ -947,7 +947,6 @@ class Client:
|
|
|
947
947
|
trade_id,
|
|
948
948
|
"order.status",
|
|
949
949
|
_params=params,
|
|
950
|
-
send_api_key=True,
|
|
951
950
|
_signed=True,
|
|
952
951
|
)
|
|
953
952
|
if b_res is None:
|
|
@@ -1050,7 +1049,6 @@ class Client:
|
|
|
1050
1049
|
trade_id,
|
|
1051
1050
|
"order.cancel",
|
|
1052
1051
|
_params=params,
|
|
1053
|
-
send_api_key=True,
|
|
1054
1052
|
_signed=True
|
|
1055
1053
|
)
|
|
1056
1054
|
if binance_res is None:
|
|
@@ -1152,7 +1150,6 @@ class Client:
|
|
|
1152
1150
|
trade_id,
|
|
1153
1151
|
"openOrders.cancelAll",
|
|
1154
1152
|
_params=params,
|
|
1155
|
-
send_api_key=True,
|
|
1156
1153
|
_signed=True
|
|
1157
1154
|
)
|
|
1158
1155
|
if binance_res is None:
|
|
@@ -1275,7 +1272,6 @@ class Client:
|
|
|
1275
1272
|
trade_id,
|
|
1276
1273
|
"openOrders.status",
|
|
1277
1274
|
_params=params,
|
|
1278
|
-
send_api_key=True,
|
|
1279
1275
|
_signed=True
|
|
1280
1276
|
)
|
|
1281
1277
|
if binance_res is None:
|
|
@@ -1522,7 +1518,6 @@ class Client:
|
|
|
1522
1518
|
trade_id,
|
|
1523
1519
|
"account.status",
|
|
1524
1520
|
_params=params,
|
|
1525
|
-
send_api_key=True,
|
|
1526
1521
|
_signed=True
|
|
1527
1522
|
)
|
|
1528
1523
|
if binance_res is None:
|
|
@@ -1744,7 +1739,6 @@ class Client:
|
|
|
1744
1739
|
trade_id,
|
|
1745
1740
|
"myTrades",
|
|
1746
1741
|
_params=params,
|
|
1747
|
-
send_api_key=True,
|
|
1748
1742
|
_signed=True
|
|
1749
1743
|
)
|
|
1750
1744
|
if binance_res is None:
|
|
@@ -1839,25 +1833,3 @@ class Client:
|
|
|
1839
1833
|
return b_res
|
|
1840
1834
|
|
|
1841
1835
|
# endregion
|
|
1842
|
-
|
|
1843
|
-
# USER DATA STREAM ENDPOINTS
|
|
1844
|
-
|
|
1845
|
-
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#create-a-listenkey
|
|
1846
|
-
async def create_listen_key(self):
|
|
1847
|
-
return await self.http.send_api_call(USER_DATA_STREAM, "POST")
|
|
1848
|
-
|
|
1849
|
-
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#close-a-listenkey
|
|
1850
|
-
async def keep_alive_listen_key(self, listen_key):
|
|
1851
|
-
if not listen_key:
|
|
1852
|
-
raise ValueError("This query requires a listen_key.")
|
|
1853
|
-
return await self.http.send_api_call(
|
|
1854
|
-
USER_DATA_STREAM, "PUT", params={"listenKey": listen_key}
|
|
1855
|
-
)
|
|
1856
|
-
|
|
1857
|
-
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#close-a-listenkey
|
|
1858
|
-
async def close_listen_key(self, listen_key):
|
|
1859
|
-
if not listen_key:
|
|
1860
|
-
raise ValueError("This query requires a listen_key.")
|
|
1861
|
-
return await self.http.send_api_call(
|
|
1862
|
-
USER_DATA_STREAM, "DELETE", params={"listenKey": listen_key}
|
|
1863
|
-
)
|
{exchanges_wrapper-2.1.33 → exchanges_wrapper-2.1.34}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
# Parameters for exchanges-wrapper REST API Server exch_srv.py
|
|
2
2
|
# Copyright © 2021-2025 Jerry Fedorenko aka VM
|
|
3
|
-
# __version__ = "2.1.
|
|
3
|
+
# __version__ = "2.1.34"
|
|
4
|
+
|
|
4
5
|
# region endpoint
|
|
5
6
|
[endpoint]
|
|
6
7
|
[endpoint.binance]
|
|
7
8
|
api_public = 'https://api.binance.com'
|
|
8
9
|
api_auth = 'https://api.binance.com'
|
|
9
10
|
ws_public = 'wss://stream.binance.com:9443'
|
|
10
|
-
ws_auth = 'wss://stream.binance.com:9443'
|
|
11
11
|
api_test = 'https://testnet.binance.vision'
|
|
12
|
-
ws_test = 'wss://testnet.binance.vision/ws'
|
|
13
12
|
ws_api = 'wss://ws-api.binance.com:443/ws-api/v3'
|
|
14
13
|
ws_api_test = 'wss://testnet.binance.vision/ws-api/v3'
|
|
15
14
|
|
|
16
|
-
[endpoint.binance_us]
|
|
17
|
-
api_public = 'https://api.binance.us'
|
|
18
|
-
api_auth = 'https://api.binance.us'
|
|
19
|
-
ws_public = 'wss://stream.binance.us:9443'
|
|
20
|
-
ws_auth = 'wss://stream.binance.us:9443'
|
|
21
|
-
api_test = 'https://testnet.binance.vision'
|
|
22
|
-
ws_test = 'wss://testnet.binance.vision/ws'
|
|
23
|
-
ws_api = 'wss://ws-api.binance.us:443/ws-api/v3'
|
|
24
|
-
ws_api_test = 'wss://testnet.binance.vision/ws-api/v3'
|
|
25
|
-
|
|
26
15
|
[endpoint.bitfinex]
|
|
27
16
|
api_public = 'https://api-pub.bitfinex.com'
|
|
28
17
|
api_auth = 'https://api.bitfinex.com'
|
|
@@ -58,35 +47,42 @@
|
|
|
58
47
|
# endregion
|
|
59
48
|
|
|
60
49
|
# region Binance.com accounts
|
|
50
|
+
# How to Generate an Ed25519 Key Pair and Register then on Binance:
|
|
51
|
+
# https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a
|
|
61
52
|
[[accounts]]
|
|
62
53
|
exchange = 'binance'
|
|
63
54
|
name = 'Demo - Binance'
|
|
64
|
-
api_key = '*********** Place API key there ************'
|
|
65
|
-
api_secret =
|
|
55
|
+
api_key = '*********** Place Ed25519 API key there ************'
|
|
56
|
+
api_secret = """
|
|
57
|
+
-----BEGIN PRIVATE KEY-----
|
|
58
|
+
*********** Place PRIVATE API key there ************
|
|
59
|
+
-----END PRIVATE KEY-----
|
|
60
|
+
"""
|
|
66
61
|
test_net = true
|
|
67
62
|
|
|
68
63
|
[[accounts]]
|
|
69
64
|
exchange = 'binance'
|
|
70
65
|
name = 'BinanceSub1'
|
|
71
|
-
api_key = '*********** Place API key there ************'
|
|
72
|
-
api_secret =
|
|
66
|
+
api_key = '*********** Place Ed25519 API key there ************'
|
|
67
|
+
api_secret = """
|
|
68
|
+
-----BEGIN PRIVATE KEY-----
|
|
69
|
+
*********** Place PRIVATE API key there ************
|
|
70
|
+
-----END PRIVATE KEY-----
|
|
71
|
+
"""
|
|
73
72
|
test_net = false
|
|
74
73
|
|
|
75
74
|
[[accounts]]
|
|
76
75
|
exchange = 'binance'
|
|
77
76
|
name = 'BinanceSub2'
|
|
78
|
-
api_key = '*********** Place API key there ************'
|
|
79
|
-
api_secret =
|
|
77
|
+
api_key = '*********** Place Ed25519 API key there ************'
|
|
78
|
+
api_secret = """
|
|
79
|
+
-----BEGIN PRIVATE KEY-----
|
|
80
|
+
*********** Place PRIVATE API key there ************
|
|
81
|
+
-----END PRIVATE KEY-----
|
|
82
|
+
"""
|
|
80
83
|
master_email = 'sub1@mail.com' # If set, 'BinanceSub1' use for collecting assets instead of Main
|
|
81
84
|
test_net = false
|
|
82
85
|
|
|
83
|
-
# Binance.us accounts
|
|
84
|
-
[[accounts]]
|
|
85
|
-
exchange = 'binance_us'
|
|
86
|
-
name = 'Binance US'
|
|
87
|
-
api_key = '*********** Place API key there ************'
|
|
88
|
-
api_secret = '*********** Place secret API key there ************'
|
|
89
|
-
test_net = false
|
|
90
86
|
# endregion
|
|
91
87
|
|
|
92
88
|
# region Bitfinex accounts
|
|
@@ -45,7 +45,6 @@ class HttpClient:
|
|
|
45
45
|
self.rate_limit_handler = RateLimitHandler() if self.exchange == 'bybit' else None
|
|
46
46
|
|
|
47
47
|
def declare_exchanges_implementation(self):
|
|
48
|
-
# noinspection PyTypeChecker
|
|
49
48
|
self.ex_imps = {
|
|
50
49
|
'binance': self._binance_request,
|
|
51
50
|
'bitfinex': self._bitfinex_request,
|
|
@@ -77,9 +77,6 @@ def get_account(account_name: str) -> dict:
|
|
|
77
77
|
ws_public = get_ws_public(endpoint, exchange, test_net)
|
|
78
78
|
ws_api, ws_auth = get_ws_api_auth(endpoint, exchange, test_net)
|
|
79
79
|
|
|
80
|
-
if exchange == 'binance_us':
|
|
81
|
-
exchange = 'binance'
|
|
82
|
-
|
|
83
80
|
return {
|
|
84
81
|
'exchange': exchange,
|
|
85
82
|
'sub_account': sub_account,
|
|
@@ -96,7 +93,7 @@ def get_account(account_name: str) -> dict:
|
|
|
96
93
|
'master_name': master_name,
|
|
97
94
|
'two_fa': account.get('two_fa'),
|
|
98
95
|
'ws_api': ws_api,
|
|
99
|
-
|
|
96
|
+
}
|
|
100
97
|
return {}
|
|
101
98
|
|
|
102
99
|
def get_ws_add_on(endpoint, exchange):
|
|
@@ -118,7 +115,7 @@ def get_ws_api_auth(endpoint, exchange, test_net):
|
|
|
118
115
|
if exchange == 'bitfinex':
|
|
119
116
|
ws_api = ws_auth = endpoint['ws_auth']
|
|
120
117
|
else:
|
|
121
|
-
ws_auth = endpoint
|
|
118
|
+
ws_auth = endpoint.get('ws_test') if test_net else endpoint.get('ws_auth')
|
|
122
119
|
ws_api = endpoint.get('ws_api_test') if test_net else endpoint.get('ws_api')
|
|
123
120
|
if exchange == 'okx':
|
|
124
121
|
ws_api = ws_auth
|
|
@@ -50,7 +50,7 @@ class OrderBook:
|
|
|
50
50
|
return self
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def get_symbols(symbols_details:
|
|
53
|
+
def get_symbols(symbols_details: list, symbol) -> str:
|
|
54
54
|
symbols = []
|
|
55
55
|
res = ",t"
|
|
56
56
|
for symbol_details in symbols_details:
|
|
@@ -86,7 +86,7 @@ def symbol_name(_pair: str) -> ():
|
|
|
86
86
|
return pair, base_asset, quote_asset
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
def exchange_info(symbols_details:
|
|
89
|
+
def exchange_info(symbols_details: list, tickers: list, symbol_t) -> dict:
|
|
90
90
|
symbols = []
|
|
91
91
|
symbols_price = {
|
|
92
92
|
pair[0].replace(':', '').upper()[1:]: pair[7] for pair in tickers
|
|
@@ -159,7 +159,7 @@ def exchange_info(symbols_details: [], tickers: [], symbol_t) -> {}:
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
|
|
162
|
-
def
|
|
162
|
+
def account_balances(res: list) -> dict:
|
|
163
163
|
balances = []
|
|
164
164
|
for balance in res:
|
|
165
165
|
if balance[0] == 'exchange':
|
|
@@ -172,23 +172,10 @@ def account_information(res: []) -> {}:
|
|
|
172
172
|
"locked": locked,
|
|
173
173
|
}
|
|
174
174
|
balances.append(_binance_res)
|
|
175
|
+
return {"balances": balances}
|
|
175
176
|
|
|
176
|
-
return {
|
|
177
|
-
"makerCommission": 0,
|
|
178
|
-
"takerCommission": 0,
|
|
179
|
-
"buyerCommission": 0,
|
|
180
|
-
"sellerCommission": 0,
|
|
181
|
-
"canTrade": True,
|
|
182
|
-
"canWithdraw": False,
|
|
183
|
-
"canDeposit": False,
|
|
184
|
-
"updateTime": int(time.time() * 1000),
|
|
185
|
-
"accountType": "SPOT",
|
|
186
|
-
"balances": balances,
|
|
187
|
-
"permissions": ["SPOT"],
|
|
188
|
-
}
|
|
189
177
|
|
|
190
|
-
|
|
191
|
-
def order(res: [], response_type=None, cancelled=False) -> {}:
|
|
178
|
+
def order(res: list, response_type=None, cancelled=False) -> dict:
|
|
192
179
|
# print(f"order.order: {res}")
|
|
193
180
|
symbol = res[3][1:].replace(':', '')
|
|
194
181
|
order_id = res[0]
|
|
@@ -275,7 +262,7 @@ def order(res: [], response_type=None, cancelled=False) -> {}:
|
|
|
275
262
|
}
|
|
276
263
|
|
|
277
264
|
|
|
278
|
-
def orders(res:
|
|
265
|
+
def orders(res: list, response_type=None, cancelled=False) -> list:
|
|
279
266
|
binance_orders = []
|
|
280
267
|
for _order in res:
|
|
281
268
|
i_order = order(_order, response_type=response_type, cancelled=cancelled)
|
|
@@ -283,7 +270,7 @@ def orders(res: [], response_type=None, cancelled=False) -> []:
|
|
|
283
270
|
return binance_orders
|
|
284
271
|
|
|
285
272
|
|
|
286
|
-
def order_book(res:
|
|
273
|
+
def order_book(res: list) -> dict:
|
|
287
274
|
binance_order_book = {"lastUpdateId": int(time.time() * 1000)}
|
|
288
275
|
bids = []
|
|
289
276
|
asks = []
|
|
@@ -297,7 +284,7 @@ def order_book(res: []) -> {}:
|
|
|
297
284
|
return binance_order_book
|
|
298
285
|
|
|
299
286
|
|
|
300
|
-
def ticker_price_change_statistics(res:
|
|
287
|
+
def ticker_price_change_statistics(res: list, symbol):
|
|
301
288
|
return {
|
|
302
289
|
"symbol": symbol,
|
|
303
290
|
"priceChange": str(res[4]),
|
|
@@ -323,7 +310,7 @@ def ticker_price_change_statistics(res: [], symbol):
|
|
|
323
310
|
}
|
|
324
311
|
|
|
325
312
|
|
|
326
|
-
def fetch_symbol_price_ticker(res:
|
|
313
|
+
def fetch_symbol_price_ticker(res: list, symbol) -> dict:
|
|
327
314
|
return {
|
|
328
315
|
"symbol": symbol,
|
|
329
316
|
"price": str(res[6]),
|
|
@@ -348,7 +335,7 @@ def interval(_interval: str) -> int:
|
|
|
348
335
|
return resolution.get(_interval, 0)
|
|
349
336
|
|
|
350
337
|
|
|
351
|
-
def klines(res:
|
|
338
|
+
def klines(res: list, _interval: str) -> list:
|
|
352
339
|
binance_klines = []
|
|
353
340
|
for i in res:
|
|
354
341
|
start_time = i[0]
|
|
@@ -370,7 +357,7 @@ def klines(res: [], _interval: str) -> []:
|
|
|
370
357
|
return binance_klines
|
|
371
358
|
|
|
372
359
|
|
|
373
|
-
def candle(res:
|
|
360
|
+
def candle(res: list, symbol: str = None, ch_type: str = None) -> dict:
|
|
374
361
|
symbol = symbol[1:].replace(':', '')
|
|
375
362
|
start_time = res[0]
|
|
376
363
|
_interval = ch_type.split('_')[1]
|
|
@@ -403,7 +390,7 @@ def candle(res: [], symbol: str = None, ch_type: str = None) -> {}:
|
|
|
403
390
|
}
|
|
404
391
|
|
|
405
392
|
|
|
406
|
-
def account_trade_list(res:
|
|
393
|
+
def account_trade_list(res: list, order_id=None) -> list:
|
|
407
394
|
binance_trade_list = []
|
|
408
395
|
for trade in res:
|
|
409
396
|
if order_id is None or order_id == trade[3]:
|
|
@@ -429,7 +416,7 @@ def account_trade_list(res: [], order_id=None) -> []:
|
|
|
429
416
|
return binance_trade_list
|
|
430
417
|
|
|
431
418
|
|
|
432
|
-
def ticker(res:
|
|
419
|
+
def ticker(res: list, symbol: str = None) -> dict:
|
|
433
420
|
_symbol = symbol[1:].replace(':', '').lower()
|
|
434
421
|
return {
|
|
435
422
|
'stream': f"{_symbol}@miniTicker",
|
|
@@ -447,7 +434,7 @@ def ticker(res: [], symbol: str = None) -> {}:
|
|
|
447
434
|
}
|
|
448
435
|
|
|
449
436
|
|
|
450
|
-
def on_funds_update(res:
|
|
437
|
+
def on_funds_update(res: list) -> dict:
|
|
451
438
|
binance_funds = {
|
|
452
439
|
'e': 'outboundAccountPosition',
|
|
453
440
|
'E': int(time.time() * 1000),
|
|
@@ -471,7 +458,7 @@ def on_funds_update(res: []) -> {}:
|
|
|
471
458
|
return binance_funds
|
|
472
459
|
|
|
473
460
|
|
|
474
|
-
def on_balance_update(res:
|
|
461
|
+
def on_balance_update(res: list) -> dict:
|
|
475
462
|
return {
|
|
476
463
|
'e': 'balanceUpdate',
|
|
477
464
|
'E': res[3],
|
|
@@ -481,7 +468,7 @@ def on_balance_update(res: []) -> {}:
|
|
|
481
468
|
}
|
|
482
469
|
|
|
483
470
|
|
|
484
|
-
def on_order_update(res:
|
|
471
|
+
def on_order_update(res: list, _order: {}) -> dict:
|
|
485
472
|
# logger.info(f"on_order_update.res: {res}, order: {_order}")
|
|
486
473
|
side = 'BUY' if res[7] > 0 else 'SELL'
|
|
487
474
|
#
|
|
@@ -548,7 +535,7 @@ def on_order_update(res: [], _order: {}) -> {}:
|
|
|
548
535
|
}
|
|
549
536
|
|
|
550
537
|
|
|
551
|
-
def on_order_trade(_order:
|
|
538
|
+
def on_order_trade(_order: dict) -> dict:
|
|
552
539
|
# logger.info(f"on_order_trade._order: {_order}")
|
|
553
540
|
event = _order['lastEvent']
|
|
554
541
|
orig_qty = _order['origQty']
|
|
@@ -604,7 +591,7 @@ def on_order_trade(_order: {}) -> {}:
|
|
|
604
591
|
}
|
|
605
592
|
|
|
606
593
|
|
|
607
|
-
def funding_wallet(res:
|
|
594
|
+
def funding_wallet(res: list) -> list:
|
|
608
595
|
balances = []
|
|
609
596
|
for balance in res:
|
|
610
597
|
if balance[0] in ('exchange', 'funding'):
|
|
@@ -621,7 +608,6 @@ def funding_wallet(res: []) -> []:
|
|
|
621
608
|
"btcValuation": "0.0",
|
|
622
609
|
}
|
|
623
610
|
balances.append(_binance_res)
|
|
624
|
-
|
|
625
611
|
return balances
|
|
626
612
|
|
|
627
613
|
|
|
@@ -17,7 +17,6 @@ class OrderBook:
|
|
|
17
17
|
self.asks = {i[0]: i[1] for i in snapshot["a"]}
|
|
18
18
|
self.bids = {i[0]: i[1] for i in snapshot["b"]}
|
|
19
19
|
|
|
20
|
-
# noinspection PyTypeChecker
|
|
21
20
|
def get_book(self) -> dict:
|
|
22
21
|
return {
|
|
23
22
|
'stream': f"{self.symbol}@depth5",
|
|
@@ -541,7 +540,7 @@ def on_balance_update(data_in: list, ts: str, symbol: str, mode: str, uid=None)
|
|
|
541
540
|
return data_out
|
|
542
541
|
|
|
543
542
|
|
|
544
|
-
def funding_wallet(res:
|
|
543
|
+
def funding_wallet(res: list) -> list:
|
|
545
544
|
balances = []
|
|
546
545
|
for balance in res:
|
|
547
546
|
_free = Decimal(balance["transferBalance"])
|
|
@@ -559,7 +558,7 @@ def funding_wallet(res: []) -> []:
|
|
|
559
558
|
return balances
|
|
560
559
|
|
|
561
560
|
|
|
562
|
-
def order_trade_list(res:
|
|
561
|
+
def order_trade_list(res: list) -> list:
|
|
563
562
|
trades = []
|
|
564
563
|
for trade in reversed(res):
|
|
565
564
|
trades.append(
|
|
@@ -79,7 +79,7 @@ def exchange_info(server_time: int, _symbol_params) -> {}:
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def orders(res:
|
|
82
|
+
def orders(res: list, response_type=None) -> list:
|
|
83
83
|
binance_orders = []
|
|
84
84
|
for _order in res:
|
|
85
85
|
i_order = order(_order, response_type=response_type)
|
|
@@ -341,7 +341,7 @@ def interval2value(_interval) -> int:
|
|
|
341
341
|
return resolution.get(_interval, 0)
|
|
342
342
|
|
|
343
343
|
|
|
344
|
-
def klines(res:
|
|
344
|
+
def klines(res: list, _interval: str) -> list:
|
|
345
345
|
binance_klines = []
|
|
346
346
|
for i in res:
|
|
347
347
|
start_time = i.get('id') * 1000
|
|
@@ -363,7 +363,7 @@ def klines(res: [], _interval: str) -> []:
|
|
|
363
363
|
return binance_klines
|
|
364
364
|
|
|
365
365
|
|
|
366
|
-
def candle(res:
|
|
366
|
+
def candle(res: dict, symbol: str = None, ch_type: str = None) -> {}:
|
|
367
367
|
tick = res.get('tick')
|
|
368
368
|
start_time = tick.get('id')
|
|
369
369
|
_interval = ch_type.split('_')[1]
|
|
@@ -473,7 +473,7 @@ def on_order_update(_order: {}) -> {}:
|
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
|
|
476
|
-
def account_trade_list(res:
|
|
476
|
+
def account_trade_list(res: list) -> list:
|
|
477
477
|
binance_trade_list = []
|
|
478
478
|
for trade in res:
|
|
479
479
|
price = trade['price']
|
|
@@ -8,12 +8,13 @@ import logging
|
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def fetch_server_time(res:
|
|
11
|
+
def fetch_server_time(res: list) -> dict | None:
|
|
12
12
|
if res:
|
|
13
13
|
return {'serverTime': int(res[0].get('ts'))}
|
|
14
|
+
return None
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def exchange_info(server_time: int, trading_symbol:
|
|
17
|
+
def exchange_info(server_time: int, trading_symbol: list, tickers: list, symbol_t) -> {}:
|
|
17
18
|
symbols = []
|
|
18
19
|
symbols_price = {}
|
|
19
20
|
for pair in tickers:
|
|
@@ -88,7 +89,7 @@ def exchange_info(server_time: int, trading_symbol: [], tickers: [], symbol_t) -
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
|
|
91
|
-
def orders(res:
|
|
92
|
+
def orders(res: list, response_type=None) -> list:
|
|
92
93
|
binance_orders = []
|
|
93
94
|
for _order in res:
|
|
94
95
|
i_order = order(_order, response_type=response_type)
|
|
@@ -201,7 +202,7 @@ def place_order_response(res: {}, req: {}) -> {}:
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
|
|
204
|
-
def
|
|
205
|
+
def account_balances(res: list) -> dict:
|
|
205
206
|
balances = []
|
|
206
207
|
for asset in res:
|
|
207
208
|
_binance_res = {
|
|
@@ -210,24 +211,10 @@ def account_information(res: [], u_time: str) -> {}:
|
|
|
210
211
|
"locked": asset.get('frozenBal'),
|
|
211
212
|
}
|
|
212
213
|
balances.append(_binance_res)
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
"makerCommission": 0,
|
|
216
|
-
"takerCommission": 0,
|
|
217
|
-
"buyerCommission": 0,
|
|
218
|
-
"sellerCommission": 0,
|
|
219
|
-
"canTrade": True,
|
|
220
|
-
"canWithdraw": False,
|
|
221
|
-
"canDeposit": False,
|
|
222
|
-
"brokered": False,
|
|
223
|
-
"updateTime": int(u_time),
|
|
224
|
-
"accountType": "SPOT",
|
|
225
|
-
"balances": balances,
|
|
226
|
-
"permissions": ["SPOT"],
|
|
227
|
-
}
|
|
214
|
+
return {"balances": balances}
|
|
228
215
|
|
|
229
216
|
|
|
230
|
-
def order_book(res:
|
|
217
|
+
def order_book(res: dict) -> dict:
|
|
231
218
|
asks = []
|
|
232
219
|
bids = []
|
|
233
220
|
binance_order_book = {"lastUpdateId": int(res.get('ts'))}
|
|
@@ -313,7 +300,7 @@ def interval(_interval: str) -> str:
|
|
|
313
300
|
return resolution.get(_interval, 0)
|
|
314
301
|
|
|
315
302
|
|
|
316
|
-
def klines(res:
|
|
303
|
+
def klines(res: list, _interval: str) -> list:
|
|
317
304
|
binance_klines = []
|
|
318
305
|
for i in res:
|
|
319
306
|
start_time = int(i[0])
|
|
@@ -352,7 +339,7 @@ def interval2value(_interval: str) -> int:
|
|
|
352
339
|
return resolution.get(_interval, 0)
|
|
353
340
|
|
|
354
341
|
|
|
355
|
-
def candle(res:
|
|
342
|
+
def candle(res: list, symbol: str = None, ch_type: str = None) -> {}:
|
|
356
343
|
symbol = symbol.replace('-', '').lower()
|
|
357
344
|
start_time = int(res[0])
|
|
358
345
|
_interval = ch_type.replace('kline_', '')
|
|
@@ -491,7 +478,7 @@ def on_balance_update(res: list, buffer: dict, transfer: bool) -> ():
|
|
|
491
478
|
return res_diff, buffer
|
|
492
479
|
|
|
493
480
|
|
|
494
|
-
def funding_wallet(res:
|
|
481
|
+
def funding_wallet(res: list) -> list:
|
|
495
482
|
balances = []
|
|
496
483
|
for balance in res:
|
|
497
484
|
_binance_res = {
|
|
@@ -506,7 +493,7 @@ def funding_wallet(res: []) -> []:
|
|
|
506
493
|
return balances
|
|
507
494
|
|
|
508
495
|
|
|
509
|
-
def order_trade_list(res:
|
|
496
|
+
def order_trade_list(res: list) -> list:
|
|
510
497
|
binance_trade_list = []
|
|
511
498
|
for trade in res:
|
|
512
499
|
price = trade['fillPx']
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import asyncio
|
|
3
|
+
# noinspection PyPackageRequirements
|
|
3
4
|
import ujson as json
|
|
4
5
|
import logging.handlers
|
|
5
6
|
from pathlib import Path
|
|
@@ -8,14 +9,16 @@ from decimal import Decimal
|
|
|
8
9
|
import gzip
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
|
|
12
|
+
# noinspection PyPackageRequirements
|
|
11
13
|
from websockets.asyncio.client import connect
|
|
14
|
+
# noinspection PyPackageRequirements
|
|
12
15
|
from websockets import ConnectionClosed
|
|
13
16
|
|
|
14
17
|
import exchanges_wrapper.parsers.bitfinex as bfx
|
|
15
18
|
import exchanges_wrapper.parsers.huobi as hbp
|
|
16
19
|
import exchanges_wrapper.parsers.okx as okx
|
|
17
20
|
import exchanges_wrapper.parsers.bybit as bbt
|
|
18
|
-
from crypto_ws_api.ws_session import generate_signature, compose_htx_ws_auth
|
|
21
|
+
from crypto_ws_api.ws_session import generate_signature, compose_htx_ws_auth, compose_binance_ws_auth
|
|
19
22
|
from exchanges_wrapper import LOG_PATH
|
|
20
23
|
|
|
21
24
|
logger = logging.getLogger(__name__)
|
|
@@ -55,7 +58,7 @@ class EventsDataStream:
|
|
|
55
58
|
async for self.websocket in connect(
|
|
56
59
|
self.endpoint,
|
|
57
60
|
logger=logger,
|
|
58
|
-
ping_interval=None if self.exchange
|
|
61
|
+
ping_interval=None if self.exchange in ('binance', 'huobi') else 20
|
|
59
62
|
):
|
|
60
63
|
start_time = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
61
64
|
try:
|
|
@@ -100,7 +103,14 @@ class EventsDataStream:
|
|
|
100
103
|
async def _handle_messages(self, msg, symbol=None, ch_type=str()):
|
|
101
104
|
msg_data = json.loads(msg if isinstance(msg, str) else gzip.decompress(msg))
|
|
102
105
|
if self.exchange == 'binance':
|
|
103
|
-
|
|
106
|
+
if "stream" in msg_data:
|
|
107
|
+
await self._handle_event(msg_data)
|
|
108
|
+
elif event := msg_data.get("event"):
|
|
109
|
+
await self._handle_event(event)
|
|
110
|
+
elif msg_data.get("status") == 200:
|
|
111
|
+
result = msg_data.get("result")
|
|
112
|
+
if isinstance(result, dict) and not result:
|
|
113
|
+
self.wss_started = True
|
|
104
114
|
elif self.exchange == 'bybit':
|
|
105
115
|
if _data := msg_data.get('data'):
|
|
106
116
|
if ch_type == 'depth5':
|
|
@@ -368,7 +378,7 @@ class MarketEventsDataStream(EventsDataStream):
|
|
|
368
378
|
content = self._order_book.get_book()
|
|
369
379
|
#
|
|
370
380
|
stream_name = None
|
|
371
|
-
if isinstance(content, dict)
|
|
381
|
+
if isinstance(content, dict):
|
|
372
382
|
stream_name = content["stream"]
|
|
373
383
|
content = content["data"]
|
|
374
384
|
content["stream"] = stream_name
|
|
@@ -593,33 +603,21 @@ class BBTPrivateEventsDataStream(EventsDataStream):
|
|
|
593
603
|
|
|
594
604
|
|
|
595
605
|
class UserEventsDataStream(EventsDataStream):
|
|
596
|
-
def __init__(self, client, endpoint, exchange, trade_id):
|
|
597
|
-
super().__init__(client, endpoint, exchange, trade_id)
|
|
598
|
-
self.listen_key = None
|
|
599
|
-
|
|
600
|
-
async def async_init(self):
|
|
601
|
-
self.listen_key = (await self.client.create_listen_key())["listenKey"]
|
|
602
|
-
self.endpoint = f"{self.endpoint}/ws/{self.listen_key}"
|
|
603
|
-
self.wss_started = True
|
|
604
|
-
return self
|
|
605
|
-
|
|
606
|
-
def __await__(self):
|
|
607
|
-
return self.async_init().__await__()
|
|
608
|
-
|
|
609
|
-
async def _heartbeat(self, listen_key, interval=60 * 30):
|
|
610
|
-
# 30 minutes is recommended according to
|
|
611
|
-
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#pingkeep-alive-a-listenkey
|
|
612
|
-
while True:
|
|
613
|
-
await asyncio.sleep(interval)
|
|
614
|
-
await self.client.keep_alive_listen_key(listen_key)
|
|
615
606
|
|
|
616
607
|
async def start_wss(self):
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
608
|
+
await self.websocket.send(
|
|
609
|
+
json.dumps(
|
|
610
|
+
compose_binance_ws_auth(self.trade_id, self.client.api_key, self.client.api_secret)
|
|
611
|
+
)
|
|
612
|
+
)
|
|
613
|
+
await asyncio.sleep(0)
|
|
614
|
+
await self._handle_messages(await self.websocket.recv())
|
|
615
|
+
#
|
|
616
|
+
request = {
|
|
617
|
+
"id": self.trade_id,
|
|
618
|
+
"method": "userDataStream.subscribe"
|
|
619
|
+
}
|
|
620
|
+
await self.ws_listener(request)
|
|
623
621
|
|
|
624
622
|
async def _handle_event(self, content):
|
|
625
623
|
# logger.debug(f"UserEventsDataStream._handle_event.content: {content}")
|
|
@@ -14,13 +14,13 @@ 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.
|
|
17
|
+
requires-python = ">=3.10"
|
|
18
18
|
|
|
19
19
|
dependencies = [
|
|
20
|
-
"crypto-ws-api==2.0.
|
|
20
|
+
"crypto-ws-api==2.0.20",
|
|
21
21
|
"pyotp==2.9.0",
|
|
22
22
|
"simplejson==3.20.1",
|
|
23
|
-
"aiohttp~=3.11.
|
|
23
|
+
"aiohttp~=3.11.18",
|
|
24
24
|
"expiringdict~=1.2.2",
|
|
25
25
|
"betterproto==2.0.0b7",
|
|
26
26
|
"grpclib~=0.4.7"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|