unicex 0.8.0__tar.gz → 0.10.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.
- {unicex-0.8.0/unicex.egg-info → unicex-0.10.0}/PKG-INFO +7 -6
- {unicex-0.8.0 → unicex-0.10.0}/README.md +5 -5
- {unicex-0.8.0 → unicex-0.10.0}/pyproject.toml +2 -1
- {unicex-0.8.0 → unicex-0.10.0}/unicex/_abc/__init__.py +2 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/_abc/uni_client.py +1 -1
- unicex-0.10.0/unicex/_base/__init__.py +9 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/_base/websocket.py +34 -14
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/client.py +2 -2
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/client.py +25 -10
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/websocket_manager.py +6 -6
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/client.py +4 -4
- unicex-0.10.0/unicex/bybit/websocket_manager.py +339 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/client.py +1 -1
- unicex-0.10.0/unicex/gateio/websocket_manager.py +513 -0
- unicex-0.10.0/unicex/hyperliquid/websocket_manager.py +363 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
- unicex-0.10.0/unicex/mexc/_spot_ws_proto/__init__.py +335 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/client.py +2 -2
- unicex-0.10.0/unicex/mexc/websocket_manager.py +456 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/client.py +26 -15
- unicex-0.10.0/unicex/okx/websocket_manager.py +743 -0
- {unicex-0.8.0 → unicex-0.10.0/unicex.egg-info}/PKG-INFO +7 -6
- {unicex-0.8.0 → unicex-0.10.0}/unicex.egg-info/SOURCES.txt +18 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex.egg-info/requires.txt +1 -0
- unicex-0.8.0/unicex/_base/__init__.py +0 -7
- unicex-0.8.0/unicex/bybit/websocket_manager.py +0 -11
- unicex-0.8.0/unicex/gateio/websocket_manager.py +0 -11
- unicex-0.8.0/unicex/hyperliquid/websocket_manager.py +0 -11
- unicex-0.8.0/unicex/mexc/websocket_manager.py +0 -11
- unicex-0.8.0/unicex/okx/websocket_manager.py +0 -11
- {unicex-0.8.0 → unicex-0.10.0}/LICENSE +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/setup.cfg +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/_abc/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/_abc/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/_base/client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/binance/websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bitget/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/bybit/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/enums.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/exceptions.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/extra.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/gateio/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/hyperliquid/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mapper.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/mexc/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/__init__.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/adapter.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/exchange_info.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/uni_client.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/uni_websocket_manager.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/okx/user_websocket.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/types.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex/utils.py +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex.egg-info/dependency_links.txt +0 -0
- {unicex-0.8.0 → unicex-0.10.0}/unicex.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unicex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Unified Crypto Exchange API
|
|
5
5
|
Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -43,6 +43,7 @@ Requires-Dist: eth-account>=0.13.7
|
|
|
43
43
|
Requires-Dist: loguru>=0.7.3
|
|
44
44
|
Requires-Dist: msgpack>=1.1.1
|
|
45
45
|
Requires-Dist: orjson>=3.11.3
|
|
46
|
+
Requires-Dist: protobuf>=6.32.1
|
|
46
47
|
Requires-Dist: websockets>=15.0.1
|
|
47
48
|
Dynamic: license-file
|
|
48
49
|
|
|
@@ -56,11 +57,11 @@ Dynamic: license-file
|
|
|
56
57
|
|-----------------|--------|------|------------|---------|------------|----------------|--------------|
|
|
57
58
|
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
|
|
58
59
|
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
59
|
-
| **Bybit** | ✓ | ✓ |
|
|
60
|
-
| **Gateio** | ✓ | ✓ |
|
|
61
|
-
| **Hyperliquid** | ✓ | ✓ |
|
|
62
|
-
| **Mexc** | ✓ | ✓ |
|
|
63
|
-
| **Okx** | ✓ | ✓ |
|
|
60
|
+
| **Bybit** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
61
|
+
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
62
|
+
| **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
|
|
63
|
+
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
64
|
+
| **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
64
65
|
---
|
|
65
66
|
|
|
66
67
|
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
|-----------------|--------|------|------------|---------|------------|----------------|--------------|
|
|
9
9
|
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
|
|
10
10
|
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
11
|
-
| **Bybit** | ✓ | ✓ |
|
|
12
|
-
| **Gateio** | ✓ | ✓ |
|
|
13
|
-
| **Hyperliquid** | ✓ | ✓ |
|
|
14
|
-
| **Mexc** | ✓ | ✓ |
|
|
15
|
-
| **Okx** | ✓ | ✓ |
|
|
11
|
+
| **Bybit** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
12
|
+
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
13
|
+
| **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
|
|
14
|
+
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | | |
|
|
15
|
+
| **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
|
|
@@ -4,7 +4,7 @@ name = "unicex"
|
|
|
4
4
|
# • PATCH (x.y.Z) → увеличивается при багфиксе, который не ломает совместимость.
|
|
5
5
|
# • MINOR (x.Y.z) → увеличивается при добавлении новой функциональности, но без ломающих изменений (backward-compatible).
|
|
6
6
|
# • MAJOR (X.y.z) → увеличивается при изменениях, которые ломают обратную совместимость.
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
8
8
|
|
|
9
9
|
description = "Unified Crypto Exchange API "
|
|
10
10
|
readme = "README.md"
|
|
@@ -19,6 +19,7 @@ dependencies = [
|
|
|
19
19
|
"loguru>=0.7.3",
|
|
20
20
|
"msgpack>=1.1.1",
|
|
21
21
|
"orjson>=3.11.3",
|
|
22
|
+
"protobuf>=6.32.1",
|
|
22
23
|
"websockets>=15.0.1",
|
|
23
24
|
]
|
|
24
25
|
|
|
@@ -115,7 +115,7 @@ class IUniClient(ABC, Generic[TClient]):
|
|
|
115
115
|
Возвращает:
|
|
116
116
|
`bool`: True, если апи ключи присутствуют, иначе False.
|
|
117
117
|
"""
|
|
118
|
-
return self._client.
|
|
118
|
+
return self._client.is_authorized()
|
|
119
119
|
|
|
120
120
|
async def close_connection(self) -> None:
|
|
121
121
|
"""Закрывает сессию клиента."""
|
|
@@ -3,7 +3,7 @@ __all__ = ["Websocket"]
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import time
|
|
5
5
|
from collections.abc import Awaitable, Callable
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Protocol
|
|
7
7
|
|
|
8
8
|
import orjson
|
|
9
9
|
import websockets
|
|
@@ -20,18 +20,30 @@ class Websocket:
|
|
|
20
20
|
MAX_QUEUE_SIZE: int = 100
|
|
21
21
|
"""Максимальная длина очереди."""
|
|
22
22
|
|
|
23
|
+
class _DecoderProtocol(Protocol):
|
|
24
|
+
"""Протокол декодирования сообщений."""
|
|
25
|
+
|
|
26
|
+
def decode(self, message: Any) -> dict: ...
|
|
27
|
+
|
|
28
|
+
class _JsonDecoder:
|
|
29
|
+
"""Протокол декодирования сообщений в формате JSON."""
|
|
30
|
+
|
|
31
|
+
def decode(self, message: Any) -> dict:
|
|
32
|
+
return orjson.loads(message)
|
|
33
|
+
|
|
23
34
|
def __init__(
|
|
24
35
|
self,
|
|
25
36
|
callback: Callable[[Any], Awaitable[None]],
|
|
26
37
|
url: str,
|
|
27
38
|
subscription_messages: list[dict] | list[str] | None = None,
|
|
28
39
|
ping_interval: int | float = 10,
|
|
29
|
-
ping_message: str | None = None,
|
|
30
|
-
pong_message: str | None = None,
|
|
40
|
+
ping_message: str | Callable | None = None,
|
|
41
|
+
pong_message: str | Callable | None = None,
|
|
31
42
|
no_message_reconnect_timeout: int | float | None = 60,
|
|
32
43
|
reconnect_timeout: int | float | None = 5,
|
|
33
44
|
worker_count: int = 2,
|
|
34
45
|
logger: LoggerLike | None = None,
|
|
46
|
+
decoder: type[_DecoderProtocol] = _JsonDecoder,
|
|
35
47
|
**kwargs: Any, # Не дадим сломаться, если юзер передал ненужные аргументы
|
|
36
48
|
) -> None:
|
|
37
49
|
"""Инициализация вебсокета.
|
|
@@ -41,12 +53,13 @@ class Websocket:
|
|
|
41
53
|
url (`str`): URL вебсокета.
|
|
42
54
|
subscription_messages (`list[dict] | list[str] | None`): Сообщения для подписки после подключения.
|
|
43
55
|
ping_interval (`int | float`): Интервал отправки ping, сек.
|
|
44
|
-
ping_message (`str | None`): Сообщение для ping (если не указано — используется ping‑frame).
|
|
45
|
-
pong_message (`str | None`): Сообщение для pong (если не указано — используется pong‑frame).
|
|
56
|
+
ping_message (`str | Callable | None`): Сообщение для ping, или функция генерации ping (если не указано — используется ping‑frame).
|
|
57
|
+
pong_message (`str | Callable | None`): Сообщение для pong, или функция генерации pong (если не указано — используется pong‑frame).
|
|
46
58
|
no_message_reconnect_timeout (`int | float | None`): Таймаут ожидания без сообщений до рестарта, сек.
|
|
47
59
|
reconnect_timeout (`int | float | None`): Пауза перед переподключением, сек.
|
|
48
60
|
worker_count (`int`): Количество рабочих задач для обработки сообщений.
|
|
49
61
|
logger (`LoggerLike | None`): Логгер для записи логов.
|
|
62
|
+
decoder (`IDecoder | None`): Декодер для обработки входящих сообщений.
|
|
50
63
|
"""
|
|
51
64
|
self._callback = callback
|
|
52
65
|
self._url = url
|
|
@@ -59,6 +72,7 @@ class Websocket:
|
|
|
59
72
|
self._last_message_time = time.monotonic()
|
|
60
73
|
self._worker_count = worker_count
|
|
61
74
|
self._logger = logger or _logger
|
|
75
|
+
self._decoder = decoder()
|
|
62
76
|
self._tasks: list[asyncio.Task] = []
|
|
63
77
|
self._queue = asyncio.Queue()
|
|
64
78
|
self._running = False
|
|
@@ -104,24 +118,26 @@ class Websocket:
|
|
|
104
118
|
|
|
105
119
|
# Цикл получения сообщений
|
|
106
120
|
while self._running:
|
|
107
|
-
message = await conn.recv(
|
|
121
|
+
message = await conn.recv()
|
|
108
122
|
await self._handle_message(message)
|
|
109
123
|
|
|
110
|
-
except websockets.exceptions.ConnectionClosed:
|
|
111
|
-
self._logger.error("Websocket connection was closed unexpectedly")
|
|
112
|
-
|
|
124
|
+
except websockets.exceptions.ConnectionClosed as e:
|
|
125
|
+
self._logger.error(f"Websocket connection was closed unexpectedly: {e}")
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self._logger.error(f"Unexpected error in websosocket connection: {e}")
|
|
113
128
|
finally:
|
|
114
129
|
await asyncio.sleep(self._reconnect_timeout)
|
|
115
130
|
await self._after_disconnect()
|
|
116
131
|
|
|
117
|
-
async def _handle_message(self, message: str) -> None:
|
|
132
|
+
async def _handle_message(self, message: str | bytes) -> None:
|
|
118
133
|
"""Обрабатывает входящее сообщение вебсокета."""
|
|
119
134
|
try:
|
|
120
135
|
# Обновленяем время последнего сообщения
|
|
121
136
|
self._last_message_time = time.monotonic()
|
|
122
137
|
|
|
123
138
|
# Ложим сообщение в очередь, предварительно его сериализуя
|
|
124
|
-
|
|
139
|
+
decoded_message = self._decoder.decode(message)
|
|
140
|
+
await self._queue.put(decoded_message)
|
|
125
141
|
|
|
126
142
|
# Проверяем размер очереди сообщений и выбрасываем ошибку, если он превышает максимальный размер
|
|
127
143
|
self._check_queue_size()
|
|
@@ -139,7 +155,7 @@ class Websocket:
|
|
|
139
155
|
"""Проверяет размер очереди и выбрасывает ошибку при переполнении."""
|
|
140
156
|
qsize = self._queue.qsize()
|
|
141
157
|
if qsize >= self.MAX_QUEUE_SIZE:
|
|
142
|
-
raise QueueOverflowError("Message queue is overflow")
|
|
158
|
+
raise QueueOverflowError(f"Message queue is overflow: {qsize}")
|
|
143
159
|
|
|
144
160
|
async def _after_connect(self, conn: ClientConnection) -> None:
|
|
145
161
|
"""Вызывается после установки соединения."""
|
|
@@ -201,8 +217,12 @@ class Websocket:
|
|
|
201
217
|
"""Периодически отправляет пользовательский ping."""
|
|
202
218
|
while self._running and self._ping_message:
|
|
203
219
|
try:
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
if isinstance(self._ping_message, Callable):
|
|
221
|
+
ping_message = self._ping_message()
|
|
222
|
+
else:
|
|
223
|
+
ping_message = self._ping_message
|
|
224
|
+
await conn.send(ping_message)
|
|
225
|
+
self._logger.debug(f"Sent ping message: {ping_message}")
|
|
206
226
|
except Exception as e:
|
|
207
227
|
self._logger.error(f"Error sending ping: {e}")
|
|
208
228
|
return
|
|
@@ -62,8 +62,8 @@ class Client(BaseClient):
|
|
|
62
62
|
if not signed:
|
|
63
63
|
return {"params": params, "data": data}, None
|
|
64
64
|
|
|
65
|
-
if not self.
|
|
66
|
-
raise NotAuthorized("Api key is required to private endpoints")
|
|
65
|
+
if not self.is_authorized():
|
|
66
|
+
raise NotAuthorized("Api key and api secret is required to private endpoints")
|
|
67
67
|
|
|
68
68
|
# Объединяем все параметры в payload
|
|
69
69
|
payload = {**params, **data}
|
|
@@ -5,6 +5,7 @@ import time
|
|
|
5
5
|
from typing import Any, Literal
|
|
6
6
|
|
|
7
7
|
from unicex._base import BaseClient
|
|
8
|
+
from unicex.exceptions import NotAuthorized
|
|
8
9
|
from unicex.types import RequestMethod
|
|
9
10
|
from unicex.utils import (
|
|
10
11
|
dict_to_query_string,
|
|
@@ -20,6 +21,18 @@ class Client(BaseClient):
|
|
|
20
21
|
_BASE_URL: str = "https://api.bitget.com"
|
|
21
22
|
"""Базовый URL для REST API Bitget."""
|
|
22
23
|
|
|
24
|
+
def is_authorized(self) -> bool:
|
|
25
|
+
"""Проверяет наличие API‑ключей у клиента.
|
|
26
|
+
|
|
27
|
+
Возвращает:
|
|
28
|
+
`bool`: Признак наличия ключей.
|
|
29
|
+
"""
|
|
30
|
+
return (
|
|
31
|
+
self._api_key is not None
|
|
32
|
+
and self._api_secret is not None
|
|
33
|
+
and self._api_passphrase is not None
|
|
34
|
+
)
|
|
35
|
+
|
|
23
36
|
def _sign_message(
|
|
24
37
|
self,
|
|
25
38
|
method: RequestMethod,
|
|
@@ -45,6 +58,9 @@ class Client(BaseClient):
|
|
|
45
58
|
- `timestamp (str)`: Временная метка в миллисекундах.
|
|
46
59
|
- `signature (str)`: Подпись в формате base64.
|
|
47
60
|
"""
|
|
61
|
+
if not self.is_authorized():
|
|
62
|
+
raise NotAuthorized("Api key and api secret is required to private endpoints")
|
|
63
|
+
|
|
48
64
|
timestamp = str(int(time.time() * 1000))
|
|
49
65
|
|
|
50
66
|
path = f"{endpoint}?{dict_to_query_string(params)}" if params else endpoint
|
|
@@ -68,16 +84,15 @@ class Client(BaseClient):
|
|
|
68
84
|
`dict[str, str]`: Словарь заголовков запроса.
|
|
69
85
|
"""
|
|
70
86
|
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
87
|
+
headers.update(
|
|
88
|
+
{
|
|
89
|
+
"ACCESS-KEY": self._api_key, # type: ignore[attr-defined]
|
|
90
|
+
"ACCESS-PASSPHRASE": self._api_passphrase, # type: ignore[attr-defined]
|
|
91
|
+
"ACCESS-TIMESTAMP": timestamp,
|
|
92
|
+
"ACCESS-SIGN": signature,
|
|
93
|
+
"locale": "en-US",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
81
96
|
return headers
|
|
82
97
|
|
|
83
98
|
def _prepare_request_params(
|
|
@@ -15,7 +15,7 @@ type CallbackType = Callable[[Any], Awaitable[None]]
|
|
|
15
15
|
class WebsocketManager:
|
|
16
16
|
"""Менеджер асинхронных вебсокетов для Bitget."""
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
_URL: str = "wss://ws.bitget.com/v2/ws/public"
|
|
19
19
|
"""Базовый URL для вебсокета."""
|
|
20
20
|
|
|
21
21
|
def __init__(self, client: Client | None = None, **ws_kwargs: Any) -> None:
|
|
@@ -91,7 +91,7 @@ class WebsocketManager:
|
|
|
91
91
|
)
|
|
92
92
|
return Websocket(
|
|
93
93
|
callback=callback,
|
|
94
|
-
url=self.
|
|
94
|
+
url=self._URL,
|
|
95
95
|
subscription_messages=subsription_messages,
|
|
96
96
|
**self._ws_kwargs,
|
|
97
97
|
)
|
|
@@ -124,7 +124,7 @@ class WebsocketManager:
|
|
|
124
124
|
)
|
|
125
125
|
return Websocket(
|
|
126
126
|
callback=callback,
|
|
127
|
-
url=self.
|
|
127
|
+
url=self._URL,
|
|
128
128
|
subscription_messages=subscription_messages,
|
|
129
129
|
**self._ws_kwargs,
|
|
130
130
|
)
|
|
@@ -160,7 +160,7 @@ class WebsocketManager:
|
|
|
160
160
|
)
|
|
161
161
|
return Websocket(
|
|
162
162
|
callback=callback,
|
|
163
|
-
url=self.
|
|
163
|
+
url=self._URL,
|
|
164
164
|
subscription_messages=subscription_messages,
|
|
165
165
|
**self._ws_kwargs,
|
|
166
166
|
)
|
|
@@ -195,7 +195,7 @@ class WebsocketManager:
|
|
|
195
195
|
)
|
|
196
196
|
return Websocket(
|
|
197
197
|
callback=callback,
|
|
198
|
-
url=self.
|
|
198
|
+
url=self._URL,
|
|
199
199
|
subscription_messages=subscription_messages,
|
|
200
200
|
**self._ws_kwargs,
|
|
201
201
|
)
|
|
@@ -226,7 +226,7 @@ class WebsocketManager:
|
|
|
226
226
|
)
|
|
227
227
|
return Websocket(
|
|
228
228
|
callback=callback,
|
|
229
|
-
url=self.
|
|
229
|
+
url=self._URL,
|
|
230
230
|
subscription_messages=subscription_messages,
|
|
231
231
|
**self._ws_kwargs,
|
|
232
232
|
)
|
|
@@ -41,12 +41,12 @@ class Client(BaseClient):
|
|
|
41
41
|
Источник: https://github.com/bybit-exchange/api-usage-examples/blob/master/V5_demo/api_demo/Encryption_HMAC.py
|
|
42
42
|
"""
|
|
43
43
|
# Проверяем наличие апи ключей для подписи запроса
|
|
44
|
-
if not self.
|
|
45
|
-
raise NotAuthorized("
|
|
44
|
+
if not self.is_authorized():
|
|
45
|
+
raise NotAuthorized("Api key and api secret is required to private endpoints")
|
|
46
46
|
|
|
47
47
|
dumped_payload = json.dumps(payload)
|
|
48
|
-
prepared_query_string = timestamp + self._api_key + self._RECV_WINDOW + dumped_payload
|
|
49
|
-
return generate_hmac_sha256_signature(self._api_secret, prepared_query_string)
|
|
48
|
+
prepared_query_string = timestamp + self._api_key + self._RECV_WINDOW + dumped_payload # type: ignore[attrDefined]
|
|
49
|
+
return generate_hmac_sha256_signature(self._api_secret, prepared_query_string) # type: ignore[attrDefined]
|
|
50
50
|
|
|
51
51
|
async def _make_request(
|
|
52
52
|
self,
|