Skip to content

Commit 4da5726

Browse files
authored
Merge pull request #473 from binance/release_common_v3.3.0
2 parents 26d4f8b + a20b276 commit 4da5726

File tree

8 files changed

+362
-48
lines changed

8 files changed

+362
-48
lines changed

common/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## 3.3.0 - 2025-12-22
4+
5+
### Changed (6)
6+
7+
- Added `body` parameter to `send_request` function for Rest API http requests.
8+
- Added `return_rate_limits` parameter to `ConfigurationWebSocketAPI` to avoid receiving rate limit headers.
9+
- Added `id_strict_int` parameter to `WebSocketStreamBase` to fix `Derivatives Trading Options` error.
10+
- Updated returned error value in `send_request` function.
11+
- Updated Websocket `timeout` and `reconnect_delay` to be in milliseconds.
12+
- Updated `backoff` to be in milliseconds.
13+
314
## 3.2.0 - 2025-10-10
415

516
### Changed (1)

common/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "binance-common"
3-
version = "3.2.0"
3+
version = "3.3.0"
44
description = "Binance Common Types and Utilities for Binance Connectors"
55
authors = ["Binance"]
66
license = "MIT"
@@ -11,7 +11,7 @@ packages = [
1111
]
1212

1313
[tool.poetry.dependencies]
14-
python = ">=3.9,<3.14"
14+
python = ">=3.9,<3.15"
1515
requests = ">=2.31.0"
1616
pydantic = ">=2.10.0"
1717
websockets = "^15.0.1"

common/src/binance_common/configuration.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,16 @@ def __init__(
104104
private_key: Optional[Union[bytes, str]] = None,
105105
private_key_passphrase: Optional[str] = None,
106106
stream_url: str = "wss://ws-api.binance.com/ws-api/v3",
107-
timeout: int = 5,
108-
reconnect_delay: int = 5,
107+
timeout: int = 5000,
108+
reconnect_delay: int = 5000,
109109
compression: int = 0,
110110
proxy: Optional[Dict[str, Union[str, int, Dict[str, str]]]] = None,
111111
mode: WebsocketMode = WebsocketMode.SINGLE,
112112
pool_size: int = 2,
113113
time_unit: TimeUnit = None,
114114
https_agent: Optional[ssl.SSLContext] = None,
115115
session_re_logon: Optional[bool] = True,
116+
return_rate_limits: Optional[bool] = True
116117
):
117118
"""
118119
Initialize the API configuration.
@@ -132,6 +133,7 @@ def __init__(
132133
time_unit (Optional[TimeUnit]): Time unit for time-based responses (default: None).
133134
https_agent (Optional[ssl.SSLContext]): Custom HTTPS Agent (default: None).
134135
session_re_logon (Optional[bool]): Enable session re-logon (default: True).
136+
return_rate_limits (Optional[bool]): Enable rate limits returns (default: True).
135137
"""
136138

