Skip to content

Commit b901cf2

Browse files
authored
Merge pull request #506 from binance/release_common_v3.7.0
2 parents 6ae8f6c + 8dc00b0 commit b901cf2

12 files changed

Lines changed: 721 additions & 351 deletions

File tree

common/CHANGELOG.md

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

3+
## 3.7.0 - 2026-03-16
4+
5+
### Added (1)
6+
7+
- Added `serverShutdown` event handler.
8+
39
## 3.6.0 - 2026-02-11
410

511
### Updated (2)

common/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "binance-common"
3-
version = "3.6.0"
3+
version = "3.7.0"
44
description = "Binance Common Types and Utilities for Binance Connectors"
55
authors = ["Binance"]
66
license = "MIT"

common/src/binance_common/configuration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(
7777
self.base_headers = {
7878
"Accept": "application/json",
7979
"X-MBX-APIKEY": str(self.api_key) if self.api_key else "",
80-
**parse_custom_headers(custom_headers)
80+
**parse_custom_headers(custom_headers),
8181
}
8282

8383

@@ -113,7 +113,7 @@ def __init__(
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
116+
return_rate_limits: Optional[bool] = True,
117117
):
118118
"""
119119
Initialize the API configuration.

common/src/binance_common/constants.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ class TimeUnit(Enum):
88
MICROSECOND = "MICROSECOND"
99
microsecond = "microsecond"
1010

11+
1112
class WebsocketMode(Enum):
1213
SINGLE = "single"
1314
POOL = "pool"
1415

16+
1517
# Algo constants
1618
ALGO_REST_API_PROD_URL = "https://api.binance.com"
1719

@@ -38,33 +40,58 @@ class WebsocketMode(Enum):
3840

3941
# Derivatives Trading (COIN-M Futures) constants
4042
DERIVATIVES_TRADING_COIN_FUTURES_REST_API_PROD_URL = "https://dapi.binance.com"
41-
DERIVATIVES_TRADING_COIN_FUTURES_REST_API_TESTNET_URL = "https://testnet.binancefuture.com"
42-
DERIVATIVES_TRADING_COIN_FUTURES_WS_API_PROD_URL = "wss://ws-dapi.binance.com/ws-dapi/v1"
43-
DERIVATIVES_TRADING_COIN_FUTURES_WS_API_TESTNET_URL = "wss://testnet.binancefuture.com/ws-dapi/v1"
43+
DERIVATIVES_TRADING_COIN_FUTURES_REST_API_TESTNET_URL = (
44+
"https://testnet.binancefuture.com"
45+
)
46+
DERIVATIVES_TRADING_COIN_FUTURES_WS_API_PROD_URL = (
47+
"wss://ws-dapi.binance.com/ws-dapi/v1"
48+
)
49+
DERIVATIVES_TRADING_COIN_FUTURES_WS_API_TESTNET_URL = (
50+
"wss://testnet.binancefuture.com/ws-dapi/v1"
51+
)
4452
DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_PROD_URL = "wss://dstream.binance.com"
45-
DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_TESTNET_URL = "wss://dstream.binancefuture.com"
53+
DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_TESTNET_URL = (
54+
"wss://dstream.binancefuture.com"
55+
)
4656

4757
# Derivatives Trading (USDS Futures) constants
4858
DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL = "https://fapi.binance.com"
49-
DERIVATIVES_TRADING_USDS_FUTURES_REST_API_TESTNET_URL = "https://testnet.binancefuture.com"
50-
DERIVATIVES_TRADING_USDS_FUTURES_WS_API_PROD_URL = "wss://ws-fapi.binance.com/ws-fapi/v1"
51-
DERIVATIVES_TRADING_USDS_FUTURES_WS_API_TESTNET_URL = "wss://testnet.binancefuture.com/ws-fapi/v1"
59+
DERIVATIVES_TRADING_USDS_FUTURES_REST_API_TESTNET_URL = (
60+
"https://testnet.binancefuture.com"
61+
)
62+
DERIVATIVES_TRADING_USDS_FUTURES_REST_API_DEMO_URL = 'https://demo-fapi.binance.com'
63+
DERIVATIVES_TRADING_USDS_FUTURES_WS_API_PROD_URL = (
64+
"wss://ws-fapi.binance.com/ws-fapi/v1"
65+
)
66+
DERIVATIVES_TRADING_USDS_FUTURES_WS_API_TESTNET_URL = (
67+
"wss://testnet.binancefuture.com/ws-fapi/v1"
68+
)
5269
DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_PROD_URL = "wss://fstream.binance.com"
53-
DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_TESTNET_URL = "wss://stream.binancefuture.com"
70+
DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_TESTNET_URL = (
71+
"wss://fstream.binancefuture.com"
72+
)
5473

5574
# Derivatives Trading (Options) constants
5675
DERIVATIVES_TRADING_OPTIONS_REST_API_PROD_URL = "https://eapi.binance.com"
5776
DERIVATIVES_TRADING_OPTIONS_WS_STREAMS_PROD_URL = "wss://fstream.binance.com"
5877

5978
# Derivatives Trading (Portfolio Margin) constants
6079
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_PROD_URL = "https://papi.binance.com"
61-
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_TESTNET_URL = "https://testnet.binancefuture.com"
62-
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_WS_STREAMS_PROD_URL = "wss://fstream.binance.com/pm"
63-
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_WS_STREAMS_TESTNET_URL = "wss://fstream.binancefuture.com/pm"
80+
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_TESTNET_URL = (
81+
"https://testnet.binancefuture.com"
82+
)
83+
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_WS_STREAMS_PROD_URL = (
84+
"wss://fstream.binance.com/pm"
85+
)
86+
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_WS_STREAMS_TESTNET_URL = (
87+
"wss://fstream.binancefuture.com/pm"
88+
)
6489

6590
# Derivatives Trading (Portfolio Margin Pro) constants
6691
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_REST_API_PROD_URL = "https://api.binance.com"
67-
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_WS_STREAMS_PROD_URL = "wss://fstream.binance.com/pm-classic"
92+
DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_WS_STREAMS_PROD_URL = (
93+
"wss://fstream.binance.com/pm-classic"
94+
)
6895

6996
# Dual Investment constants
7097
DUAL_INVESTMENT_REST_API_PROD_URL = "https://api.binance.com"
@@ -98,10 +125,13 @@ class WebsocketMode(Enum):
98125
# Spot Constants
99126
SPOT_REST_API_PROD_URL = "https://api.binance.com"
100127
SPOT_REST_API_TESTNET_URL = "https://testnet.binance.vision"
128+
SPOT_REST_API_DEMO_URL = 'https://demo-api.binance.com'
101129
SPOT_WS_API_PROD_URL = "wss://ws-api.binance.com:443/ws-api/v3"
102130
SPOT_WS_API_TESTNET_URL = "wss://ws-api.testnet.binance.vision/ws-api/v3"
131+
SPOT_WS_API_DEMO_URL = 'wss://demo-ws-api.binance.com/ws-api/v3'
103132
SPOT_WS_STREAMS_PROD_URL = "wss://stream.binance.com:9443"
104133
SPOT_WS_STREAMS_TESTNET_URL = "wss://stream.testnet.binance.vision"
134+
SPOT_WS_STREAMS_DEMO_URL = 'wss://demo-stream.binance.com:9443'
105135
SPOT_REST_API_MARKET_URL = "https://data-api.binance.vision"
106136
SPOT_WS_STREAMS_MARKET_URL = "wss://data-stream.binance.vision"
107137

common/src/binance_common/errors.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ 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, status_code: Optional[int] = None):
11+
def __init__(
12+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
13+
):
1214
self.error_message = error_message or "An unexpected error occurred."
1315
self.status_code = status_code
1416
super().__init__(self.status_code, self.error_message)
@@ -28,44 +30,56 @@ def __init__(self, field: str, error_message: Optional[str] = None):
2830
class UnauthorizedError(Error):
2931
"""Represents an error when a client is unauthorized to access a resource."""
3032

31-
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
33+
def __init__(
34+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
35+
):
3236
self.error_message = (
3337
error_message or "Unauthorized access. Authentication required."
3438
)
3539
self.status_code = status_code
3640
super().__init__(self.status_code, self.error_message)
3741

42+
3843
class ForbiddenError(Error):
3944
"""Represents an error when access to the resource is forbidden."""
4045

41-
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
46+
def __init__(
47+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
48+
):
4249
self.error_message = (
4350
error_message or "Access to the requested resource is forbidden."
4451
)
4552
self.status_code = status_code
4653
super().__init__(self.status_code, self.error_message)
4754

55+
4856
class TooManyRequestsError(Error):
4957
"""Represents an error when the client is doing too many requests."""
5058

51-
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
59+
def __init__(
60+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
61+
):
5262
self.error_message = (
5363
error_message or "Too many requests. You are being rate-limited."
5464
)
5565
self.status_code = status_code
5666
super().__init__(self.status_code, self.error_message)
5767

68+
5869
class RateLimitBanError(Error):
5970
"""Represents an error when the client's IP has been banned for exceeding rate
6071
limits."""
6172

62-
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
73+
def __init__(
74+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
75+
):
6376
self.error_message = (
6477
error_message or "The IP address has been banned for exceeding rate limits."
6578
)
6679
self.status_code = status_code
6780
super().__init__(self.status_code, self.error_message)
6881

82+
6983
class ServerError(Error):
7084
"""Represents an error when there is an internal server error."""
7185

@@ -90,7 +104,9 @@ def __init__(self, error_message: Optional[str] = None):
90104
class NotFoundError(Error):
91105
"""Represents an error when the requested resource was not found."""
92106

93-
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
107+
def __init__(
108+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
109+
):
94110
self.error_message = error_message or "The requested resource was not found."
95111
self.status_code = status_code
96112
super().__init__(self.status_code, self.error_message)
@@ -99,7 +115,9 @@ def __init__(self, error_message: Optional[str] = None, status_code: Optional[in
99115
class BadRequestError(Error):
100116
"""Represents an error when a request is invalid or cannot be otherwise served."""
101117

102-
def __init__(self, error_message: Optional[str] = None, status_code: Optional[int] = None):
118+
def __init__(
119+
self, error_message: Optional[str] = None, status_code: Optional[int] = None
120+
):
103121
self.error_message = (
104122
error_message or "The request was invalid or cannot be otherwise served."
105123
)

common/src/binance_common/headers.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import List, Optional, Union
44

5+
56
def sanitize_header_value(value: Union[str, List[str]]) -> Union[str, List[str]]:
67
"""Sanitizes a header value by checking for and preventing carriage return and line feed characters.
78
@@ -19,7 +20,10 @@ def sanitize_header_value(value: Union[str, List[str]]) -> Union[str, List[str]]
1920
raise ValueError(f'Invalid header value (contains CR/LF): "{value}"')
2021
return value
2122

22-
def parse_custom_headers(custom_headers: Optional[dict[str, Union[str, List[str]]]]) -> dict[str, Union[str, List[str]]]:
23+
24+
def parse_custom_headers(
25+
custom_headers: Optional[dict[str, Union[str, List[str]]]],
26+
) -> dict[str, Union[str, List[str]]]:
2327
"""Parses custom headers for the API client.
2428
2529
Args:
@@ -46,8 +50,8 @@ def parse_custom_headers(custom_headers: Optional[dict[str, Union[str, List[str]
4650
parsed_headers[header_name] = [sanitize_header_value(v) for v in value]
4751
else:
4852
parsed_headers[header_name] = sanitize_header_value(value)
49-
except ValueError as e:
53+
except ValueError:
5054
logging.warning(f"Dropping header '{header_name}' due to invalid value.")
5155
continue
5256

53-
return parsed_headers
57+
return parsed_headers

common/src/binance_common/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
T_Response = TypeVar("T_Response")
88
T_Stream = TypeVar("T_Stream")
99

10+
1011
class RateLimit(BaseModel):
1112
"""Represents a single rate limit entry.
1213
@@ -132,6 +133,7 @@ def __init__(
132133
self.is_signed = is_signed
133134
self.skip_auth = skip_auth
134135

136+
135137
class WebsocketApiUserDataEndpoints(BaseModel):
136138
"""Represents the WebSocket user data endpoints."""
137139

common/src/binance_common/signature.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ def get_ed25519_signer(cls, key: str, passphrase: Optional[str]) -> object:
5555
ed_key = cls.get_ed25519_key(key, passphrase)
5656
cls._ed25519_signers[cache_key] = eddsa.new(ed_key, "rfc8032")
5757
return cls._ed25519_signers[cache_key]
58-
58+
5959
@classmethod
60-
def get_signer(cls, private_key: str, passphrase: Optional[str] = None) -> Union[pkcs1_15.PKCS115_SigScheme, object]:
60+
def get_signer(
61+
cls, private_key: str, passphrase: Optional[str] = None
62+
) -> Union[pkcs1_15.PKCS115_SigScheme, object]:
6163
"""
6264
Automatically detects the key type (RSA or Ed25519) and returns the appropriate signer.
6365
Raises ValueError if the key is not supported.
@@ -73,4 +75,6 @@ def get_signer(cls, private_key: str, passphrase: Optional[str] = None) -> Union
7375
except (ValueError, IndexError, TypeError):
7476
pass
7577

76-
raise ValueError("Unsupported or invalid private key format. Private key must be either 'RSA' or 'ED25519'")
78+
raise ValueError(
79+
"Unsupported or invalid private key format. Private key must be either 'RSA' or 'ED25519'"
80+
)

0 commit comments

Comments
 (0)