exchanges-wrapper 2.1.27__tar.gz → 2.1.29__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.27 → exchanges_wrapper-2.1.29}/PKG-INFO +1 -1
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/client.py +25 -19
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/exch_srv.py +17 -10
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/exch_srv_cfg.toml.template +6 -6
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/http_client.py +20 -5
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/parsers/bybit.py +54 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/README.md +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/parsers/bitfinex.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/parsers/huobi.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/parsers/okx.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/proto/martin.proto +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/web_sockets.py +0 -0
- {exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: exchanges-wrapper
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.29
|
|
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
|
|
@@ -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.29"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -472,13 +472,15 @@ class Client:
|
|
|
472
472
|
'status': 'SUCCESS',
|
|
473
473
|
'startTime': max(self.ts_start[symbol], (int(time.time()) - 300) * 1000)
|
|
474
474
|
}
|
|
475
|
+
_res = []
|
|
475
476
|
# Internal transfer, ie from Funding to UTA account
|
|
476
477
|
res, ts = await self.http.send_api_call(
|
|
477
478
|
"/v5/asset/transfer/query-inter-transfer-list",
|
|
478
479
|
signed=True,
|
|
479
480
|
**params
|
|
480
481
|
)
|
|
481
|
-
|
|
482
|
+
if res:
|
|
483
|
+
_res = bbt.on_balance_update(res['list'], ts, symbol, 'internal')
|
|
482
484
|
|
|
483
485
|
# Universal Transfer Records, ie from Sub account to Main account
|
|
484
486
|
res, ts = await self.http.send_api_call(
|
|
@@ -486,13 +488,14 @@ class Client:
|
|
|
486
488
|
signed=True,
|
|
487
489
|
**params
|
|
488
490
|
)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
491
|
+
if res:
|
|
492
|
+
_res += bbt.on_balance_update(
|
|
493
|
+
res['list'],
|
|
494
|
+
ts,
|
|
495
|
+
symbol,
|
|
496
|
+
'universal',
|
|
497
|
+
uid=self.account_uid
|
|
498
|
+
)
|
|
496
499
|
|
|
497
500
|
if not _res:
|
|
498
501
|
# Get Transaction Log
|
|
@@ -506,12 +509,13 @@ class Client:
|
|
|
506
509
|
signed=True,
|
|
507
510
|
**params
|
|
508
511
|
)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
512
|
+
if res:
|
|
513
|
+
_res += bbt.on_balance_update(
|
|
514
|
+
res['list'],
|
|
515
|
+
ts,
|
|
516
|
+
symbol,
|
|
517
|
+
'log'
|
|
518
|
+
)
|
|
515
519
|
|
|
516
520
|
for i in _res:
|
|
517
521
|
_id = next(iter(i))
|
|
@@ -1005,11 +1009,13 @@ class Client:
|
|
|
1005
1009
|
elif self.exchange == 'bybit':
|
|
1006
1010
|
params = {
|
|
1007
1011
|
'category': 'spot',
|
|
1008
|
-
'symbol': symbol
|
|
1009
|
-
'orderId': str(order_id),
|
|
1010
|
-
'orderLinkId': str(origin_client_order_id),
|
|
1012
|
+
'symbol': symbol
|
|
1011
1013
|
}
|
|
1012
|
-
|
|
1014
|
+
if order_id:
|
|
1015
|
+
params['orderId'] = str(order_id)
|
|
1016
|
+
else:
|
|
1017
|
+
params['orderLinkId'] = str(origin_client_order_id)
|
|
1018
|
+
res, _ = await self.http.send_api_call("/v5/order/realtime", signed=True, **params)
|
|
1013
1019
|
if res["list"]:
|
|
1014
1020
|
b_res = bbt.order(res["list"][0], response_type=response_type)
|
|
1015
1021
|
return b_res
|
|
@@ -1311,7 +1317,7 @@ class Client:
|
|
|
1311
1317
|
elif self.exchange == 'bybit':
|
|
1312
1318
|
params = {'category': 'spot', 'symbol': symbol}
|
|
1313
1319
|
res, _ = await self.http.send_api_call("/v5/order/realtime", signed=True, **params)
|
|
1314
|
-
binance_res = bbt.orders(res
|
|
1320
|
+
binance_res = bbt.orders(res.get('list', []), response_type=response_type)
|
|
1315
1321
|
return binance_res
|
|
1316
1322
|
|
|
1317
1323
|
# https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#all-orders-user_data
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
from typing import Any, AsyncGenerator
|
|
3
4
|
|
|
4
5
|
import grpclib.exceptions
|
|
5
6
|
|
|
@@ -25,6 +26,9 @@ from exchanges_wrapper.lib import (
|
|
|
25
26
|
REST_RATE_LIMIT_INTERVAL,
|
|
26
27
|
FILTER_TYPE_MAP,
|
|
27
28
|
)
|
|
29
|
+
from exchanges_wrapper.martin import StreamResponse, SimpleResponse, OnKlinesUpdateResponse, OnTickerUpdateResponse, \
|
|
30
|
+
FetchOrderBookResponse
|
|
31
|
+
|
|
28
32
|
#
|
|
29
33
|
HEARTBEAT = 1 # sec
|
|
30
34
|
MAX_QUEUE_SIZE = 100
|
|
@@ -154,12 +158,13 @@ class Martin(mr.MartinBase):
|
|
|
154
158
|
async def reset_rate_limit(self, request: mr.OpenClientConnectionId) -> mr.SimpleResponse:
|
|
155
159
|
Martin.rate_limiter = max(Martin.rate_limiter or 0, request.rate_limiter)
|
|
156
160
|
_success = False
|
|
157
|
-
|
|
161
|
+
open_client = OpenClient.get_client(request.client_id)
|
|
162
|
+
client = open_client.client
|
|
158
163
|
if Martin.rate_limit_reached_time:
|
|
159
164
|
if time.time() - Martin.rate_limit_reached_time > 30:
|
|
160
165
|
client.http.rate_limit_reached = False
|
|
161
166
|
Martin.rate_limit_reached_time = None
|
|
162
|
-
logger.info("RateLimit error clear, trying one else time")
|
|
167
|
+
logger.info(f"RateLimit error clear for {open_client.name}, trying one else time")
|
|
163
168
|
_success = True
|
|
164
169
|
elif client.http.rate_limit_reached:
|
|
165
170
|
Martin.rate_limit_reached_time = time.time()
|
|
@@ -434,7 +439,7 @@ class Martin(mr.MartinBase):
|
|
|
434
439
|
response.items = list(map(json.dumps, res))
|
|
435
440
|
return response
|
|
436
441
|
|
|
437
|
-
async def on_klines_update(self, request: mr.FetchKlinesRequest) ->
|
|
442
|
+
async def on_klines_update(self, request: mr.FetchKlinesRequest) -> AsyncGenerator[OnKlinesUpdateResponse, Any]:
|
|
438
443
|
response = mr.OnKlinesUpdateResponse()
|
|
439
444
|
open_client = OpenClient.get_client(request.client_id)
|
|
440
445
|
client = open_client.client
|
|
@@ -508,7 +513,7 @@ class Martin(mr.MartinBase):
|
|
|
508
513
|
response.items = list(map(json.dumps, res))
|
|
509
514
|
return response
|
|
510
515
|
|
|
511
|
-
async def on_ticker_update(self, request: mr.MarketRequest) ->
|
|
516
|
+
async def on_ticker_update(self, request: mr.MarketRequest) -> AsyncGenerator[OnTickerUpdateResponse, Any]:
|
|
512
517
|
response = mr.OnTickerUpdateResponse()
|
|
513
518
|
open_client = OpenClient.get_client(request.client_id)
|
|
514
519
|
client = open_client.client
|
|
@@ -543,7 +548,7 @@ class Martin(mr.MartinBase):
|
|
|
543
548
|
yield response
|
|
544
549
|
_queue.task_done()
|
|
545
550
|
|
|
546
|
-
async def on_order_book_update(self, request: mr.MarketRequest) ->
|
|
551
|
+
async def on_order_book_update(self, request: mr.MarketRequest) -> AsyncGenerator[FetchOrderBookResponse, Any]:
|
|
547
552
|
response = mr.FetchOrderBookResponse()
|
|
548
553
|
open_client = OpenClient.get_client(request.client_id)
|
|
549
554
|
client = open_client.client
|
|
@@ -577,7 +582,7 @@ class Martin(mr.MartinBase):
|
|
|
577
582
|
yield response
|
|
578
583
|
_queue.task_done()
|
|
579
584
|
|
|
580
|
-
async def on_funds_update(self, request: mr.OnFundsUpdateRequest) ->
|
|
585
|
+
async def on_funds_update(self, request: mr.OnFundsUpdateRequest) -> AsyncGenerator[StreamResponse, Any]:
|
|
581
586
|
response = mr.StreamResponse()
|
|
582
587
|
open_client = OpenClient.get_client(request.client_id)
|
|
583
588
|
client = open_client.client
|
|
@@ -597,7 +602,7 @@ class Martin(mr.MartinBase):
|
|
|
597
602
|
yield response
|
|
598
603
|
_queue.task_done()
|
|
599
604
|
|
|
600
|
-
async def on_balance_update(self, request: mr.MarketRequest) ->
|
|
605
|
+
async def on_balance_update(self, request: mr.MarketRequest) -> AsyncGenerator[StreamResponse, Any]:
|
|
601
606
|
response = mr.StreamResponse()
|
|
602
607
|
open_client = OpenClient.get_client(request.client_id)
|
|
603
608
|
client = open_client.client
|
|
@@ -640,10 +645,10 @@ class Martin(mr.MartinBase):
|
|
|
640
645
|
response.event = json.dumps(balance)
|
|
641
646
|
yield response
|
|
642
647
|
|
|
643
|
-
|
|
644
|
-
|
|
648
|
+
if _get_event_from_queue:
|
|
649
|
+
_queue.task_done()
|
|
645
650
|
|
|
646
|
-
async def on_order_update(self, request: mr.MarketRequest) ->
|
|
651
|
+
async def on_order_update(self, request: mr.MarketRequest) -> AsyncGenerator[SimpleResponse, Any]:
|
|
647
652
|
response = mr.SimpleResponse()
|
|
648
653
|
open_client = OpenClient.get_client(request.client_id)
|
|
649
654
|
client = open_client.client
|
|
@@ -787,6 +792,7 @@ class Martin(mr.MartinBase):
|
|
|
787
792
|
OpenClient.remove_client(request.account_name)
|
|
788
793
|
return mr.SimpleResponse(success=True)
|
|
789
794
|
|
|
795
|
+
|
|
790
796
|
async def stop_stream(client, trade_id):
|
|
791
797
|
await client.stop_events_listener(trade_id)
|
|
792
798
|
client.events.unregister(client.exchange, trade_id)
|
|
@@ -808,6 +814,7 @@ async def event_handler(_queue, client, trade_id, _event_type, event):
|
|
|
808
814
|
MAX_QUEUE_SIZE += int(MAX_QUEUE_SIZE / 10)
|
|
809
815
|
logger.info(f"MAX_QUEUE_SIZE was updated: new value is {MAX_QUEUE_SIZE}")
|
|
810
816
|
|
|
817
|
+
|
|
811
818
|
def is_port_in_use(port: int) -> bool:
|
|
812
819
|
import socket
|
|
813
820
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
{exchanges_wrapper-2.1.27 → exchanges_wrapper-2.1.29}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
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.29"
|
|
4
4
|
# region endpoint
|
|
5
5
|
[endpoint]
|
|
6
6
|
[endpoint.binance]
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
ws_api = 'wss://api.huobi.pro/ws/trade'
|
|
39
39
|
|
|
40
40
|
[endpoint.okx]
|
|
41
|
-
api_public = 'https://
|
|
42
|
-
api_auth = 'https://
|
|
43
|
-
ws_public = 'wss://
|
|
44
|
-
ws_auth = 'wss://
|
|
41
|
+
api_public = 'https://www.okx.com'
|
|
42
|
+
api_auth = 'https://www.okx.com'
|
|
43
|
+
ws_public = 'wss://ws.okx.com:8443/ws/v5/public'
|
|
44
|
+
ws_auth = 'wss://ws.okx.com:8443/ws/v5/private'
|
|
45
45
|
ws_business = 'wss://ws.okx.com:8443/ws/v5/business'
|
|
46
|
-
api_test = 'https://
|
|
46
|
+
api_test = 'https://www.okx.com'
|
|
47
47
|
ws_test = 'wss://wspap.okx.com:8443/ws/v5/private?brokerId=9999'
|
|
48
48
|
|
|
49
49
|
[endpoint.bybit]
|
|
@@ -15,6 +15,9 @@ from exchanges_wrapper.errors import (
|
|
|
15
15
|
HTTPError,
|
|
16
16
|
QueryCanceled,
|
|
17
17
|
)
|
|
18
|
+
from exchanges_wrapper.parsers.bybit import RateLimitHandler
|
|
19
|
+
|
|
20
|
+
|
|
18
21
|
logger = logging.getLogger(__name__)
|
|
19
22
|
|
|
20
23
|
AJ = 'application/json'
|
|
@@ -39,6 +42,7 @@ class HttpClient:
|
|
|
39
42
|
self._session_mutex = asyncio.Lock()
|
|
40
43
|
self.ex_imps = {} # exchanges implementation
|
|
41
44
|
self.declare_exchanges_implementation()
|
|
45
|
+
self.rate_limit_handler = RateLimitHandler() if self.exchange == 'bybit' else None
|
|
42
46
|
|
|
43
47
|
def declare_exchanges_implementation(self):
|
|
44
48
|
# noinspection PyTypeChecker
|
|
@@ -55,15 +59,15 @@ class HttpClient:
|
|
|
55
59
|
async with self._session_mutex:
|
|
56
60
|
self.session = aiohttp.ClientSession(timeout=TIMEOUT)
|
|
57
61
|
|
|
58
|
-
async def handle_errors(self, response):
|
|
62
|
+
async def handle_errors(self, response, path=None):
|
|
59
63
|
if response.status >= 500:
|
|
60
64
|
raise ExchangeError(
|
|
61
65
|
f"{'API request rejected' if self.exchange == 'bitfinex' else 'An issue occurred on exchange side'}:"
|
|
62
66
|
f" {response.status}: {response.url}: {response.reason}"
|
|
63
67
|
)
|
|
64
|
-
if response.status == 429:
|
|
68
|
+
if response.status == 429 or (self.exchange == 'bybit' and response.status == STATUS_FORBIDDEN):
|
|
65
69
|
logger.error(f"API RateLimitReached: {response.url}")
|
|
66
|
-
self.rate_limit_reached = self.exchange in ('binance', 'okx')
|
|
70
|
+
self.rate_limit_reached = self.exchange in ('binance', 'okx', 'bybit')
|
|
67
71
|
raise RateLimitReached(RateLimitReached.message)
|
|
68
72
|
|
|
69
73
|
try:
|
|
@@ -75,6 +79,7 @@ class HttpClient:
|
|
|
75
79
|
if response.status == STATUS_BAD_REQUEST:
|
|
76
80
|
if payload:
|
|
77
81
|
if payload.get("error", "") == "ERR_RATE_LIMIT":
|
|
82
|
+
self.rate_limit_reached = True
|
|
78
83
|
raise RateLimitReached(RateLimitReached.message)
|
|
79
84
|
elif self.exchange == 'binance' and payload.get('code', 0) == -1021:
|
|
80
85
|
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
@@ -102,6 +107,10 @@ class HttpClient:
|
|
|
102
107
|
return payload.get('result'), payload.get('time')
|
|
103
108
|
elif payload.get('retCode') == 10002:
|
|
104
109
|
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
110
|
+
elif payload.get('retCode') == 10006:
|
|
111
|
+
self.rate_limit_handler.fire_exceeded_rate_limit(path)
|
|
112
|
+
logger.warning(f"ByBit API: {payload.get('retMsg')}")
|
|
113
|
+
return payload.get('result'), payload.get('time')
|
|
105
114
|
else:
|
|
106
115
|
raise ExchangeError(f"API request failed: {response.status}:{response.reason}:{payload}")
|
|
107
116
|
elif self.exchange == 'huobi' and payload and (payload.get('status') == 'ok' or payload.get('ok')):
|
|
@@ -126,10 +135,14 @@ class HttpClient:
|
|
|
126
135
|
raise QueryCanceled(QueryCanceled.message)
|
|
127
136
|
return await self.ex_imps[self.exchange](path, method, signed, send_api_key, endpoint, timeout, **kwargs)
|
|
128
137
|
|
|
129
|
-
async def send_request(self, method, url, timeout, query_kwargs):
|
|
138
|
+
async def send_request(self, method, url, timeout, query_kwargs, path=None):
|
|
130
139
|
await self._create_session_if_required()
|
|
131
140
|
try:
|
|
132
141
|
async with self.session.request(method, url, timeout=timeout, **query_kwargs) as response:
|
|
142
|
+
|
|
143
|
+
if self.exchange == 'bybit':
|
|
144
|
+
self.rate_limit_handler.update(path, response.headers)
|
|
145
|
+
|
|
133
146
|
return await self.handle_errors(response)
|
|
134
147
|
except (aiohttp.ClientConnectionError, asyncio.exceptions.TimeoutError):
|
|
135
148
|
await self.session.close()
|
|
@@ -184,6 +197,8 @@ class HttpClient:
|
|
|
184
197
|
return await self.send_request(method, url, timeout, query_kwargs)
|
|
185
198
|
|
|
186
199
|
async def _bybit_request(self, path, method, signed, _send_api_key, endpoint, timeout, **kwargs):
|
|
200
|
+
await self.rate_limit_handler.wait(path)
|
|
201
|
+
|
|
187
202
|
url = endpoint or self.endpoint
|
|
188
203
|
query_kwargs = {}
|
|
189
204
|
data = None
|
|
@@ -214,7 +229,7 @@ class HttpClient:
|
|
|
214
229
|
|
|
215
230
|
query_kwargs['data'] = data
|
|
216
231
|
query_kwargs['headers'] = headers
|
|
217
|
-
return await self.send_request(method, url, timeout, query_kwargs)
|
|
232
|
+
return await self.send_request(method, url, timeout, query_kwargs, path)
|
|
218
233
|
|
|
219
234
|
async def _huobi_request(self, path, method, signed, _send_api_key, endpoint, timeout, **kwargs):
|
|
220
235
|
_endpoint = endpoint or self.endpoint
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Parser for convert Bybit REST API/WSS V5 response to Binance like result
|
|
3
3
|
"""
|
|
4
|
+
import asyncio
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
4
7
|
from decimal import Decimal
|
|
5
8
|
import logging
|
|
6
9
|
|
|
@@ -49,6 +52,57 @@ class OrderBook:
|
|
|
49
52
|
# return self
|
|
50
53
|
|
|
51
54
|
|
|
55
|
+
class RateLimitHandler:
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self.stats = {} # {'path': [limit_status, start_time, reset_time, range_window, limit]}
|
|
58
|
+
|
|
59
|
+
def update(self, path, headers):
|
|
60
|
+
if limit := int(headers.get('X-Bapi-Limit', '0')):
|
|
61
|
+
limit_status = int(headers['X-Bapi-Limit-Status'])
|
|
62
|
+
now = int(time.time() * 1000)
|
|
63
|
+
reset_time = max(int(headers['X-Bapi-Limit-Reset-Timestamp']), now)
|
|
64
|
+
if stats := self.stats.get(path):
|
|
65
|
+
_limit_status, start_time, _reset_time, _range_window, _limit = stats
|
|
66
|
+
else:
|
|
67
|
+
_limit_status = limit_status
|
|
68
|
+
start_time = now
|
|
69
|
+
_range_window = 1000
|
|
70
|
+
if limit_status == limit - 1:
|
|
71
|
+
start_time = now
|
|
72
|
+
range_window = max(_range_window, 1000)
|
|
73
|
+
else:
|
|
74
|
+
range_window = max(_range_window, now - start_time)
|
|
75
|
+
n = (_limit_status - limit_status) if _limit_status > limit_status else 1
|
|
76
|
+
reset_time += n * range_window / limit
|
|
77
|
+
self.stats[path] = [limit_status, start_time, reset_time, range_window, limit]
|
|
78
|
+
|
|
79
|
+
async def wait(self, path):
|
|
80
|
+
if self.stats.get(path) is None:
|
|
81
|
+
return
|
|
82
|
+
limit_status, start_time, reset_time, range_window, limit = self.stats[path]
|
|
83
|
+
min_delay = range_window / limit
|
|
84
|
+
now = int(time.time() * 1000)
|
|
85
|
+
if limit_status <= 1:
|
|
86
|
+
delay = max(
|
|
87
|
+
random.randint(1000, 2000), #NOSONAR python:S2245
|
|
88
|
+
max(reset_time, start_time + range_window) - now
|
|
89
|
+
) / 1000
|
|
90
|
+
else:
|
|
91
|
+
delay = max(min_delay, reset_time - now) / 1000
|
|
92
|
+
await asyncio.sleep(delay)
|
|
93
|
+
|
|
94
|
+
def fire_exceeded_rate_limit(self, path):
|
|
95
|
+
if stats := self.stats.get(path):
|
|
96
|
+
limit_status, start_time, _reset_time, range_window, limit = stats
|
|
97
|
+
self.stats[path] = [
|
|
98
|
+
limit_status,
|
|
99
|
+
start_time,
|
|
100
|
+
int(time.time() * 1000) + range_window,
|
|
101
|
+
range_window,
|
|
102
|
+
limit
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
|
|
52
106
|
def fetch_server_time(res: dict) -> dict:
|
|
53
107
|
return {'serverTime': int(res['timeNano']) // 1000000}
|
|
54
108
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|