137139
self.api_key = api_key
@@ -149,6 +151,7 @@ def __init__(
149151
self.https_agent = https_agent
150152
self.user_agent = ""
151153
self.session_re_logon = session_re_logon
154+
self.return_rate_limits = return_rate_limits
152155

153156

154157
class ConfigurationWebSocketStreams:
@@ -166,7 +169,7 @@ class ConfigurationWebSocketStreams:
166169
def __init__(
167170
self,
168171
stream_url: str = "wss://stream.binance.com:9443/stream",
169-
reconnect_delay: int = 5,
172+
reconnect_delay: int = 5000,
170173
compression: int = 0,
171174
proxy: Optional[Dict[str, Union[str, int, Dict[str, str]]]] = None,
172175
mode: WebsocketMode = WebsocketMode.SINGLE,

common/src/binance_common/errors.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ class Error(Exception):
88
class ClientError(Error):
99
"""Represents an error that occurred in the Connector client."""
1010

11-
def __init__(self, error_message: Optional[str] = None):
11+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
1212
self.error_message = error_message or "An unexpected error occurred."
13-
super().__init__(error_message)
13+
self.status_code = status_code
14+
super().__init__(self.status_code, self.error_message)
1415

1516

1617
class RequiredError(Error):
@@ -27,43 +28,43 @@ def __init__(self, field: str, error_message: Optional[str] = None):
2728
class UnauthorizedError(Error):
2829
"""Represents an error when a client is unauthorized to access a resource."""
2930

30-
def __init__(self, error_message: Optional[str] = None):
31+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
3132
self.error_message = (
3233
error_message or "Unauthorized access. Authentication required."
3334
)
34-
super().__init__(error_message)
35-
35+
self.status_code = status_code
36+
super().__init__(self.status_code, self.error_message)
3637

3738
class ForbiddenError(Error):
3839
"""Represents an error when access to the resource is forbidden."""
3940

40-
def __init__(self, error_message: Optional[str] = None):
41+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
4142
self.error_message = (
4243
error_message or "Access to the requested resource is forbidden."
4344
)
44-
super().__init__(error_message)
45-
45+
self.status_code = status_code
46+
super().__init__(self.status_code, self.error_message)
4647

4748
class TooManyRequestsError(Error):
4849
"""Represents an error when the client is doing too many requests."""
4950

50-
def __init__(self, error_message: Optional[str] = None):
51+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
5152
self.error_message = (
5253
error_message or "Too many requests. You are being rate-limited."
5354
)
54-
super().__init__(error_message)
55-
55+
self.status_code = status_code
56+
super().__init__(self.status_code, self.error_message)
5657

5758
class RateLimitBanError(Error):
5859
"""Represents an error when the client's IP has been banned for exceeding rate
5960
limits."""
6061

61-
def __init__(self, error_message: Optional[str] = None):
62+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
6263
self.error_message = (
6364
error_message or "The IP address has been banned for exceeding rate limits."
6465
)
65-
super().__init__(error_message)
66-
66+
self.status_code = status_code
67+
super().__init__(self.status_code, self.error_message)
6768

6869
class ServerError(Error):
6970
"""Represents an error when there is an internal server error."""
@@ -89,16 +90,18 @@ def __init__(self, error_message: Optional[str] = None):
8990
class NotFoundError(Error):
9091
"""Represents an error when the requested resource was not found."""
9192

92-
def __init__(self, error_message: Optional[str] = None):
93+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
9394
self.error_message = error_message or "The requested resource was not found."
94-
super().__init__(error_message)
95+
self.status_code = status_code
96+
super().__init__(self.status_code, self.error_message)
9597

9698

9799
class BadRequestError(Error):
98100
"""Represents an error when a request is invalid or cannot be otherwise served."""
99101

100-
def __init__(self, error_message: Optional[str] = None):
102+
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
101103
self.error_message = (
102104
error_message or "The request was invalid or cannot be otherwise served."
103105
)
104-
super().__init__(self.error_message)
106+
self.status_code = status_code
107+
super().__init__(self.status_code, self.error_message)

common/src/binance_common/utils.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ def get_uuid() -> str:
208208
return str(uuid.uuid4())
209209

210210

211+
def get_random_int() -> int:
212+
"""Generates a random alphanumeric string of the specified length.
213+
214+
Returns:
215+
int: A random integer derived from a UUID.
216+
"""
217+
218+
return int(uuid.uuid4().int >> 64)
219+
211220
def validate_time_unit(time_unit: Optional[str]) -> Optional[str]:
212221
"""Validates the time unit against the defined TimeUnit constants.
213222
@@ -276,10 +285,11 @@ def send_request(
276285
method: str,
277286
path: str,
278287
payload: Optional[dict] = None,
288+
body: Optional[dict] = None,
279289
time_unit: Optional[str] = None,
280290
response_model: Type[T] = None,
281291
is_signed: bool = False,
282-
signer: Optional[Signers]=None
292+
signer: Optional[Signers]=None,
283293
) -> ApiResponse[T]:
284294
"""Sends an HTTP request with the specified configuration, method, path, and
285295
optional payload and time unit.
@@ -290,9 +300,11 @@ def send_request(
290300
- `method`: The HTTP method to use (e.g. "GET", "POST", etc.).
291301
- `path`: The API endpoint path.
292302
- `payload`: The request payload (optional).
303+
- `body`: The body data to send with the request (optional).
293304
- `time_unit`: The time unit for the `X-MBX-TIME-UNIT` header (optional).
294305
- `response_model`: The response model to use for deserializing the response (optional).
295306
- `is_signed`: A boolean indicating whether the request should be signed (optional).
307+
- `signer`: The signer to use for signing the request (optional).
296308
297309
The function returns the JSON response from the server, or raises an appropriate exception if an error occurs.
298310
"""
@@ -302,6 +314,8 @@ def send_request(
302314

303315
if is_signed:
304316
cleaned_payload = clean_none_value(payload)
317+
if body:
318+
cleaned_payload.update(clean_none_value(body))
305319
cleaned_payload["timestamp"] = get_timestamp()
306320
query_string = encoded_string(cleaned_payload)
307321
cleaned_payload["signature"] = get_signature(configuration, query_string, signer)
@@ -317,7 +331,7 @@ def send_request(
317331

318332
url = f"{configuration.base_path}{path}"
319333
retries = configuration.retries if configuration else 0
320-
backoff = configuration.backoff if configuration else 1
334+
backoff = configuration.backoff / 1000 if configuration else 1
321335
timeout = configuration.timeout / 1000 if configuration else 10
322336
proxies = (
323337
parse_proxies(configuration.proxy)
@@ -347,6 +361,7 @@ def send_request(
347361
headers=headers,
348362
timeout=timeout,
349363
proxies=proxies,
364+
data=encoded_string(clean_none_value(body)) if body else None,
350365
)
351366

352367
if response.status_code >= 400:
@@ -360,17 +375,17 @@ def send_request(
360375
)
361376

362377
if status == 400:
363-
raise BadRequestError(error_message=data.get("msg"))
378+
raise BadRequestError(error_message=data.get("msg"), status_code=data.get("code"))
364379
elif status == 401:
365-
raise UnauthorizedError(error_message=data.get("msg"))
380+
raise UnauthorizedError(error_message=data.get("msg"), status_code=data.get("code"))
366381
elif status == 403:
367-
raise ForbiddenError(error_message=data.get("msg"))
382+
raise ForbiddenError(error_message=data.get("msg"), status_code=data.get("code"))
368383
elif status == 404:
369-
raise NotFoundError(error_message=data.get("msg"))
384+
raise NotFoundError(error_message=data.get("msg"), status_code=data.get("code"))
370385
elif status == 418:
371-
raise RateLimitBanError(error_message=data.get("msg"))
386+
raise RateLimitBanError(error_message=data.get("msg"), status_code=data.get("code"))
372387
elif status == 429:
373-
raise TooManyRequestsError(error_message=data.get("msg"))
388+
raise TooManyRequestsError(error_message=data.get("msg"), status_code=data.get("code"))
374389
elif 500 <= status < 600:
375390
raise ServerError(
376391
error_message=f"Server error: {status}", status_code=status

0 commit comments

Comments
 (0)