From 1bf3a8b1a7972020d1a1d6baeed2b11393511a42 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 1 Dec 2025 08:21:14 +0100 Subject: [PATCH 1/4] feat: add verbose mode --- binance/async_client.py | 24 +++- binance/base_client.py | 11 ++ binance/client.py | 15 +++ binance/ws/streams.py | 21 ++-- binance/ws/threaded_stream.py | 20 +++- docs/faqs.rst | 12 ++ docs/overview.rst | 197 ++++++++++++++++++++++++++++++++ examples/verbose_example.py | 119 +++++++++++++++++++ tests/test_verbose_mode.py | 55 +++++++++ tests/test_websocket_verbose.py | 116 +++++++++++++++++++ 10 files changed, 579 insertions(+), 11 deletions(-) create mode 100644 examples/verbose_example.py create mode 100644 tests/test_verbose_mode.py create mode 100644 tests/test_websocket_verbose.py diff --git a/binance/async_client.py b/binance/async_client.py index 541bd047e..653e5fd0b 100644 --- a/binance/async_client.py +++ b/binance/async_client.py @@ -38,11 +38,12 @@ def __init__( private_key_pass: Optional[str] = None, https_proxy: Optional[str] = None, time_unit: Optional[str] = None, + verbose: bool = False, ): self.https_proxy = https_proxy self.loop = loop or get_loop() self._session_params: Dict[str, Any] = session_params or {} - + # Convert https_proxy to requests_params format for BaseClient if https_proxy and requests_params is None: requests_params = {'proxies': {'http': https_proxy, 'https': https_proxy}} @@ -50,7 +51,7 @@ def __init__( if 'proxies' not in requests_params: requests_params['proxies'] = {} requests_params['proxies'].update({'http': https_proxy, 'https': https_proxy}) - + super().__init__( api_key, api_secret, @@ -62,6 +63,7 @@ def __init__( private_key, private_key_pass, time_unit=time_unit, + verbose=verbose, ) @classmethod @@ -80,6 +82,7 @@ async def create( private_key_pass: Optional[str] = None, https_proxy: Optional[str] = None, time_unit: Optional[str] = None, + verbose: bool = False, ): self = cls( api_key, @@ -94,7 +97,8 @@ async def create( private_key, private_key_pass, https_proxy, - time_unit + time_unit, + verbose ) self.https_proxy = https_proxy # move this to the constructor @@ -175,6 +179,20 @@ async def _request( **kwargs, ) as response: self.response = response + + if self.verbose: + response_text = await response.text() + self.logger.debug( + "\nRequest: %s %s\nRequestHeaders: %s\nRequestBody: %s\nResponse: %s\nResponseHeaders: %s\nResponseBody: %s", + method.upper(), + uri, + headers, + data, + response.status, + dict(response.headers), + response_text[:1000] if response_text else None + ) + return await self._handle_response(response) async def _handle_response(self, response: aiohttp.ClientResponse): diff --git a/binance/base_client.py b/binance/base_client.py index b7ae76d74..d6423048b 100644 --- a/binance/base_client.py +++ b/binance/base_client.py @@ -6,6 +6,7 @@ import asyncio import hashlib import hmac +import logging import time from Crypto.PublicKey import RSA, ECC from Crypto.Hash import SHA256 @@ -167,6 +168,7 @@ def __init__( private_key_pass: Optional[str] = None, loop: Optional[asyncio.AbstractEventLoop] = None, time_unit: Optional[str] = None, + verbose: bool = False, ): """Binance API Client constructor @@ -184,10 +186,19 @@ def __init__( :type private_key_pass: optional - str :param time_unit: Time unit to use for requests. Supported values: "MILLISECOND", "MICROSECOND" :type time_unit: optional - str + :param verbose: Enable verbose logging for debugging + :type verbose: bool """ self.tld = tld + self.verbose = verbose + self.logger = logging.getLogger(__name__) + + # Set logger level based on verbose flag + # Users can override this by configuring logging externally + if verbose: + self.logger.setLevel(logging.DEBUG) self.API_URL = self.API_URL.format(base_endpoint, tld) self.MARGIN_API_URL = self.MARGIN_API_URL.format(base_endpoint, tld) self.WEBSITE_URL = self.WEBSITE_URL.format(tld) diff --git a/binance/client.py b/binance/client.py index 95d761905..db7a373ac 100755 --- a/binance/client.py +++ b/binance/client.py @@ -34,6 +34,7 @@ def __init__( private_key_pass: Optional[str] = None, ping: Optional[bool] = True, time_unit: Optional[str] = None, + verbose: bool = False, ): super().__init__( api_key, @@ -46,6 +47,7 @@ def __init__( private_key, private_key_pass, time_unit=time_unit, + verbose=verbose, ) # init DNS and SSL cert @@ -88,6 +90,19 @@ def _request( data = f"{url_encoded_data}&signature={signature}" self.response = getattr(self.session, method)(uri, headers=headers, data=data, **kwargs) + + if self.verbose: + self.logger.debug( + "\nRequest: %s %s\nRequestHeaders: %s\nRequestBody: %s\nResponse: %s\nResponseHeaders: %s\nResponseBody: %s", + method.upper(), + uri, + headers, + data, + self.response.status_code, + dict(self.response.headers), + self.response.text[:1000] if self.response.text else None + ) + return self._handle_response(self.response) @staticmethod diff --git a/binance/ws/streams.py b/binance/ws/streams.py index 33d5f3bea..f3f27fe8b 100755 --- a/binance/ws/streams.py +++ b/binance/ws/streams.py @@ -2,6 +2,7 @@ import time from enum import Enum from typing import Optional, List, Dict, Callable, Any +import logging from binance.ws.constants import KEEPALIVE_TIMEOUT from binance.ws.keepalive_websocket import KeepAliveWebsocket @@ -40,10 +41,11 @@ class BinanceSocketManager: WEBSOCKET_DEPTH_20 = "20" def __init__( - self, - client: AsyncClient, + self, + client: AsyncClient, user_timeout=KEEPALIVE_TIMEOUT, max_queue_size: int = 100, + verbose: bool = False, ): """Initialise the BinanceSocketManager @@ -52,6 +54,8 @@ def __init__( :param user_timeout: Timeout for user socket in seconds :param max_queue_size: Max size of the websocket queue, defaults to 100 :type max_queue_size: int + :param verbose: Enable verbose logging for WebSocket connections + :type verbose: bool """ self.STREAM_URL = self.STREAM_URL.format(client.tld) self.FSTREAM_URL = self.FSTREAM_URL.format(client.tld) @@ -65,8 +69,12 @@ def __init__( self.testnet = self._client.testnet self.demo = self._client.demo self._max_queue_size = max_queue_size + self.verbose = verbose self.ws_kwargs = {} + if verbose: + logging.getLogger('binance.ws').setLevel(logging.DEBUG) + def _get_stream_url(self, stream_url: Optional[str] = None): if stream_url: return stream_url @@ -871,7 +879,7 @@ def multiplex_socket(self, streams: List[str]): def options_multiplex_socket(self, streams: List[str]): """Start a multiplexed socket using a list of socket names. - + https://developers.binance.com/docs/derivatives/option/websocket-market-streams """ @@ -966,7 +974,6 @@ def margin_socket(self): stream_url = self.STREAM_DEMO_URL return self._get_account_socket("margin", stream_url=stream_url) - def futures_socket(self): """Start a websocket for futures data @@ -1040,7 +1047,7 @@ def options_ticker_socket(self, symbol: str): API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/24-hour-TICKER - Stream provides real-time 24hr ticker information for all symbols. Only symbols whose ticker info + Stream provides real-time 24hr ticker information for all symbols. Only symbols whose ticker info changed will be sent. Updates every 1000ms. :param symbol: The option symbol to subscribe to (e.g. "BTC-220930-18000-C") @@ -1065,7 +1072,7 @@ def options_ticker_by_expiration_socket(self, symbol: str, expiration_date: str) def options_recent_trades_socket(self, symbol: str): """Subscribe to a real-time trade information stream. - + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Trade-Streams Stream pushes raw trade information for a specific symbol or underlying asset. @@ -1080,7 +1087,7 @@ def options_kline_socket( self, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE ): """Subscribe to a Kline/Candlestick data stream. - + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Kline-Candlestick-Streams Stream pushes updates to the current klines/candlestick every 1000ms (if existing). diff --git a/binance/ws/threaded_stream.py b/binance/ws/threaded_stream.py index 47993e0c6..8bf6eb6e4 100755 --- a/binance/ws/threaded_stream.py +++ b/binance/ws/threaded_stream.py @@ -18,14 +18,28 @@ def __init__( session_params: Optional[Dict[str, Any]] = None, https_proxy: Optional[str] = None, _loop: Optional[asyncio.AbstractEventLoop] = None, + verbose: bool = False, ): - """Initialise the BinanceSocketManager""" + """Initialise the ThreadedApiManager + + :param api_key: Binance API key + :param api_secret: Binance API secret + :param requests_params: optional - Dictionary of requests params + :param tld: optional - Top level domain, default is 'com' + :param testnet: optional - Use testnet endpoint + :param session_params: optional - Session params for aiohttp + :param https_proxy: optional - Proxy URL + :param _loop: optional - Event loop + :param verbose: Enable verbose logging for WebSocket connections + :type verbose: bool + """ super().__init__() self._loop: asyncio.AbstractEventLoop = get_loop() if _loop is None else _loop self._client: Optional[AsyncClient] = None self._running: bool = True self._socket_running: Dict[str, bool] = {} self._log = logging.getLogger(__name__) + self.verbose = verbose self._client_params = { "api_key": api_key, "api_secret": api_secret, @@ -34,8 +48,12 @@ def __init__( "testnet": testnet, "session_params": session_params, "https_proxy": https_proxy, + "verbose": verbose, } + if verbose: + logging.getLogger('binance.ws').setLevel(logging.DEBUG) + async def _before_socket_listener_start(self): ... async def socket_listener(self): diff --git a/docs/faqs.rst b/docs/faqs.rst index 43eb2d3cc..db8c9558f 100644 --- a/docs/faqs.rst +++ b/docs/faqs.rst @@ -20,3 +20,15 @@ Check recvWindow is an integer and not a string. *A2*: You may need to regenerate your API Key and Secret *A3*: You may be attempting to access the API from a Chinese IP address, these are now restricted by Binance. + +*Q: How can I debug API issues?* + +*A*: Enable verbose mode to see detailed request and response information: + +.. code:: python + + client = Client(api_key, api_secret, verbose=True) + +This will log all HTTP requests and responses, including headers, body, and status codes. This is particularly helpful for debugging authentication issues, understanding API behavior, and troubleshooting network problems. + +See the Logging section in the `Getting Started guide `_ for more details. diff --git a/docs/overview.rst b/docs/overview.rst index fa8f0dca3..f0d6cedd6 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -276,4 +276,201 @@ For more detailed logging with timestamps and log levels: format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', ) +Verbose Mode +~~~~~~~~~~~~ + +Verbose mode provides detailed logging of all HTTP requests and responses, which is particularly useful for debugging API issues, understanding request/response formats, and troubleshooting authentication or network problems. + +Method 1: Using the verbose Parameter (Recommended for Quick Debugging) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Enable verbose mode by passing ``verbose=True`` when creating the client: + +.. code:: python + + from binance.client import Client + import logging + + # Configure logging (optional - for seeing the output) + logging.basicConfig(level=logging.DEBUG) + + # Enable verbose mode + client = Client(api_key, api_secret, verbose=True) + + # All API calls will now log detailed information + server_time = client.get_server_time() + +For AsyncClient: + +.. code:: python + + import asyncio + import logging + from binance.async_client import AsyncClient + + logging.basicConfig(level=logging.DEBUG) + + async def main(): + # Enable verbose mode + client = await AsyncClient.create(api_key, api_secret, verbose=True) + + # All API calls will now log detailed information + server_time = await client.get_server_time() + + await client.close_connection() + + if __name__ == "__main__": + asyncio.run(main()) + +Method 2: Using Python's Logging Module (Recommended for Production) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For more control over logging configuration, use Python's standard logging module: + +.. code:: python + + import logging + from binance.client import Client + + # Configure logging for binance module + logging.basicConfig( + level=logging.INFO, # Set root level + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + # Enable debug logging for binance specifically + logging.getLogger('binance.base_client').setLevel(logging.DEBUG) + + # Create client (verbose parameter not needed) + client = Client(api_key, api_secret) + +This approach gives you fine-grained control and integrates with your application's existing logging infrastructure. + +What Gets Logged +^^^^^^^^^^^^^^^^ + +When verbose mode is enabled, you'll see detailed logs for each request including: + +- HTTP method and URL +- Request headers and body +- Response status code +- Response headers and body (truncated to 1000 characters) + +Example output: + +.. code-block:: text + + 2025-11-30 22:01:26,957 - binance.base_client - DEBUG - + Request: GET https://api.binance.com/api/v3/time + RequestHeaders: {'Accept': 'application/json', 'Content-Type': 'application/json'} + RequestBody: None + Response: 200 + ResponseHeaders: {'Content-Type': 'application/json;charset=UTF-8', ...} + ResponseBody: {"serverTime":1764536487218} + +**Note:** Verbose mode should typically be disabled in production environments to minimize overhead and log volume. Use the logging module approach for production with appropriate log levels. + +WebSocket Verbose Logging +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +WebSocket connections support verbose mode just like REST API calls. + +Method 1: Using the verbose Parameter (Recommended for Quick Debugging) +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.. code:: python + + import logging + from binance import AsyncClient, BinanceSocketManager + + # Configure logging to see output + logging.basicConfig(level=logging.DEBUG) + + async def main(): + client = await AsyncClient.create() + + # Enable verbose mode for WebSocket connections + bm = BinanceSocketManager(client, verbose=True) + + # WebSocket messages will be logged at DEBUG level + ts = bm.trade_socket('BTCUSDT') + async with ts as tscm: + msg = await tscm.recv() + print(msg) + + await client.close_connection() + +Method 2: Using Python's Logging Module (Recommended for Production) +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.. code:: python + + import logging + from binance import AsyncClient, BinanceSocketManager + + # Configure logging + logging.basicConfig(level=logging.DEBUG) + + # Enable debug logging for all WebSocket connections + logging.getLogger('binance.ws').setLevel(logging.DEBUG) + + async def main(): + client = await AsyncClient.create() + bm = BinanceSocketManager(client) + + # WebSocket messages will be logged at DEBUG level + ts = bm.trade_socket('BTCUSDT') + async with ts as tscm: + msg = await tscm.recv() + print(msg) + + await client.close_connection() + +You can also enable logging for specific WebSocket components: + +.. code:: python + + # Log only WebSocket API messages + logging.getLogger('binance.ws.websocket_api').setLevel(logging.DEBUG) + + # Log reconnection events + logging.getLogger('binance.ws.reconnecting_websocket').setLevel(logging.DEBUG) + + # Log stream events + logging.getLogger('binance.ws.streams').setLevel(logging.DEBUG) + +WebSocket debug logs include: + +- Raw received messages +- Connection state changes +- Reconnection attempts +- Subscription events +- Error messages + +**Tip:** For comprehensive debugging, enable verbose mode for both REST API and WebSocket connections: + +.. code:: python + + import logging + from binance import AsyncClient, BinanceSocketManager + + logging.basicConfig(level=logging.DEBUG) + + # Enable verbose for both REST API and WebSocket + client = await AsyncClient.create(verbose=True) + bm = BinanceSocketManager(client, verbose=True) + +For Threaded WebSocket Manager: + +.. code:: python + + import logging + from binance.ws.threaded_stream import ThreadedApiManager + + logging.basicConfig(level=logging.DEBUG) + + # Enable verbose mode for threaded WebSocket manager + twm = ThreadedApiManager(api_key='your_key', api_secret='your_secret', verbose=True) + twm.start() + .. image:: https://analytics-pixel.appspot.com/UA-111417213-1/github/python-binance/docs/overview?pixel diff --git a/examples/verbose_example.py b/examples/verbose_example.py new file mode 100644 index 000000000..e9f4d3691 --- /dev/null +++ b/examples/verbose_example.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +""" +Comprehensive verbose mode example for python-binance + +This example demonstrates verbose logging for: +- Synchronous Client (REST API) +- Async Client (REST API) +- WebSocket streams (BinanceSocketManager) +""" + +import asyncio +import logging +from binance import Client, AsyncClient, BinanceSocketManager + +# Configure logging to see verbose output +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +def sync_client_example(): + """Example 1: Synchronous Client with verbose mode""" + print("\n" + "="*80) + print("Example 1: Synchronous Client (verbose=True)") + print("="*80) + + client = Client(verbose=True) + + # Make API call - will show detailed HTTP logs + server_time = client.get_server_time() + print(f"Server time: {server_time['serverTime']}\n") + +async def async_client_example(): + """Example 2: Async Client with verbose mode""" + print("\n" + "="*80) + print("Example 2: Async Client (verbose=True)") + print("="*80) + + client = await AsyncClient.create(verbose=True) + + # Make API call - will show detailed HTTP logs + ticker = await client.get_symbol_ticker(symbol='BTCUSDT') + print(f"BTC/USDT price: {ticker['price']}\n") + + await client.close_connection() + +async def websocket_example(): + """Example 3: WebSocket with verbose mode""" + print("\n" + "="*80) + print("Example 3: WebSocket Streams (verbose=True)") + print("="*80) + + client = await AsyncClient.create() + + # Create socket manager with verbose mode + bm = BinanceSocketManager(client, verbose=True) + + # Start trade socket - will show detailed WebSocket logs + ts = bm.trade_socket('ETHUSDT') + + print("Receiving 3 trade messages...\n") + async with ts as tscm: + for i in range(3): + msg = await tscm.recv() + print(f"Trade {i+1}: Price={msg['p']}, Qty={msg['q']}") + + await client.close_connection() + +async def combined_example(): + """Example 4: Combined REST + WebSocket verbose mode""" + print("\n" + "="*80) + print("Example 4: Combined REST + WebSocket (both verbose=True)") + print("="*80) + + # Enable verbose for both REST and WebSocket + client = await AsyncClient.create(verbose=True) + bm = BinanceSocketManager(client, verbose=True) + + # REST API call + await client.get_server_time() + + # WebSocket stream + ts = bm.trade_socket('BNBUSDT') + async with ts as tscm: + msg = await tscm.recv() + print(f"BNB/USDT trade: {msg['p']}\n") + + await client.close_connection() + +def main(): + """Run all examples""" + print("\n" + "="*80) + print("Python-Binance Verbose Mode Examples") + print("="*80) + print("\nThese examples show how to enable verbose logging for debugging:") + print("- REST API requests/responses (HTTP details)") + print("- WebSocket messages (connection & data)") + print("\nWatch the DEBUG logs above each example output.\n") + + # Run sync example + sync_client_example() + + # Run async examples + asyncio.run(async_client_example()) + asyncio.run(websocket_example()) + asyncio.run(combined_example()) + + print("\n" + "="*80) + print("All examples completed!") + print("="*80) + print("\nKey takeaways:") + print(" ✓ Use verbose=True for quick debugging") + print(" ✓ Works with Client, AsyncClient, and BinanceSocketManager") + print(" ✓ Shows full HTTP request/response details") + print(" ✓ Logs WebSocket messages as they arrive") + print(" ✓ Disable verbose mode in production (it's off by default)") + +if __name__ == "__main__": + main() diff --git a/tests/test_verbose_mode.py b/tests/test_verbose_mode.py new file mode 100644 index 000000000..9c501985f --- /dev/null +++ b/tests/test_verbose_mode.py @@ -0,0 +1,55 @@ +"""Tests for verbose mode functionality""" +import pytest +import logging +from binance.client import Client +from binance.async_client import AsyncClient + + +def test_client_verbose_initialization(): + """Test that Client can be initialized with verbose mode""" + client = Client(verbose=True) + assert client.verbose is True + assert client.logger is not None + assert client.logger.level == logging.DEBUG + + +def test_client_non_verbose_initialization(): + """Test that Client defaults to non-verbose mode""" + client = Client(verbose=False) + assert client.verbose is False + assert client.logger is not None + # When verbose=False, we don't set the logger level (respects external config) + + +def test_client_default_verbose(): + """Test that Client defaults to verbose=False""" + client = Client() + assert client.verbose is False + + +@pytest.mark.asyncio() +async def test_async_client_verbose_initialization(): + """Test that AsyncClient can be initialized with verbose mode""" + client = await AsyncClient.create(verbose=True) + assert client.verbose is True + assert client.logger is not None + assert client.logger.level == logging.DEBUG + await client.close_connection() + + +@pytest.mark.asyncio() +async def test_async_client_non_verbose_initialization(): + """Test that AsyncClient defaults to non-verbose mode""" + client = await AsyncClient.create(verbose=False) + assert client.verbose is False + assert client.logger is not None + # When verbose=False, we don't set the logger level (respects external config) + await client.close_connection() + + +@pytest.mark.asyncio() +async def test_async_client_default_verbose(): + """Test that AsyncClient defaults to verbose=False""" + client = await AsyncClient.create() + assert client.verbose is False + await client.close_connection() diff --git a/tests/test_websocket_verbose.py b/tests/test_websocket_verbose.py new file mode 100644 index 000000000..4052bf3b1 --- /dev/null +++ b/tests/test_websocket_verbose.py @@ -0,0 +1,116 @@ +"""Tests for WebSocket verbose logging""" +import logging +import pytest + + +def test_websocket_logger_exists(): + """Test that WebSocket loggers can be configured""" + # Test main WebSocket logger + ws_logger = logging.getLogger('binance.ws') + assert ws_logger is not None + + # Test WebSocket API logger + ws_api_logger = logging.getLogger('binance.ws.websocket_api') + assert ws_api_logger is not None + + # Test reconnecting WebSocket logger + ws_reconnect_logger = logging.getLogger('binance.ws.reconnecting_websocket') + assert ws_reconnect_logger is not None + + # Test streams logger + ws_streams_logger = logging.getLogger('binance.ws.streams') + assert ws_streams_logger is not None + + +def test_websocket_logger_level_configuration(): + """Test that WebSocket logger levels can be set""" + ws_logger = logging.getLogger('binance.ws.test_config') + + # Set to DEBUG + ws_logger.setLevel(logging.DEBUG) + assert ws_logger.level == logging.DEBUG + + # Set to INFO + ws_logger.setLevel(logging.INFO) + assert ws_logger.level == logging.INFO + + # Set to WARNING + ws_logger.setLevel(logging.WARNING) + assert ws_logger.level == logging.WARNING + + +def test_combined_logging_configuration(): + """Test that both REST and WebSocket logging can be configured together""" + # Configure REST verbose logging + from binance.client import Client + + # This should work without errors + client = Client(verbose=True) + assert client.verbose is True + assert client.logger is not None + + # Configure WebSocket logging + ws_logger = logging.getLogger('binance.ws') + ws_logger.setLevel(logging.DEBUG) + assert ws_logger.level == logging.DEBUG + + # Both should be independently configurable + assert client.logger.level == logging.DEBUG + assert ws_logger.level == logging.DEBUG + + +@pytest.mark.asyncio() +async def test_binance_socket_manager_verbose(): + """Test that BinanceSocketManager can be initialized with verbose mode""" + from binance import AsyncClient, BinanceSocketManager + + client = await AsyncClient.create() + + # Test with verbose=True + bm_verbose = BinanceSocketManager(client, verbose=True) + assert bm_verbose.verbose is True + + # Test with verbose=False (default) + bm_quiet = BinanceSocketManager(client, verbose=False) + assert bm_quiet.verbose is False + + # Test default + bm_default = BinanceSocketManager(client) + assert bm_default.verbose is False + + await client.close_connection() + + +def test_threaded_websocket_manager_verbose(): + """Test that ThreadedApiManager can be initialized with verbose mode""" + from binance.ws.threaded_stream import ThreadedApiManager + + # Test with verbose=True + twm_verbose = ThreadedApiManager(verbose=True) + assert twm_verbose.verbose is True + + # Test with verbose=False (default) + twm_quiet = ThreadedApiManager(verbose=False) + assert twm_quiet.verbose is False + + # Test default + twm_default = ThreadedApiManager() + assert twm_default.verbose is False + + +def test_websocket_manager_sets_logging_level(): + """Test that verbose mode sets the logging level for WebSocket managers""" + from binance.ws.threaded_stream import ThreadedApiManager + + # Get initial logging level + ws_logger = logging.getLogger('binance.ws') + initial_level = ws_logger.level + + # Create manager with verbose=True + twm = ThreadedApiManager(verbose=True) + + # Check that logging level was set to DEBUG + assert ws_logger.level == logging.DEBUG + + # Restore initial level + ws_logger.setLevel(initial_level) From 3f5cfa33af1e846695d37d46f69f594d038c1e69 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 1 Dec 2025 08:47:05 +0100 Subject: [PATCH 2/4] lint --- examples/verbose_example.py | 41 ++++++++++++++++++--------------- tests/test_threaded_stream.py | 3 +++ tests/test_verbose_mode.py | 1 + tests/test_websocket_verbose.py | 15 ++++++------ 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/examples/verbose_example.py b/examples/verbose_example.py index e9f4d3691..e07e3f594 100644 --- a/examples/verbose_example.py +++ b/examples/verbose_example.py @@ -14,15 +14,15 @@ # Configure logging to see verbose output logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) + def sync_client_example(): """Example 1: Synchronous Client with verbose mode""" - print("\n" + "="*80) + print("\n" + "=" * 80) print("Example 1: Synchronous Client (verbose=True)") - print("="*80) + print("=" * 80) client = Client(verbose=True) @@ -30,25 +30,27 @@ def sync_client_example(): server_time = client.get_server_time() print(f"Server time: {server_time['serverTime']}\n") + async def async_client_example(): """Example 2: Async Client with verbose mode""" - print("\n" + "="*80) + print("\n" + "=" * 80) print("Example 2: Async Client (verbose=True)") - print("="*80) + print("=" * 80) client = await AsyncClient.create(verbose=True) # Make API call - will show detailed HTTP logs - ticker = await client.get_symbol_ticker(symbol='BTCUSDT') + ticker = await client.get_symbol_ticker(symbol="BTCUSDT") print(f"BTC/USDT price: {ticker['price']}\n") await client.close_connection() + async def websocket_example(): """Example 3: WebSocket with verbose mode""" - print("\n" + "="*80) + print("\n" + "=" * 80) print("Example 3: WebSocket Streams (verbose=True)") - print("="*80) + print("=" * 80) client = await AsyncClient.create() @@ -56,21 +58,22 @@ async def websocket_example(): bm = BinanceSocketManager(client, verbose=True) # Start trade socket - will show detailed WebSocket logs - ts = bm.trade_socket('ETHUSDT') + ts = bm.trade_socket("ETHUSDT") print("Receiving 3 trade messages...\n") async with ts as tscm: for i in range(3): msg = await tscm.recv() - print(f"Trade {i+1}: Price={msg['p']}, Qty={msg['q']}") + print(f"Trade {i + 1}: Price={msg['p']}, Qty={msg['q']}") await client.close_connection() + async def combined_example(): """Example 4: Combined REST + WebSocket verbose mode""" - print("\n" + "="*80) + print("\n" + "=" * 80) print("Example 4: Combined REST + WebSocket (both verbose=True)") - print("="*80) + print("=" * 80) # Enable verbose for both REST and WebSocket client = await AsyncClient.create(verbose=True) @@ -80,18 +83,19 @@ async def combined_example(): await client.get_server_time() # WebSocket stream - ts = bm.trade_socket('BNBUSDT') + ts = bm.trade_socket("BNBUSDT") async with ts as tscm: msg = await tscm.recv() print(f"BNB/USDT trade: {msg['p']}\n") await client.close_connection() + def main(): """Run all examples""" - print("\n" + "="*80) + print("\n" + "=" * 80) print("Python-Binance Verbose Mode Examples") - print("="*80) + print("=" * 80) print("\nThese examples show how to enable verbose logging for debugging:") print("- REST API requests/responses (HTTP details)") print("- WebSocket messages (connection & data)") @@ -105,9 +109,9 @@ def main(): asyncio.run(websocket_example()) asyncio.run(combined_example()) - print("\n" + "="*80) + print("\n" + "=" * 80) print("All examples completed!") - print("="*80) + print("=" * 80) print("\nKey takeaways:") print(" ✓ Use verbose=True for quick debugging") print(" ✓ Works with Client, AsyncClient, and BinanceSocketManager") @@ -115,5 +119,6 @@ def main(): print(" ✓ Logs WebSocket messages as they arrive") print(" ✓ Disable verbose mode in production (it's off by default)") + if __name__ == "__main__": main() diff --git a/tests/test_threaded_stream.py b/tests/test_threaded_stream.py index 2d726934a..44a62a52c 100644 --- a/tests/test_threaded_stream.py +++ b/tests/test_threaded_stream.py @@ -26,6 +26,7 @@ async def __aiter__(self): async def __anext__(self): raise StopAsyncIteration + @pytest.mark.asyncio async def test_initialization(): """Test that manager initializes with correct parameters""" @@ -48,6 +49,7 @@ async def test_initialization(): "testnet": True, "session_params": {"trust_env": True}, "https_proxy": None, + "verbose": False, } @@ -63,6 +65,7 @@ async def test_start_and_stop_socket(manager): # Track number of recv calls recv_count = 0 + async def controlled_recv(): nonlocal recv_count recv_count += 1 diff --git a/tests/test_verbose_mode.py b/tests/test_verbose_mode.py index 9c501985f..8776ac76a 100644 --- a/tests/test_verbose_mode.py +++ b/tests/test_verbose_mode.py @@ -1,4 +1,5 @@ """Tests for verbose mode functionality""" + import pytest import logging from binance.client import Client diff --git a/tests/test_websocket_verbose.py b/tests/test_websocket_verbose.py index 4052bf3b1..e40532514 100644 --- a/tests/test_websocket_verbose.py +++ b/tests/test_websocket_verbose.py @@ -1,4 +1,5 @@ """Tests for WebSocket verbose logging""" + import logging import pytest @@ -6,25 +7,25 @@ def test_websocket_logger_exists(): """Test that WebSocket loggers can be configured""" # Test main WebSocket logger - ws_logger = logging.getLogger('binance.ws') + ws_logger = logging.getLogger("binance.ws") assert ws_logger is not None # Test WebSocket API logger - ws_api_logger = logging.getLogger('binance.ws.websocket_api') + ws_api_logger = logging.getLogger("binance.ws.websocket_api") assert ws_api_logger is not None # Test reconnecting WebSocket logger - ws_reconnect_logger = logging.getLogger('binance.ws.reconnecting_websocket') + ws_reconnect_logger = logging.getLogger("binance.ws.reconnecting_websocket") assert ws_reconnect_logger is not None # Test streams logger - ws_streams_logger = logging.getLogger('binance.ws.streams') + ws_streams_logger = logging.getLogger("binance.ws.streams") assert ws_streams_logger is not None def test_websocket_logger_level_configuration(): """Test that WebSocket logger levels can be set""" - ws_logger = logging.getLogger('binance.ws.test_config') + ws_logger = logging.getLogger("binance.ws.test_config") # Set to DEBUG ws_logger.setLevel(logging.DEBUG) @@ -50,7 +51,7 @@ def test_combined_logging_configuration(): assert client.logger is not None # Configure WebSocket logging - ws_logger = logging.getLogger('binance.ws') + ws_logger = logging.getLogger("binance.ws") ws_logger.setLevel(logging.DEBUG) assert ws_logger.level == logging.DEBUG @@ -103,7 +104,7 @@ def test_websocket_manager_sets_logging_level(): from binance.ws.threaded_stream import ThreadedApiManager # Get initial logging level - ws_logger = logging.getLogger('binance.ws') + ws_logger = logging.getLogger("binance.ws") initial_level = ws_logger.level # Create manager with verbose=True From 0528a563e7ea9ed9ce3d03f0f4d2b36390bb3dc3 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 1 Dec 2025 09:00:13 +0100 Subject: [PATCH 3/4] fix test --- tests/test_verbose_mode.py | 6 +++--- tests/test_websocket_verbose.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_verbose_mode.py b/tests/test_verbose_mode.py index 8776ac76a..42360d184 100644 --- a/tests/test_verbose_mode.py +++ b/tests/test_verbose_mode.py @@ -31,7 +31,7 @@ def test_client_default_verbose(): @pytest.mark.asyncio() async def test_async_client_verbose_initialization(): """Test that AsyncClient can be initialized with verbose mode""" - client = await AsyncClient.create(verbose=True) + client = AsyncClient(verbose=True) assert client.verbose is True assert client.logger is not None assert client.logger.level == logging.DEBUG @@ -41,7 +41,7 @@ async def test_async_client_verbose_initialization(): @pytest.mark.asyncio() async def test_async_client_non_verbose_initialization(): """Test that AsyncClient defaults to non-verbose mode""" - client = await AsyncClient.create(verbose=False) + client = AsyncClient(verbose=False) assert client.verbose is False assert client.logger is not None # When verbose=False, we don't set the logger level (respects external config) @@ -51,6 +51,6 @@ async def test_async_client_non_verbose_initialization(): @pytest.mark.asyncio() async def test_async_client_default_verbose(): """Test that AsyncClient defaults to verbose=False""" - client = await AsyncClient.create() + client = AsyncClient() assert client.verbose is False await client.close_connection() diff --git a/tests/test_websocket_verbose.py b/tests/test_websocket_verbose.py index e40532514..067b7139e 100644 --- a/tests/test_websocket_verbose.py +++ b/tests/test_websocket_verbose.py @@ -65,7 +65,7 @@ async def test_binance_socket_manager_verbose(): """Test that BinanceSocketManager can be initialized with verbose mode""" from binance import AsyncClient, BinanceSocketManager - client = await AsyncClient.create() + client = AsyncClient() # Test with verbose=True bm_verbose = BinanceSocketManager(client, verbose=True) From 05285dc56511ad7e48f32b0aca433c0bbee2f831 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 1 Dec 2025 09:33:56 +0100 Subject: [PATCH 4/4] fix tests --- tests/test_verbose_mode.py | 6 +++--- tests/test_websocket_verbose.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_verbose_mode.py b/tests/test_verbose_mode.py index 42360d184..b2689a5dd 100644 --- a/tests/test_verbose_mode.py +++ b/tests/test_verbose_mode.py @@ -8,7 +8,7 @@ def test_client_verbose_initialization(): """Test that Client can be initialized with verbose mode""" - client = Client(verbose=True) + client = Client(verbose=True, ping=False) assert client.verbose is True assert client.logger is not None assert client.logger.level == logging.DEBUG @@ -16,7 +16,7 @@ def test_client_verbose_initialization(): def test_client_non_verbose_initialization(): """Test that Client defaults to non-verbose mode""" - client = Client(verbose=False) + client = Client(verbose=False, ping=False) assert client.verbose is False assert client.logger is not None # When verbose=False, we don't set the logger level (respects external config) @@ -24,7 +24,7 @@ def test_client_non_verbose_initialization(): def test_client_default_verbose(): """Test that Client defaults to verbose=False""" - client = Client() + client = Client(ping=False) assert client.verbose is False diff --git a/tests/test_websocket_verbose.py b/tests/test_websocket_verbose.py index 067b7139e..1effa29c9 100644 --- a/tests/test_websocket_verbose.py +++ b/tests/test_websocket_verbose.py @@ -46,7 +46,7 @@ def test_combined_logging_configuration(): from binance.client import Client # This should work without errors - client = Client(verbose=True) + client = Client(verbose=True, ping=False) assert client.verbose is True assert client.logger is not None