Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions binance/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1538,45 +1538,45 @@ async def get_personal_left_quota(self, **params):
# US Staking Endpoints

async def get_staking_asset_us(self, **params):
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_asset_us")
return await self._request_margin_api("get", "staking/asset", True, data=params)

get_staking_asset_us.__doc__ = Client.get_staking_asset_us.__doc__

async def stake_asset_us(self, **params):
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "stake_asset_us")
return await self._request_margin_api(
"post", "staking/stake", True, data=params
)

stake_asset_us.__doc__ = Client.stake_asset_us.__doc__

async def unstake_asset_us(self, **params):
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "unstake_asset_us")
return await self._request_margin_api(
"post", "staking/unstake", True, data=params
)

unstake_asset_us.__doc__ = Client.unstake_asset_us.__doc__

async def get_staking_balance_us(self, **params):
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_balance_us")
return await self._request_margin_api(
"get", "staking/stakingBalance", True, data=params
)

get_staking_balance_us.__doc__ = Client.get_staking_balance_us.__doc__

async def get_staking_history_us(self, **params):
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_history_us")
return await self._request_margin_api(
"get", "staking/history", True, data=params
)

get_staking_history_us.__doc__ = Client.get_staking_history_us.__doc__

async def get_staking_rewards_history_us(self, **params):
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_rewards_history_us")
return await self._request_margin_api(
"get", "staking/stakingRewardsHistory", True, data=params
)
Expand Down
12 changes: 12 additions & 0 deletions binance/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,18 @@ def convert_to_dict(list_tuples):
dictionary = dict((key, value) for key, value in list_tuples)
return dictionary

def _require_tld(self, required_tld: str, endpoint_name: str = "endpoint") -> None:
"""Validate client is configured for required TLD.

:param required_tld: The required TLD (e.g., "us")
:param endpoint_name: Description of the endpoint for error messages
:raises BinanceRegionException: If the client TLD doesn't match
"""
if self.tld != required_tld:
from binance.exceptions import BinanceRegionException

raise BinanceRegionException(required_tld, self.tld, endpoint_name)

def _ed25519_signature(self, query_string: str):
assert self.PRIVATE_KEY
res = b64encode(
Expand Down
18 changes: 12 additions & 6 deletions binance/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6261,35 +6261,39 @@ def get_staking_asset_us(self, **params):

https://docs.binance.us/#get-staking-asset-information

:raises BinanceRegionException: If client is not configured for binance.us
"""
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_asset_us")
return self._request_margin_api("get", "staking/asset", True, data=params)

def stake_asset_us(self, **params):
"""Stake a supported asset.

https://docs.binance.us/#stake-asset

:raises BinanceRegionException: If client is not configured for binance.us
"""
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "stake_asset_us")
return self._request_margin_api("post", "staking/stake", True, data=params)

def unstake_asset_us(self, **params):
"""Unstake a staked asset

https://docs.binance.us/#unstake-asset

:raises BinanceRegionException: If client is not configured for binance.us
"""
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "unstake_asset_us")
return self._request_margin_api("post", "staking/unstake", True, data=params)

def get_staking_balance_us(self, **params):
"""Get staking balance

https://docs.binance.us/#get-staking-balance

:raises BinanceRegionException: If client is not configured for binance.us
"""
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_balance_us")
return self._request_margin_api(
"get", "staking/stakingBalance", True, data=params
)
Expand All @@ -6299,17 +6303,19 @@ def get_staking_history_us(self, **params):

https://docs.binance.us/#get-staking-history

:raises BinanceRegionException: If client is not configured for binance.us
"""
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_history_us")
return self._request_margin_api("get", "staking/history", True, data=params)

def get_staking_rewards_history_us(self, **params):
"""Get staking rewards history for an asset(or assets) within a given time range.

https://docs.binance.us/#get-staking-rewards-history

:raises BinanceRegionException: If client is not configured for binance.us
"""
assert self.tld == "us", "Endpoint only available on binance.us"
self._require_tld("us", "get_staking_rewards_history_us")
return self._request_margin_api(
"get", "staking/stakingRewardsHistory", True, data=params
)
Expand Down
19 changes: 19 additions & 0 deletions binance/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,22 @@ def __init__(self, value):

class UnknownDateFormat(Exception):
...


class BinanceRegionException(Exception):
"""Raised when using a region-specific endpoint with incompatible client."""

def __init__(
self, required_tld: str, actual_tld: str, endpoint_name: str = "endpoint"
):
self.required_tld = required_tld
self.actual_tld = actual_tld
self.endpoint_name = endpoint_name
self.message = (
f"{endpoint_name} is only available on binance.{required_tld}, "
f"but client is configured for binance.{actual_tld}"
)
super().__init__(self.message)

def __str__(self):
return f"BinanceRegionException: {self.message}"
155 changes: 155 additions & 0 deletions tests/test_region_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""Tests for BinanceRegionException and region validation."""

import pytest
from binance.client import Client
from binance.async_client import AsyncClient
from binance.exceptions import BinanceRegionException


class TestBinanceRegionException:
"""Tests for the BinanceRegionException class itself."""

def test_exception_attributes(self):
"""Test that exception has correct attributes."""
exc = BinanceRegionException("us", "com", "test_endpoint")
assert exc.required_tld == "us"
assert exc.actual_tld == "com"
assert exc.endpoint_name == "test_endpoint"

def test_exception_message_format(self):
"""Test that exception message is properly formatted."""
exc = BinanceRegionException("us", "com", "get_staking_asset_us")
assert "binance.us" in str(exc)
assert "binance.com" in str(exc)
assert "get_staking_asset_us" in str(exc)

def test_exception_default_endpoint_name(self):
"""Test that endpoint_name defaults to 'endpoint'."""
exc = BinanceRegionException("us", "com")
assert exc.endpoint_name == "endpoint"
assert "endpoint is only available" in str(exc)


class TestSyncClientRegionValidation:
"""Tests for region validation in synchronous Client."""

def test_get_staking_asset_us_wrong_tld(self):
"""Test that get_staking_asset_us raises exception for non-US client."""
client = Client("test_key", "test_secret", tld="com", ping=False)
with pytest.raises(BinanceRegionException) as exc_info:
client.get_staking_asset_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.actual_tld == "com"
assert exc_info.value.endpoint_name == "get_staking_asset_us"

def test_stake_asset_us_wrong_tld(self):
"""Test that stake_asset_us raises exception for non-US client."""
client = Client("test_key", "test_secret", tld="com", ping=False)
with pytest.raises(BinanceRegionException) as exc_info:
client.stake_asset_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "stake_asset_us"

def test_unstake_asset_us_wrong_tld(self):
"""Test that unstake_asset_us raises exception for non-US client."""
client = Client("test_key", "test_secret", tld="com", ping=False)
with pytest.raises(BinanceRegionException) as exc_info:
client.unstake_asset_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "unstake_asset_us"

def test_get_staking_balance_us_wrong_tld(self):
"""Test that get_staking_balance_us raises exception for non-US client."""
client = Client("test_key", "test_secret", tld="com", ping=False)
with pytest.raises(BinanceRegionException) as exc_info:
client.get_staking_balance_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "get_staking_balance_us"

def test_get_staking_history_us_wrong_tld(self):
"""Test that get_staking_history_us raises exception for non-US client."""
client = Client("test_key", "test_secret", tld="com", ping=False)
with pytest.raises(BinanceRegionException) as exc_info:
client.get_staking_history_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "get_staking_history_us"

def test_get_staking_rewards_history_us_wrong_tld(self):
"""Test that get_staking_rewards_history_us raises exception for non-US client."""
client = Client("test_key", "test_secret", tld="com", ping=False)
with pytest.raises(BinanceRegionException) as exc_info:
client.get_staking_rewards_history_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "get_staking_rewards_history_us"


@pytest.mark.asyncio
class TestAsyncClientRegionValidation:
"""Tests for region validation in asynchronous AsyncClient."""

async def test_get_staking_asset_us_wrong_tld_async(self):
"""Test that async get_staking_asset_us raises exception for non-US client."""
client = AsyncClient("test_key", "test_secret", tld="com")
try:
with pytest.raises(BinanceRegionException) as exc_info:
await client.get_staking_asset_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.actual_tld == "com"
assert exc_info.value.endpoint_name == "get_staking_asset_us"
finally:
await client.close_connection()

async def test_stake_asset_us_wrong_tld_async(self):
"""Test that async stake_asset_us raises exception for non-US client."""
client = AsyncClient("test_key", "test_secret", tld="com")
try:
with pytest.raises(BinanceRegionException) as exc_info:
await client.stake_asset_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "stake_asset_us"
finally:
await client.close_connection()

async def test_unstake_asset_us_wrong_tld_async(self):
"""Test that async unstake_asset_us raises exception for non-US client."""
client = AsyncClient("test_key", "test_secret", tld="com")
try:
with pytest.raises(BinanceRegionException) as exc_info:
await client.unstake_asset_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "unstake_asset_us"
finally:
await client.close_connection()

async def test_get_staking_balance_us_wrong_tld_async(self):
"""Test that async get_staking_balance_us raises exception for non-US client."""
client = AsyncClient("test_key", "test_secret", tld="com")
try:
with pytest.raises(BinanceRegionException) as exc_info:
await client.get_staking_balance_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "get_staking_balance_us"
finally:
await client.close_connection()

async def test_get_staking_history_us_wrong_tld_async(self):
"""Test that async get_staking_history_us raises exception for non-US client."""
client = AsyncClient("test_key", "test_secret", tld="com")
try:
with pytest.raises(BinanceRegionException) as exc_info:
await client.get_staking_history_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "get_staking_history_us"
finally:
await client.close_connection()

async def test_get_staking_rewards_history_us_wrong_tld_async(self):
"""Test that async get_staking_rewards_history_us raises exception for non-US client."""
client = AsyncClient("test_key", "test_secret", tld="com")
try:
with pytest.raises(BinanceRegionException) as exc_info:
await client.get_staking_rewards_history_us()
assert exc_info.value.required_tld == "us"
assert exc_info.value.endpoint_name == "get_staking_rewards_history_us"
finally:
await client.close_connection()
Loading